Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edcba24399 | ||
|
|
484d7888cf | ||
|
|
187f1929fe | ||
|
|
0a4333645b | ||
|
|
310506e5af | ||
|
|
0b33ed4299 | ||
|
|
13f2e51798 | ||
|
|
fc047feeee | ||
|
|
005455c870 | ||
|
|
2c2bc391e8 | ||
|
|
445a085190 |
@@ -1,10 +1,4 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
const webpack = require('webpack');
|
||||
const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
module.exports = defineConfig({
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
@@ -13,36 +7,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
|
||||
const defaultConfig = webpackPreprocessor.defaultOptions;
|
||||
defaultConfig.webpackOptions.context = path.resolve(__dirname, './');
|
||||
defaultConfig.webpackOptions.module.rules[1].test = /(\.tsx?|\.jsx?)$/;
|
||||
defaultConfig.webpackOptions.module.rules[1].exclude = [
|
||||
/node_modules\/(?!(@vegaprotocol\/protos|protobuf-codec))/,
|
||||
/browserslist/,
|
||||
];
|
||||
defaultConfig.webpackOptions.module.rules.push({
|
||||
test: /(\.css|\.s[ac]ss)$/i,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
});
|
||||
defaultConfig.webpackOptions.plugins =
|
||||
defaultConfig.webpackOptions.plugins || [];
|
||||
defaultConfig.webpackOptions.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify(process.env),
|
||||
})
|
||||
);
|
||||
defaultConfig.webpackOptions.cache = {
|
||||
type: 'filesystem',
|
||||
allowCollectingMemory: true,
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
};
|
||||
defaultConfig.typescript = require.resolve('typescript');
|
||||
|
||||
on('file:preprocessor', webpackPreprocessor(defaultConfig));
|
||||
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
import '../../../../setup-test';
|
||||
|
||||
Object.defineProperty(window, 'ResizeObserver', {
|
||||
writable: false,
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
const webpack = require('webpack');
|
||||
const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
module.exports = defineConfig({
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
@@ -13,36 +7,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
|
||||
const defaultConfig = webpackPreprocessor.defaultOptions;
|
||||
defaultConfig.webpackOptions.context = path.resolve(__dirname, './');
|
||||
defaultConfig.webpackOptions.module.rules[1].test = /(\.tsx?|\.jsx?)$/;
|
||||
defaultConfig.webpackOptions.module.rules[1].exclude = [
|
||||
/node_modules\/(?!(@vegaprotocol\/protos|protobuf-codec))/,
|
||||
/browserslist/,
|
||||
];
|
||||
defaultConfig.webpackOptions.module.rules.push({
|
||||
test: /(\.css|\.s[ac]ss)$/i,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
});
|
||||
defaultConfig.webpackOptions.plugins =
|
||||
defaultConfig.webpackOptions.plugins || [];
|
||||
defaultConfig.webpackOptions.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify(process.env),
|
||||
})
|
||||
);
|
||||
defaultConfig.webpackOptions.cache = {
|
||||
type: 'filesystem',
|
||||
allowCollectingMemory: true,
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
};
|
||||
defaultConfig.typescript = require.resolve('typescript');
|
||||
|
||||
on('file:preprocessor', webpackPreprocessor(defaultConfig));
|
||||
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:4210',
|
||||
@@ -85,7 +49,7 @@ module.exports = defineConfig({
|
||||
vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7',
|
||||
vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de',
|
||||
txTimeout: { timeout: 70000 },
|
||||
epochTimeout: { timeout: 10000 },
|
||||
epochTimeout: { timeout: 12000 },
|
||||
blockConfirmations: 3,
|
||||
grepTags: '@regression @smoke @slow',
|
||||
grepFilterSpecs: true,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
export const previousEpochData = {
|
||||
epoch: {
|
||||
id: '7611',
|
||||
validatorsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: 'cd96782bc0ad5679869cf69fe7838a92212da7f53b4a214bed68067117494122',
|
||||
stakedTotal: '3154229668720612941799',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '0',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211713765544955625',
|
||||
stakeScore: '0.2016321576618625',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '887d936f797a47032eceb572a13b69b581d3b1fa595a7d021a3e3cf2a5d2acfd',
|
||||
stakedTotal: '3151161904761904764551',
|
||||
rewardScore: {
|
||||
rawValidatorScore: '0.2',
|
||||
performanceScore: '1',
|
||||
multisigScore: '1',
|
||||
validatorScore: '0.2',
|
||||
normalisedScore: '0.2007216887087119',
|
||||
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
__typename: 'RewardScore',
|
||||
},
|
||||
rankingScore: {
|
||||
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
|
||||
rankingScore: '0.211507855409133255',
|
||||
stakeScore: '0.2014360527706031',
|
||||
performanceScore: '1',
|
||||
votingPower: '2007',
|
||||
__typename: 'RankingScore',
|
||||
},
|
||||
__typename: 'Node',
|
||||
},
|
||||
__typename: 'NodeEdge',
|
||||
},
|
||||
],
|
||||
__typename: 'NodesConnection',
|
||||
},
|
||||
__typename: 'Epoch',
|
||||
},
|
||||
};
|
||||
@@ -58,8 +58,6 @@ context(
|
||||
before('visit staking tab and connect vega wallet', function () {
|
||||
cy.visit('/');
|
||||
ethereumWalletConnect();
|
||||
// this is a workaround for #2422 which can be removed once issue is resolved
|
||||
cy.associateTokensToVegaWallet('4');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
navigation,
|
||||
verifyPageHeader,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
clickOnValidatorFromList,
|
||||
waitForBeginningOfEpoch,
|
||||
} from '../../support/staking.functions';
|
||||
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
|
||||
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
@@ -30,6 +32,7 @@ const normalisedVotingPowerToolTip =
|
||||
'[data-testid="normalised-voting-power-tooltip"]';
|
||||
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
|
||||
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
|
||||
const multisigPenaltyToolTip = '[data-testid="multisig-error-tooltip"]';
|
||||
const epochCountDown = '[data-testid="epoch-countdown"]';
|
||||
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
|
||||
|
||||
@@ -151,6 +154,22 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.wrap($pendingStake).should('contain.text', '0.00');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to see multisig error', function () {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'PreviousEpoch', previousEpochData);
|
||||
});
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('total-penalty').first().realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
|
||||
cy.getByTestId('total-penalty').eq(1).realHover();
|
||||
cy.get(multisigPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Multisig penalty: 100%');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -282,33 +282,33 @@ context(
|
||||
// 2002-SINC-016
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: 10.0,
|
||||
},
|
||||
{
|
||||
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
|
||||
name: 'DAI (fake)',
|
||||
symbol: 'fDAI',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
expectedAmount: 2.0,
|
||||
},
|
||||
{
|
||||
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
|
||||
name: 'BTC (fake)',
|
||||
symbol: 'fBTC',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
expectedAmount: 6.0,
|
||||
},
|
||||
{
|
||||
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
|
||||
name: 'EURO (fake)',
|
||||
symbol: 'fEURO',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
expectedAmount: 8.0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -319,12 +319,6 @@ context(
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.at.least',
|
||||
5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
for (const { name, symbol, expectedAmount } of assets) {
|
||||
@@ -338,10 +332,10 @@ context(
|
||||
.contains(name)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('have.length.at.least', 4)
|
||||
.then(parseFloat)
|
||||
.should('be.gte', parseFloat(expectedAmount));
|
||||
.then((elementAmount) => {
|
||||
const displayedAmount = parseFloat(elementAmount.text());
|
||||
expect(displayedAmount).be.gte(expectedAmount);
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(name)
|
||||
|
||||
@@ -37,7 +37,7 @@ export function navigateTo(page: navigation) {
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(page).eq(0).click();
|
||||
cy.get(page).eq(0).click({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { BigNumber } from '../../../lib/bignumber';
|
||||
import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
import { vega as VegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
interface PendingStakeProps {
|
||||
pendingAmount: BigNumber;
|
||||
@@ -39,7 +38,7 @@ export const PendingStake = ({
|
||||
undelegateSubmission: {
|
||||
nodeId,
|
||||
amount: removeDecimal(pendingAmount.toString(), appState.decimals),
|
||||
method: VegaProtos.commands.v1.UndelegateSubmission.Method.METHOD_NOW,
|
||||
method: 'METHOD_NOW',
|
||||
},
|
||||
};
|
||||
await sendTx(pubKey, command);
|
||||
|
||||
@@ -4,7 +4,6 @@ import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ENV } from '../../../config';
|
||||
import { vega as VegaProtos } from '@vegaprotocol/protos';
|
||||
import { usePartyDelegationsLazyQuery } from './__generated__/PartyDelegations';
|
||||
import { TokenInput } from '../../../components/token-input';
|
||||
import { useAppState } from '../../../contexts/app-state/app-state-context';
|
||||
@@ -129,9 +128,8 @@ export const StakingForm = ({
|
||||
amount: removeDecimal(amount, appState.decimals),
|
||||
method:
|
||||
removeType === RemoveType.Now
|
||||
? VegaProtos.commands.v1.UndelegateSubmission.Method.METHOD_NOW
|
||||
: VegaProtos.commands.v1.UndelegateSubmission.Method
|
||||
.METHOD_AT_END_OF_EPOCH,
|
||||
? 'METHOD_NOW'
|
||||
: 'METHOD_AT_END_OF_EPOCH',
|
||||
},
|
||||
};
|
||||
try {
|
||||
|
||||
@@ -7,7 +7,6 @@ import dev from './i18n/translations/dev.json';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
// Set up i18n instance so that components have the correct default
|
||||
// en translations
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
const webpack = require('webpack');
|
||||
const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
module.exports = defineConfig({
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
@@ -12,36 +6,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
|
||||
const defaultConfig = webpackPreprocessor.defaultOptions;
|
||||
defaultConfig.webpackOptions.context = path.resolve(__dirname, './');
|
||||
defaultConfig.webpackOptions.module.rules[1].test = /(\.tsx?|\.jsx?)$/;
|
||||
defaultConfig.webpackOptions.module.rules[1].exclude = [
|
||||
/node_modules\/(?!(@vegaprotocol\/protos|protobuf-codec))/,
|
||||
/browserslist/,
|
||||
];
|
||||
defaultConfig.webpackOptions.module.rules.push({
|
||||
test: /(\.css|\.s[ac]ss)$/i,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
});
|
||||
defaultConfig.webpackOptions.plugins =
|
||||
defaultConfig.webpackOptions.plugins || [];
|
||||
defaultConfig.webpackOptions.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': JSON.stringify(process.env),
|
||||
})
|
||||
);
|
||||
defaultConfig.webpackOptions.cache = {
|
||||
type: 'filesystem',
|
||||
allowCollectingMemory: true,
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
};
|
||||
defaultConfig.typescript = require.resolve('typescript');
|
||||
|
||||
on('file:preprocessor', webpackPreprocessor(defaultConfig));
|
||||
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:4200',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
describe('charts', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('Depth').click();
|
||||
});
|
||||
|
||||
it('can see market depth chart', () => {
|
||||
// 6006-DEPC-001
|
||||
cy.getByTestId('tab-depth').should('be.visible');
|
||||
cy.get('.depth-chart-module_canvas__260De').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -154,7 +154,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
.find(dropdownContentItem)
|
||||
.eq(2)
|
||||
.should('have.text', 'View asset');
|
||||
cy.getByTestId('market-actions-content').should('be.visible');
|
||||
cy.getByTestId('market-actions-content').click();
|
||||
});
|
||||
|
||||
it('able to open and sort full market list - market page', () => {
|
||||
@@ -197,8 +197,8 @@ describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
|
||||
@@ -182,7 +182,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0');
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0.7');
|
||||
validateMarketDataRow(1, 'Time Window', '3,600');
|
||||
validateMarketDataRow(2, 'Scaling Factor', '10');
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
describe('must submit order', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-039
|
||||
@@ -22,7 +22,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
// 0003-WTXN-012
|
||||
// 0003-WTXN-003
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -37,7 +37,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
|
||||
it('successfully places market sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -53,7 +53,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
it('successfully places limit buy order', () => {
|
||||
// 7002-SORD-017
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -69,7 +69,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -86,7 +86,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -97,10 +97,12 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
expiresAt: order.expiresAt as string,
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
amendGeneralAccountBalance,
|
||||
amendMarginAccountBalance,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
describe(
|
||||
'account validation',
|
||||
@@ -94,7 +94,7 @@ describe(
|
||||
it('must see a prompt to check connected vega wallet to approve transaction', () => {
|
||||
// 0003-WTXN-002
|
||||
cy.mockVegaWalletTransaction(1000);
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -124,7 +124,7 @@ describe(
|
||||
});
|
||||
});
|
||||
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -160,7 +160,7 @@ describe(
|
||||
});
|
||||
});
|
||||
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
const displayTomorrow = () => {
|
||||
const tomorrow = new Date();
|
||||
@@ -31,7 +31,7 @@ describe(
|
||||
|
||||
it('successfully places limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -47,7 +47,7 @@ describe(
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -63,7 +63,7 @@ describe(
|
||||
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -79,7 +79,8 @@ describe(
|
||||
price: '100000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
expiresAt: order.expiresAt as string,
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+6
-5
@@ -1,7 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
const displayTomorrow = () => {
|
||||
const tomorrow = new Date();
|
||||
@@ -31,7 +31,7 @@ describe(
|
||||
|
||||
it('successfully places limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -47,7 +47,7 @@ describe(
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -63,7 +63,7 @@ describe(
|
||||
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -77,7 +77,8 @@ describe(
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
expiresAt: order.expiresAt as string,
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
const displayTomorrow = () => {
|
||||
const tomorrow = new Date();
|
||||
@@ -31,7 +31,7 @@ describe(
|
||||
|
||||
it('successfully places limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
@@ -47,7 +47,7 @@ describe(
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -63,7 +63,7 @@ describe(
|
||||
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderObj = {
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
@@ -77,7 +77,8 @@ describe(
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
expiresAt: order.expiresAt as string,
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,406 +0,0 @@
|
||||
import {
|
||||
getSubscriptionMocks,
|
||||
updateOrder,
|
||||
} from '../support/order-update-subscription';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const orderSymbol = 'market.tradableInstrument.instrument.code';
|
||||
const orderSize = 'size';
|
||||
const orderType = 'type';
|
||||
const orderStatus = 'status';
|
||||
const orderRemaining = 'remaining';
|
||||
const orderPrice = 'price';
|
||||
const orderTimeInForce = 'timeInForce';
|
||||
const orderCreatedAt = 'createdAt';
|
||||
const cancelOrderBtn = 'cancel';
|
||||
const cancelAllOrdersBtn = 'cancelAll';
|
||||
const editOrderBtn = 'edit';
|
||||
|
||||
describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('All').click();
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('renders orders', () => {
|
||||
cy.getByTestId('tab-orders').should('be.visible');
|
||||
cy.getByTestId(cancelAllOrdersBtn).should('be.visible');
|
||||
cy.getByTestId(cancelOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId(editOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get(`[role='rowgroup']`)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderSymbol}']`).each(($symbol) => {
|
||||
cy.wrap($symbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderSize}']`).each(($size) => {
|
||||
cy.wrap($size).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderType}']`).each(($type) => {
|
||||
cy.wrap($type).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderStatus}']`).each(($status) => {
|
||||
cy.wrap($status).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderRemaining}']`).each(($remaining) => {
|
||||
cy.wrap($remaining).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderPrice}']`).each(($price) => {
|
||||
cy.wrap($price).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderTimeInForce}']`).each(($timeInForce) => {
|
||||
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderCreatedAt}']`).each(($dateTime) => {
|
||||
cy.wrap($dateTime).invoke('text').should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('partially filled orders should not show close/edit buttons', () => {
|
||||
const partiallyFilledId =
|
||||
'94aead3ca92dc932efcb503631b03a410e2a5d4606cae6083e2406dc38e52f78';
|
||||
|
||||
cy.getByTestId('tab-orders').should('be.visible');
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get('[col-id="status"]').realHover();
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Partially Filled').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderStatus}']`).should(
|
||||
'have.text',
|
||||
'Partially Filled'
|
||||
);
|
||||
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7/10');
|
||||
cy.getByTestId(cancelOrderBtn).should('not.exist');
|
||||
cy.getByTestId(editOrderBtn).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('orders are sorted by most recent order', () => {
|
||||
// 7003-MORD-002
|
||||
const expectedOrderList = [
|
||||
'BTCUSD.MF21',
|
||||
'SOLUSD',
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'BTCUSD.MF21',
|
||||
];
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get('[col-id="status"]').realHover();
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Reset').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get(`.ag-center-cols-container [col-id='${orderSymbol}']`)
|
||||
.should('have.length.at.least', expectedOrderList.length)
|
||||
.then(($symbols) => {
|
||||
const symbolNames: string[] = [];
|
||||
cy.wrap($symbols)
|
||||
.each(($symbol) => {
|
||||
cy.wrap($symbol)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
symbolNames.push(text);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(symbolNames).to.include.ordered.members(expectedOrderList);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
let orderId = '0';
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
.find('.ag-header-cell-menu-button')
|
||||
.click();
|
||||
cy.get('.ag-filter-apply-panel-button').click();
|
||||
});
|
||||
orderId = (parseInt(orderId, 10) + 1).toString();
|
||||
});
|
||||
// 7002-SORD-053
|
||||
// 7002-SORD-040
|
||||
// 7003-MORD-001
|
||||
|
||||
it('must see an active order', () => {
|
||||
// 7002-SORD-041
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Active');
|
||||
});
|
||||
|
||||
it('must see an expired order', () => {
|
||||
// 7002-SORD-042
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_EXPIRED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Expired');
|
||||
});
|
||||
|
||||
it('must see a cancelled order', () => {
|
||||
// 7002-SORD-043
|
||||
// NOT COVERED: see the txn that cancelled it and a link to the block explorer, if cancelled by a user transaction.
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_CANCELLED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Cancelled');
|
||||
});
|
||||
|
||||
it('must see a stopped order', () => {
|
||||
// 7002-SORD-044
|
||||
// NOT COVERED: see an explanation of why stopped
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_STOPPED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Stopped');
|
||||
});
|
||||
|
||||
it('must see a partially filled order', () => {
|
||||
// 7002-SORD-045
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
size: '5',
|
||||
remaining: '1',
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Partially Filled'
|
||||
);
|
||||
cy.getByTestId(`order-status-${orderId}`)
|
||||
.parentsUntil(`.ag-row`)
|
||||
.siblings(`[col-id=${orderRemaining}]`)
|
||||
.should('have.text', '4/5');
|
||||
});
|
||||
|
||||
it('must see a filled order', () => {
|
||||
// 7002-SORD-046
|
||||
// NOT COVERED: Must be able to see/link to all trades that were created from this order
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_FILLED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
|
||||
});
|
||||
|
||||
it('must see a rejected order', () => {
|
||||
// 7002-SORD-047
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_REJECTED,
|
||||
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_INTERNAL_ERROR,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Rejected: Internal error'
|
||||
);
|
||||
});
|
||||
|
||||
it('must see a parked order', () => {
|
||||
// 7002-SORD-048
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Parked: Internal error'
|
||||
);
|
||||
});
|
||||
|
||||
it('must see the size of the order and direction/side -', () => {
|
||||
// 7003-MORD-003
|
||||
// 7003-MORD-004
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
size: '15',
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="size"]')
|
||||
.should('have.text', '-15');
|
||||
});
|
||||
|
||||
it('must see the size of the order and direction/side +', () => {
|
||||
// 7003-MORD-003
|
||||
// 7003-MORD-004
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
size: '5',
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="size"]')
|
||||
.should('have.text', '+5');
|
||||
});
|
||||
|
||||
it('for limit typy must see the Limit price that was set on the order', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '200.00');
|
||||
});
|
||||
|
||||
it('must see a pegged order - ask', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
||||
offset: '250000',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Ask - 2.50 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - bid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
offset: '100',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Bid + 0.001 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - mid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
offset: '0.5',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Mid + 0.00001 Peg limit');
|
||||
});
|
||||
|
||||
it('for market typy must not see a price for active or parked orders', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
peggedOrder: null,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '-');
|
||||
});
|
||||
|
||||
it('must see the time in force applied to the order', () => {
|
||||
// 7003-MORD-006
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[col-id='${orderTimeInForce}']`)
|
||||
.should('have.text', "Good 'til Cancelled (GTC)");
|
||||
});
|
||||
|
||||
it('for Active order when is part of a liquidity or peg shape, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('for Active order when is part of a liquidity, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('for Active order when is part of a peg shape, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,407 @@ import {
|
||||
testOrderCancellation,
|
||||
testOrderAmendment,
|
||||
} from '../support/order-validation';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
const orderSymbol = 'market.tradableInstrument.instrument.code';
|
||||
const orderSize = 'size';
|
||||
const orderType = 'type';
|
||||
const orderStatus = 'status';
|
||||
const orderRemaining = 'remaining';
|
||||
const orderPrice = 'price';
|
||||
const orderTimeInForce = 'timeInForce';
|
||||
const orderCreatedAt = 'createdAt';
|
||||
const cancelOrderBtn = 'cancel';
|
||||
const cancelAllOrdersBtn = 'cancelAll';
|
||||
const editOrderBtn = 'edit';
|
||||
|
||||
describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('All').click();
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('renders orders', () => {
|
||||
cy.getByTestId('tab-orders').should('be.visible');
|
||||
cy.getByTestId(cancelAllOrdersBtn).should('be.visible');
|
||||
cy.getByTestId(cancelOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId(editOrderBtn).should('have.length.at.least', 1);
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get(`[role='rowgroup']`)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderSymbol}']`).each(($symbol) => {
|
||||
cy.wrap($symbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderSize}']`).each(($size) => {
|
||||
cy.wrap($size).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderType}']`).each(($type) => {
|
||||
cy.wrap($type).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderStatus}']`).each(($status) => {
|
||||
cy.wrap($status).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderRemaining}']`).each(($remaining) => {
|
||||
cy.wrap($remaining).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderPrice}']`).each(($price) => {
|
||||
cy.wrap($price).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderTimeInForce}']`).each(($timeInForce) => {
|
||||
cy.wrap($timeInForce).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id='${orderCreatedAt}']`).each(($dateTime) => {
|
||||
cy.wrap($dateTime).invoke('text').should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('partially filled orders should not show close/edit buttons', () => {
|
||||
const partiallyFilledId =
|
||||
'94aead3ca92dc932efcb503631b03a410e2a5d4606cae6083e2406dc38e52f78';
|
||||
|
||||
cy.getByTestId('tab-orders').should('be.visible');
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get('[col-id="status"]').realHover();
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Partially Filled').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get(`[col-id='${orderStatus}']`).should(
|
||||
'have.text',
|
||||
'Partially Filled'
|
||||
);
|
||||
cy.get(`[col-id='${orderRemaining}']`).should('have.text', '7/10');
|
||||
cy.getByTestId(cancelOrderBtn).should('not.exist');
|
||||
cy.getByTestId(editOrderBtn).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('orders are sorted by most recent order', () => {
|
||||
// 7003-MORD-002
|
||||
const expectedOrderList = [
|
||||
'BTCUSD.MF21',
|
||||
'SOLUSD',
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'BTCUSD.MF21',
|
||||
];
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get('[col-id="status"]').realHover();
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Reset').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get(`.ag-center-cols-container [col-id='${orderSymbol}']`)
|
||||
.should('have.length.at.least', expectedOrderList.length)
|
||||
.then(($symbols) => {
|
||||
const symbolNames: string[] = [];
|
||||
cy.wrap($symbols)
|
||||
.each(($symbol) => {
|
||||
cy.wrap($symbol)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
symbolNames.push(text);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
expect(symbolNames).to.include.ordered.members(expectedOrderList);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
let orderId = '0';
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
.find('.ag-header-cell-menu-button')
|
||||
.click();
|
||||
cy.get('.ag-filter-apply-panel-button').click();
|
||||
});
|
||||
orderId = (parseInt(orderId, 10) + 1).toString();
|
||||
});
|
||||
// 7002-SORD-053
|
||||
// 7002-SORD-040
|
||||
// 7003-MORD-001
|
||||
|
||||
it('must see an active order', () => {
|
||||
// 7002-SORD-041
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Active');
|
||||
});
|
||||
|
||||
it('must see an expired order', () => {
|
||||
// 7002-SORD-042
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_EXPIRED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Expired');
|
||||
});
|
||||
|
||||
it('must see a cancelled order', () => {
|
||||
// 7002-SORD-043
|
||||
// NOT COVERED: see the txn that cancelled it and a link to the block explorer, if cancelled by a user transaction.
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_CANCELLED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Cancelled');
|
||||
});
|
||||
|
||||
it('must see a stopped order', () => {
|
||||
// 7002-SORD-044
|
||||
// NOT COVERED: see an explanation of why stopped
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_STOPPED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Stopped');
|
||||
});
|
||||
|
||||
it('must see a partially filled order', () => {
|
||||
// 7002-SORD-045
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
size: '5',
|
||||
remaining: '1',
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Partially Filled'
|
||||
);
|
||||
cy.getByTestId(`order-status-${orderId}`)
|
||||
.parentsUntil(`.ag-row`)
|
||||
.siblings(`[col-id=${orderRemaining}]`)
|
||||
.should('have.text', '4/5');
|
||||
});
|
||||
|
||||
it('must see a filled order', () => {
|
||||
// 7002-SORD-046
|
||||
// NOT COVERED: Must be able to see/link to all trades that were created from this order
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_FILLED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
|
||||
});
|
||||
|
||||
it('must see a rejected order', () => {
|
||||
// 7002-SORD-047
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_REJECTED,
|
||||
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_INTERNAL_ERROR,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Rejected: Internal error'
|
||||
);
|
||||
});
|
||||
|
||||
it('must see a parked order', () => {
|
||||
// 7002-SORD-048
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
});
|
||||
cy.getByTestId(`order-status-${orderId}`).should(
|
||||
'have.text',
|
||||
'Parked: Internal error'
|
||||
);
|
||||
});
|
||||
|
||||
it('must see the size of the order and direction/side -', () => {
|
||||
// 7003-MORD-003
|
||||
// 7003-MORD-004
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
size: '15',
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="size"]')
|
||||
.should('have.text', '-15');
|
||||
});
|
||||
|
||||
it('must see the size of the order and direction/side +', () => {
|
||||
// 7003-MORD-003
|
||||
// 7003-MORD-004
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
size: '5',
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="size"]')
|
||||
.should('have.text', '+5');
|
||||
});
|
||||
|
||||
it('for limit typy must see the Limit price that was set on the order', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '200.00');
|
||||
});
|
||||
|
||||
it('must see a pegged order - ask', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
||||
offset: '250000',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Ask - 2.50 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - bid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
offset: '100',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Bid + 0.001 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - mid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
offset: '0.5',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Mid + 0.00001 Peg limit');
|
||||
});
|
||||
|
||||
it('for market typy must not see a price for active or parked orders', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
peggedOrder: null,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '-');
|
||||
});
|
||||
|
||||
it('must see the time in force applied to the order', () => {
|
||||
// 7003-MORD-006
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[col-id='${orderTimeInForce}']`)
|
||||
.should('have.text', "Good 'til Cancelled (GTC)");
|
||||
});
|
||||
|
||||
it('for Active order when is part of a liquidity or peg shape, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('for Active order when is part of a liquidity, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
|
||||
it('for Active order when is part of a peg shape, must not see an option to amend the individual order ', () => {
|
||||
// 7003-MORD-008
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="cancel"]`)
|
||||
.should('not.exist');
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find(`[data-testid="edit"]`)
|
||||
.should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('amend and cancel order', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -53,8 +453,8 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
|
||||
orderId: orderId,
|
||||
marketId: 'market-0',
|
||||
price: '10000000',
|
||||
timeInForce: vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTC,
|
||||
sizeDelta: BigInt(0),
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
sizeDelta: 0,
|
||||
};
|
||||
testOrderAmendment(order);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
|
||||
const orderSizeField = 'order-size';
|
||||
const orderPriceField = 'order-price';
|
||||
const orderTIFDropDown = 'order-tif';
|
||||
const placeOrderBtn = 'place-order';
|
||||
|
||||
export const createOrder = (order: OrderObj): void => {
|
||||
export const createOrder = (order: OrderSubmission): void => {
|
||||
cy.log('Placing order', order);
|
||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ import type {
|
||||
OrderAmendmentBody,
|
||||
OrderCancellation,
|
||||
OrderCancellationBody,
|
||||
OrderSubmission,
|
||||
OrderSubmissionBody,
|
||||
Transaction,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { normalizeOrderSubmission } from '@vegaprotocol/deal-ticket';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
export const testOrderSubmission = (
|
||||
order: OrderObj,
|
||||
expected?: Partial<OrderObj>
|
||||
order: OrderSubmission,
|
||||
expected?: Partial<OrderSubmission>
|
||||
) => {
|
||||
const expectedOrder = {
|
||||
...order,
|
||||
@@ -19,7 +18,7 @@ export const testOrderSubmission = (
|
||||
};
|
||||
|
||||
const transaction: OrderSubmissionBody = {
|
||||
orderSubmission: normalizeOrderSubmission(expectedOrder, 0, 0),
|
||||
orderSubmission: expectedOrder,
|
||||
};
|
||||
vegaWalletTransaction(transaction);
|
||||
verifyToast();
|
||||
@@ -64,7 +63,7 @@ const vegaWalletTransaction = (transaction: Transaction) => {
|
||||
expect(req.body.params).to.deep.equal({
|
||||
publicKey: Cypress.env('VEGA_PUBLIC_KEY'),
|
||||
sendingMode: 'TYPE_SYNC',
|
||||
transaction: JSON.parse(JSON.stringify(transaction)), // this is because BigInt needs to be serialized
|
||||
transaction,
|
||||
});
|
||||
expect(req.headers.authorization).to.equal(
|
||||
`VWT ${Cypress.env('VEGA_WALLET_API_TOKEN')}`
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@@ -184,7 +184,6 @@ const MarketList = ({
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
@@ -204,6 +203,29 @@ const MarketList = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}
|
||||
|
||||
const ListItem = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}: {
|
||||
index: number;
|
||||
style: CSSProperties;
|
||||
data: ListItemData;
|
||||
}) => (
|
||||
<MarketSelectorItem
|
||||
market={data.data[index]}
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
const List = ({
|
||||
data,
|
||||
loading,
|
||||
@@ -212,28 +234,20 @@ const List = ({
|
||||
onSelect,
|
||||
noItems,
|
||||
currentMarketId,
|
||||
}: {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
}: ListItemData & {
|
||||
loading: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
noItems: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}) => {
|
||||
const row = ({ index, style }: { index: number; style: CSSProperties }) => {
|
||||
const market = data[index];
|
||||
|
||||
return (
|
||||
<MarketSelectorItem
|
||||
market={market}
|
||||
currentMarketId={currentMarketId}
|
||||
style={style}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const itemKey = useCallback(
|
||||
(index: number, data: ListItemData) => data.data[index].id,
|
||||
[]
|
||||
);
|
||||
const itemData = useMemo(
|
||||
() => ({ data, onSelect, currentMarketId }),
|
||||
[data, onSelect, currentMarketId]
|
||||
);
|
||||
if (!data || loading) {
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
@@ -259,11 +273,13 @@ const List = ({
|
||||
<FixedSizeList
|
||||
className="virtualized-list"
|
||||
itemCount={data.length}
|
||||
itemData={itemData}
|
||||
itemSize={130}
|
||||
itemKey={itemKey}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
{row}
|
||||
{ListItem}
|
||||
</FixedSizeList>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,19 @@ import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
} from '../../components/resizable-grid';
|
||||
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
if (!ready || ready.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="bg-vega-blue-450 text-white text-[10px] rounded p-[3px] pb-[2px] leading-none">
|
||||
{ready.length}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const Portfolio = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
@@ -103,7 +116,11 @@ export const Portfolio = () => {
|
||||
<DepositsContainer />
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="withdrawals" name={t('Withdrawals')}>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
name={t('Withdrawals')}
|
||||
indicator={<WithdrawalsIndicator />}
|
||||
>
|
||||
<WithdrawalsContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
WithdrawalsTable,
|
||||
useIncompleteWithdrawals,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -17,6 +18,7 @@ export const WithdrawalsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
|
||||
return (
|
||||
<VegaWalletContainer>
|
||||
@@ -25,6 +27,8 @@ export const WithdrawalsContainer = () => {
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
ready={ready}
|
||||
delayed={delayed}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { DefaultWeb3ProviderContextShape } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
createConnectors,
|
||||
Web3Provider as Web3ProviderInternal,
|
||||
useWeb3ConnectStore,
|
||||
createDefaultProvider,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
@@ -17,10 +20,13 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const connectors = useWeb3ConnectStore((store) => store.connectors);
|
||||
const initializeConnectors = useWeb3ConnectStore((store) => store.initialize);
|
||||
const [defaultProvider, setDefaultProvider] = useState<
|
||||
DefaultWeb3ProviderContextShape['provider'] | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (config?.chain_id) {
|
||||
return initializeConnectors(
|
||||
initializeConnectors(
|
||||
createConnectors(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id),
|
||||
@@ -29,6 +35,11 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
),
|
||||
Number(config.chain_id)
|
||||
);
|
||||
const defaultProvider = createDefaultProvider(
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
Number(config?.chain_id)
|
||||
);
|
||||
setDefaultProvider(defaultProvider);
|
||||
}
|
||||
}, [
|
||||
config?.chain_id,
|
||||
@@ -49,7 +60,10 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
}}
|
||||
noDataMessage={t('Could not fetch Ethereum configuration')}
|
||||
>
|
||||
<Web3ProviderInternal connectors={connectors}>
|
||||
<Web3ProviderInternal
|
||||
connectors={connectors}
|
||||
defaultProvider={defaultProvider}
|
||||
>
|
||||
<>{children}</>
|
||||
</Web3ProviderInternal>
|
||||
</AsyncRenderer>
|
||||
|
||||
@@ -3,12 +3,17 @@ import { useUpdateNetworkParametersToasts } from '@vegaprotocol/proposals';
|
||||
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
|
||||
import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
|
||||
import { Routes } from './client-router';
|
||||
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
|
||||
|
||||
export const ToastsManager = () => {
|
||||
useUpdateNetworkParametersToasts();
|
||||
useVegaTransactionToasts();
|
||||
useEthereumTransactionToasts();
|
||||
useEthereumWithdrawApprovalsToasts();
|
||||
useReadyToWithdrawalToasts({
|
||||
withdrawalsLink: `${Routes.PORTFOLIO}`,
|
||||
});
|
||||
|
||||
const toasts = useToasts((store) => store.toasts);
|
||||
return <ToastsContainer order="desc" toasts={toasts} />;
|
||||
|
||||
@@ -2,7 +2,6 @@ import '@testing-library/jest-dom';
|
||||
import 'jest-canvas-mock';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { defaultFallbackInView } from 'react-intersection-observer';
|
||||
import '../../setup-test';
|
||||
|
||||
defaultFallbackInView(true);
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
+1
-9
@@ -1,11 +1,3 @@
|
||||
{
|
||||
"babelrcRoots": ["*"],
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": [
|
||||
["@babel/preset-env", { "targets": { "node": "current" } }],
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
}
|
||||
}
|
||||
"babelrcRoots": ["*"]
|
||||
}
|
||||
|
||||
+1
-7
@@ -1,9 +1,3 @@
|
||||
const nxPreset = require('@nrwl/jest/preset').default;
|
||||
|
||||
module.exports = {
|
||||
...nxPreset,
|
||||
transformIgnorePatterns: [
|
||||
...(nxPreset?.transformIgnorePatterns || []),
|
||||
'node_modules/(?!(@vegaprotocol/protos|protobuf-codec))',
|
||||
],
|
||||
};
|
||||
module.exports = { ...nxPreset };
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/positions',
|
||||
|
||||
@@ -23,8 +23,8 @@ const margins: MarginFieldsFragment = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockGetMargins = jest.fn(() => margins);
|
||||
const mockGetBalance = jest.fn(() => '0');
|
||||
const getMargins = jest.fn(() => margins);
|
||||
const getBalance = jest.fn(() => '0');
|
||||
|
||||
jest.mock('./margin-data-provider', () => ({}));
|
||||
|
||||
@@ -49,7 +49,7 @@ jest.mock('@vegaprotocol/wallet', () => ({
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
useDataProvider: () => {
|
||||
return {
|
||||
data: mockGetMargins(),
|
||||
data: getMargins(),
|
||||
};
|
||||
},
|
||||
}));
|
||||
@@ -57,7 +57,7 @@ jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
jest.mock('./use-account-balance', () => ({
|
||||
useAccountBalance: () => {
|
||||
return {
|
||||
accountBalance: mockGetBalance(),
|
||||
accountBalance: getBalance(),
|
||||
};
|
||||
},
|
||||
}));
|
||||
@@ -88,7 +88,7 @@ describe('MarginHealthChart', () => {
|
||||
});
|
||||
|
||||
it('should use correct scale', async () => {
|
||||
mockGetBalance.mockReturnValueOnce('1300');
|
||||
getBalance.mockReturnValueOnce('1300');
|
||||
await act(async () => {
|
||||
render(<MarginHealthChart marketId="marketId" assetId="assetId" />);
|
||||
});
|
||||
|
||||
@@ -7,13 +7,10 @@ import {
|
||||
} from '@testing-library/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||
const amount = '100';
|
||||
const pubKey =
|
||||
@@ -129,14 +126,12 @@ describe('TransferForm', () => {
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
oneOff: {},
|
||||
kind: null,
|
||||
reference: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -154,23 +149,21 @@ describe('TransferForm', () => {
|
||||
]);
|
||||
|
||||
submit();
|
||||
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
|
||||
await act(() => {
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
@@ -182,7 +175,7 @@ describe('TransferForm', () => {
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
await act(() => {
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
@@ -204,20 +197,18 @@ describe('TransferForm', () => {
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
|
||||
amount
|
||||
);
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(0);
|
||||
|
||||
submit();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(expectedAmount, asset.decimals),
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
oneOff: {},
|
||||
kind: null,
|
||||
reference: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -235,21 +226,20 @@ describe('TransferForm', () => {
|
||||
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
await act(() => {
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
// Select a pubkey
|
||||
fireEvent.change(screen.getByLabelText('Vega key'), {
|
||||
target: { value: props.pubKeys[1] },
|
||||
});
|
||||
|
||||
// Select asset
|
||||
fireEvent.change(
|
||||
// Bypass RichSelect and target hidden native select
|
||||
// eslint-disable-next-line
|
||||
document.querySelector('select[name="asset"]')!,
|
||||
{ target: { value: asset.id } }
|
||||
);
|
||||
|
||||
// assert rich select as updated
|
||||
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
|
||||
asset.name
|
||||
@@ -261,7 +251,7 @@ describe('TransferForm', () => {
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
await act(() => {
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
// set valid amount
|
||||
fireEvent.change(amountInput, {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
vegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
removeDecimal,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -20,12 +19,11 @@ import {
|
||||
Checkbox,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { normalizeTransfer } from '@vegaprotocol/wallet';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import type { Exact } from 'type-fest';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
interface FormFields {
|
||||
toAddress: string;
|
||||
@@ -48,29 +46,6 @@ interface TransferFormProps {
|
||||
submitTransfer: (transfer: Transfer) => void;
|
||||
}
|
||||
|
||||
export const normalizeTransfer = <T extends Exact<Transfer, T>>(
|
||||
address: string,
|
||||
amount: string,
|
||||
asset: {
|
||||
id: string;
|
||||
decimals: number;
|
||||
}
|
||||
): Transfer => {
|
||||
return {
|
||||
fromAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: address,
|
||||
toAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
// oneOff or recurring required otherwise wallet will error
|
||||
// default oneOff is immediate transfer
|
||||
oneOff: {},
|
||||
// to be consistent with proto types
|
||||
kind: null,
|
||||
reference: '',
|
||||
};
|
||||
};
|
||||
|
||||
export const TransferForm = ({
|
||||
pubKey,
|
||||
pubKeys,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { defaultFallbackInView } from 'react-intersection-observer';
|
||||
import '../../../setup-test';
|
||||
|
||||
defaultFallbackInView(true);
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import '../../../setup-test';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import 'jest-canvas-mock';
|
||||
import '../../../setup-test';
|
||||
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { determineId } from '../utils';
|
||||
import { setGraphQLEndpoint } from './request';
|
||||
import { vote } from './vote';
|
||||
@@ -63,7 +63,7 @@ export async function createMarket(cfg: {
|
||||
const proposal = await waitForProposal(proposalId);
|
||||
|
||||
// Vote on new market proposal
|
||||
await vote(proposal.id, vegaProtos.Vote.Value.VALUE_YES, cfg.vegaPubKey);
|
||||
await vote(proposal.id, Schema.VoteValue.VALUE_YES, cfg.vegaPubKey);
|
||||
|
||||
// Wait for the market to be enacted and go into opening auction
|
||||
await waitForEnactment();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Value as ProtoOrderTypeValue } from '@vegaprotocol/protos/dist/vega/Vote/Value';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { createLog } from './logging';
|
||||
import { sendVegaTx } from './wallet-client';
|
||||
|
||||
@@ -6,7 +6,7 @@ const log = createLog('vote');
|
||||
|
||||
export async function vote(
|
||||
proposalId: string,
|
||||
voteValue: ProtoOrderTypeValue,
|
||||
voteValue: Schema.VoteValue,
|
||||
publicKey: string
|
||||
) {
|
||||
log(`voting ${voteValue} on ${proposalId}`);
|
||||
@@ -17,7 +17,7 @@ export async function vote(
|
||||
return voteResult.result;
|
||||
}
|
||||
|
||||
function createVote(proposalId: string, value: ProtoOrderTypeValue) {
|
||||
function createVote(proposalId: string, value: Schema.VoteValue) {
|
||||
return {
|
||||
voteSubmission: {
|
||||
value,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import { PeggedReference } from '@vegaprotocol/types';
|
||||
import type { LiquidityProvisionSubmission } from '@vegaprotocol/wallet';
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
|
||||
@@ -31,28 +31,27 @@ export const addVegaWalletSubmitLiquidityProvision = () => {
|
||||
buys: [
|
||||
{
|
||||
offset: '10',
|
||||
proportion: 1,
|
||||
reference: vegaProtos.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
proportion: '1',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '12',
|
||||
proportion: 2,
|
||||
reference: vegaProtos.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
sells: [
|
||||
{
|
||||
offset: '10',
|
||||
proportion: 2,
|
||||
reference: vegaProtos.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '12',
|
||||
proportion: 2,
|
||||
reference: vegaProtos.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
reference: '',
|
||||
},
|
||||
pubKey: vegaPubKey,
|
||||
propagate: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { TransferBody } from '@vegaprotocol/wallet';
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
|
||||
@@ -39,15 +39,15 @@ export function addVegaWalletTopUpRewardsPool() {
|
||||
|
||||
const transactionBody: TransferBody = {
|
||||
transfer: {
|
||||
fromAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: vegaProtos.AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
asset: assetAddress,
|
||||
amount: '1000000000000000000',
|
||||
recurring: {
|
||||
factor: '1',
|
||||
startEpoch: BigInt(transferStartEpoch),
|
||||
endEpoch: BigInt(transferEndEpoch),
|
||||
startEpoch: transferStartEpoch,
|
||||
endEpoch: transferEndEpoch,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||
import { useMarket, marketDataProvider } from '@vegaprotocol/markets';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
@@ -48,11 +47,7 @@ export const DealTicketContainer = ({
|
||||
<DealTicket
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
submit={(dealTicketOrder: OrderSubmission) =>
|
||||
create({
|
||||
orderSubmission: dealTicketOrder,
|
||||
})
|
||||
}
|
||||
submit={(orderSubmission) => create({ orderSubmission })}
|
||||
onClickCollateral={onClickCollateral}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
|
||||
@@ -3,11 +3,10 @@ import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateMarket, generateMarketData } from '../../test-helpers';
|
||||
import { DealTicket, normalizeOrderSubmission } from './deal-ticket';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
import { useOrderStore } from '@vegaprotocol/orders';
|
||||
|
||||
jest.mock('zustand');
|
||||
@@ -336,57 +335,4 @@ describe('DealTicket', () => {
|
||||
Object.keys(Schema.OrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
describe('normalizeOrderSubmission', () => {
|
||||
it('sets and formats price only for limit orders', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission({ price: '100' } as unknown as OrderObj, 2, 1)
|
||||
.price
|
||||
).toEqual('');
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
price: '100',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
} as unknown as OrderObj,
|
||||
2,
|
||||
1
|
||||
).price
|
||||
).toEqual('10000');
|
||||
});
|
||||
|
||||
it('sets and formats expiresAt only for time in force orders', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
} as OrderObj,
|
||||
2,
|
||||
1
|
||||
).expiresAt
|
||||
).toEqual(BigInt(0));
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
} as OrderObj,
|
||||
2,
|
||||
1
|
||||
).expiresAt
|
||||
).toEqual(BigInt('1640995200000000000'));
|
||||
});
|
||||
|
||||
it('formats size', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
size: '100',
|
||||
} as OrderObj,
|
||||
2,
|
||||
1
|
||||
).size
|
||||
).toEqual(BigInt('1000'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
@@ -11,10 +11,7 @@ import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
SideMap,
|
||||
SideRevertMap,
|
||||
TimeInForceMap,
|
||||
TypeMap,
|
||||
normalizeOrderSubmission,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
@@ -27,21 +24,17 @@ import {
|
||||
Tooltip,
|
||||
TinyScroll,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import {
|
||||
useEstimatePositionQuery,
|
||||
useOpenVolume,
|
||||
} from '@vegaprotocol/positions';
|
||||
import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
toNanoSeconds,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
import { toBigNum, removeDecimal } from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useEstimateFees } from '../../hooks/use-estimate-fees';
|
||||
import { getDerivedPrice } from '../../utils/get-price';
|
||||
import type { OrderInfo } from '@vegaprotocol/types';
|
||||
|
||||
import {
|
||||
validateExpiration,
|
||||
validateMarketState,
|
||||
@@ -57,40 +50,10 @@ import {
|
||||
useMarketAccountBalance,
|
||||
useAccountBalance,
|
||||
} from '@vegaprotocol/accounts';
|
||||
|
||||
import { OrderTimeInForce, OrderType } from '@vegaprotocol/types';
|
||||
import { useOrderForm } from '../../hooks/use-order-form';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
export const normalizeOrderSubmission = (
|
||||
order: OrderObj,
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
): OrderSubmission => ({
|
||||
marketId: order.marketId,
|
||||
type: TypeMap[order.type],
|
||||
side: SideMap[order.side],
|
||||
timeInForce: TimeInForceMap[order.timeInForce],
|
||||
price:
|
||||
order.type === OrderType.TYPE_LIMIT && order.price
|
||||
? removeDecimal(order.price, decimalPlaces)
|
||||
: '',
|
||||
size: BigInt(removeDecimal(order.size, positionDecimalPlaces)),
|
||||
expiresAt: BigInt(
|
||||
order.expiresAt &&
|
||||
order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
isNumeric(new Date(order.expiresAt).getTime())
|
||||
? toNanoSeconds(order.expiresAt)
|
||||
: 0
|
||||
),
|
||||
postOnly: Boolean(order.postOnly),
|
||||
reduceOnly: Boolean(order.reduceOnly),
|
||||
peggedOrder: {
|
||||
offset: '',
|
||||
reference: vegaProtos.PeggedReference.PEGGED_REFERENCE_UNSPECIFIED,
|
||||
},
|
||||
reference: '',
|
||||
});
|
||||
|
||||
export interface DealTicketProps {
|
||||
market: Market;
|
||||
@@ -158,7 +121,7 @@ export const DealTicket = ({
|
||||
if (price && normalizedOrder?.size) {
|
||||
return removeDecimal(
|
||||
toBigNum(
|
||||
String(normalizedOrder.size),
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
).multipliedBy(toBigNum(price, market.decimalPlaces)),
|
||||
asset.decimals
|
||||
@@ -173,7 +136,9 @@ export const DealTicket = ({
|
||||
asset.decimals,
|
||||
]);
|
||||
|
||||
const feeEstimate = useEstimateFees(order && { ...order, price });
|
||||
const feeEstimate = useEstimateFees(
|
||||
normalizedOrder && { ...normalizedOrder, price }
|
||||
);
|
||||
const { data: activeOrders } = useDataProvider({
|
||||
dataProvider: activeOrdersProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
@@ -190,10 +155,10 @@ export const DealTicket = ({
|
||||
: [];
|
||||
if (normalizedOrder) {
|
||||
orders.push({
|
||||
isMarketOrder: normalizedOrder.type === TypeMap[OrderType.TYPE_MARKET],
|
||||
isMarketOrder: normalizedOrder.type === OrderType.TYPE_MARKET,
|
||||
price: normalizedOrder.price ?? '0',
|
||||
remaining: String(normalizedOrder.size),
|
||||
side: SideRevertMap[normalizedOrder.side],
|
||||
remaining: normalizedOrder.size,
|
||||
side: normalizedOrder.side,
|
||||
});
|
||||
}
|
||||
const { data: positionEstimate } = useEstimatePositionQuery({
|
||||
@@ -281,7 +246,7 @@ export const DealTicket = ({
|
||||
}, [order]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(order: OrderObj) => {
|
||||
(order: OrderSubmission) => {
|
||||
const now = new Date().getTime();
|
||||
if (lastSubmitTime.current && now - lastSubmitTime.current < 1000) {
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './__generated__/EstimateOrder';
|
||||
export * from './use-estimate-fees';
|
||||
export * from './use-order-form';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
|
||||
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
|
||||
export const useEstimateFees = (order?: OrderObj) => {
|
||||
export const useEstimateFees = (
|
||||
order?: OrderSubmissionBody['orderSubmission']
|
||||
) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const { data } = useEstimateFeesQuery({
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { OrderObj } from '@vegaprotocol/orders';
|
||||
import { getDefaultOrder, useOrder } from '@vegaprotocol/orders';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import type { Exact } from 'type-fest';
|
||||
|
||||
export type OrderFormFields = OrderObj & {
|
||||
summary: string;
|
||||
@@ -49,7 +51,9 @@ export const useOrderForm = (marketId: string) => {
|
||||
}
|
||||
}, [order, isSubmitted, getValues, setValue]);
|
||||
|
||||
const handleSubmitWrapper = (cb: (o: OrderObj) => void) => {
|
||||
const handleSubmitWrapper = (
|
||||
cb: <T>(o: Exact<OrderSubmission, T>) => void
|
||||
) => {
|
||||
return handleSubmit(() => {
|
||||
// remove the persist key from the order in the store, the wallet will reject
|
||||
// an order that contains unrecognized additional keys
|
||||
|
||||
@@ -2,7 +2,6 @@ import '@testing-library/jest-dom';
|
||||
import 'jest-canvas-mock';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { defaultFallbackInView } from 'react-intersection-observer';
|
||||
import '../../../setup-test';
|
||||
|
||||
defaultFallbackInView(true);
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { toDecimal } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { OrderObj } from '@vegaprotocol/orders';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
|
||||
export const getDefaultOrder = (market: {
|
||||
id: string;
|
||||
positionDecimalPlaces: number;
|
||||
}): Partial<OrderObj> => ({
|
||||
}): OrderSubmissionBody['orderSubmission'] => ({
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { isMarketInAuction } from './is-market-in-auction';
|
||||
import type { MarketData } from '@vegaprotocol/markets';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import type { Type as OrderType } from '@vegaprotocol/protos/dist/vega/Order';
|
||||
|
||||
/**
|
||||
* Get the market price based on market mode (auction or not auction)
|
||||
*/
|
||||
@@ -30,7 +30,7 @@ export const getMarketPrice = (marketData: MarketData) => {
|
||||
*/
|
||||
export const getDerivedPrice = (
|
||||
order: {
|
||||
type: OrderType.Type;
|
||||
type: Schema.OrderType;
|
||||
price?: string | undefined;
|
||||
},
|
||||
marketData: MarketData
|
||||
@@ -41,7 +41,7 @@ export const getDerivedPrice = (
|
||||
|
||||
// Use the market price if order is a market order
|
||||
let price;
|
||||
if (order.type === vegaProtos.Order.Type.TYPE_LIMIT && order.price) {
|
||||
if (order.type === Schema.OrderType.TYPE_LIMIT && order.price) {
|
||||
price = order.price;
|
||||
} else {
|
||||
price = getMarketPrice(marketData);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Liquidity Provisions
|
||||
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
+3
-2
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
|
||||
|
||||
export type LiquidityProvisionsQueryVariables = Types.Exact<{
|
||||
marketId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
|
||||
|
||||
export type LiquidityProvisionsUpdateSubscriptionVariables = Types.Exact<{
|
||||
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
|
||||
@@ -31,6 +31,7 @@ export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: {
|
||||
|
||||
export const LiquidityProvisionFieldsFragmentDoc = gql`
|
||||
fragment LiquidityProvisionFields on LiquidityProvision {
|
||||
id
|
||||
party {
|
||||
id
|
||||
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
|
||||
|
||||
@@ -38,8 +38,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
) => {
|
||||
return produce(data || [], (draft) => {
|
||||
deltas?.forEach((delta) => {
|
||||
const id = delta.id;
|
||||
const index = draft.findIndex((a) => delta.id === id);
|
||||
const index = draft.findIndex((a) => delta.id === a.id);
|
||||
if (index !== -1) {
|
||||
draft[index].commitmentAmount = delta.commitmentAmount;
|
||||
draft[index].fee = delta.fee;
|
||||
@@ -47,6 +46,7 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
|
||||
draft[index].status = delta.status;
|
||||
} else {
|
||||
draft.unshift({
|
||||
id: delta.id,
|
||||
commitmentAmount: delta.commitmentAmount,
|
||||
fee: delta.fee,
|
||||
status: delta.status,
|
||||
|
||||
@@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => `${data.party.id}-${data.status}`}
|
||||
getRowId={({ data }) => data.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
@@ -200,7 +200,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
storeKey="liquidityProvisionTable"
|
||||
{...props}
|
||||
columnDefs={colDefs}
|
||||
></AgGrid>
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import 'jest-canvas-mock';
|
||||
import '../../../setup-test';
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -47,7 +46,10 @@ const Row = ({
|
||||
if (asPercentage) {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
}
|
||||
return `${formatNumber(Number(value))} ${assetSymbol}`;
|
||||
if (assetSymbol) {
|
||||
return `${Number(value).toLocaleString()} ${assetSymbol}`;
|
||||
}
|
||||
return Number(value).toLocaleString();
|
||||
};
|
||||
|
||||
const formattedValue = getFormattedValue(value);
|
||||
|
||||
@@ -94,7 +94,7 @@ export const marketInfoQuery = (
|
||||
},
|
||||
lpPriceRange: '0.02',
|
||||
liquidityMonitoringParameters: {
|
||||
triggeringRatio: '0',
|
||||
triggeringRatio: '0.7',
|
||||
targetStakeParameters: {
|
||||
timeWindow: 3600,
|
||||
scalingFactor: 10,
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
import { marketDataErrorPolicyGuard } from '@vegaprotocol/data-provider';
|
||||
import { makeDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { MarketsDataQuery } from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketsDataQuery,
|
||||
MarketsDataQueryVariables,
|
||||
} from './__generated__/markets-data';
|
||||
import type {
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
} from './__generated__/market-data';
|
||||
import { MarketDataUpdateDocument } from './__generated__/market-data';
|
||||
import { MarketsDataDocument } from './__generated__/markets-data';
|
||||
import type { MarketData } from './market-data-provider';
|
||||
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
|
||||
const getData = (responseData: MarketsDataQuery | null): MarketData[] =>
|
||||
responseData?.marketsConnection?.edges
|
||||
.filter((edge) => edge.node.data)
|
||||
.map((edge) => edge.node.data as MarketData) || null;
|
||||
.map((edge) => edge.node.data as MarketData) || [];
|
||||
|
||||
export const mapMarketDataUpdateToMarketData = (
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
): MarketData => {
|
||||
const { marketId, __typename, ...marketData } = delta;
|
||||
return { ...marketData, market: { id: marketId } };
|
||||
};
|
||||
|
||||
const update = (
|
||||
data: MarketData[] | null,
|
||||
delta: MarketDataUpdateFieldsFragment
|
||||
) => {
|
||||
const updatedData = data ? [...data] : [];
|
||||
const item = mapMarketDataUpdateToMarketData(delta);
|
||||
const index = updatedData.findIndex(
|
||||
(data) => data.market.id === item.market.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
updatedData[index] = { ...updatedData[index], ...item };
|
||||
} else {
|
||||
updatedData.push(item);
|
||||
}
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const getDelta = (
|
||||
subscriptionData: MarketDataUpdateSubscription
|
||||
): MarketDataUpdateFieldsFragment => subscriptionData.marketsData[0];
|
||||
|
||||
export const marketsDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
@@ -19,3 +56,25 @@ export const marketsDataProvider = makeDataProvider<
|
||||
getData,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
});
|
||||
|
||||
type Variables = { marketIds: string[] };
|
||||
|
||||
export const marketsLiveDataProvider = makeDataProvider<
|
||||
MarketsDataQuery,
|
||||
MarketData[],
|
||||
MarketDataUpdateSubscription,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
Variables,
|
||||
MarketDataUpdateSubscriptionVariables,
|
||||
MarketsDataQueryVariables
|
||||
>({
|
||||
query: MarketsDataDocument,
|
||||
subscriptionQuery: MarketDataUpdateDocument,
|
||||
getData,
|
||||
getDelta,
|
||||
update,
|
||||
errorPolicyGuard: marketDataErrorPolicyGuard,
|
||||
getQueryVariables: () => ({}),
|
||||
getSubscriptionVariables: ({ marketIds }: Variables) =>
|
||||
marketIds.map((marketId) => ({ marketId })),
|
||||
});
|
||||
|
||||
@@ -10,10 +10,15 @@ import type {
|
||||
} from './__generated__/markets';
|
||||
import type { MarketsCandlesQueryVariables } from './__generated__/markets-candles';
|
||||
|
||||
import { marketsDataProvider } from './markets-data-provider';
|
||||
import {
|
||||
marketsDataProvider,
|
||||
marketsLiveDataProvider,
|
||||
mapMarketDataUpdateToMarketData,
|
||||
} from './markets-data-provider';
|
||||
import { marketDataProvider } from './market-data-provider';
|
||||
import { marketsCandlesProvider } from './markets-candles-provider';
|
||||
import type { MarketData } from './market-data-provider';
|
||||
import type { MarketDataUpdateFieldsFragment } from './__generated__';
|
||||
import type { MarketCandles } from './markets-candles-provider';
|
||||
import { useMemo } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -160,6 +165,44 @@ export const allMarketsWithDataProvider = makeDerivedDataProvider<
|
||||
addData(parts[0] as Market[], parts[1] as MarketData[])
|
||||
);
|
||||
|
||||
export const allMarketsWithLiveDataProvider = makeDerivedDataProvider<
|
||||
MarketMaybeWithData[],
|
||||
MarketMaybeWithData,
|
||||
{ marketIds: string[] }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
marketsProvider(callback, client, undefined),
|
||||
marketsLiveDataProvider,
|
||||
],
|
||||
(partsData, variables, prevData, parts) => {
|
||||
if (prevData && parts[1].isUpdate) {
|
||||
const data = mapMarketDataUpdateToMarketData(parts[1].delta);
|
||||
const index = prevData.findIndex(
|
||||
(market) => market.id === data.market.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
const updatedData = [...prevData];
|
||||
updatedData[index] = { ...updatedData[index], data };
|
||||
return updatedData;
|
||||
} else {
|
||||
return prevData;
|
||||
}
|
||||
}
|
||||
return addData(partsData[0] as Market[], partsData[1] as MarketData[]);
|
||||
},
|
||||
(data, parts) => {
|
||||
if (!parts[1].isUpdate && parts[1].delta) {
|
||||
return;
|
||||
}
|
||||
return data.find(
|
||||
(market) =>
|
||||
market.id ===
|
||||
(parts[1].delta as MarketDataUpdateFieldsFragment).marketId
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
|
||||
MarketMaybeWithCandles;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
OrderListManager,
|
||||
normalizeOrderAmendment,
|
||||
} from './order-list-manager';
|
||||
import { OrderListManager } from './order-list-manager';
|
||||
import * as useDataProviderHook from '@vegaprotocol/data-provider';
|
||||
import type { OrderFieldsFragment } from '../';
|
||||
import * as orderListMock from '../order-list/order-list';
|
||||
@@ -11,7 +7,6 @@ import { forwardRef } from 'react';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
// @ts-ignore OrderList is read only but we need to override with the forwardRef to
|
||||
// avoid warnings about padding refs
|
||||
@@ -44,57 +39,3 @@ describe('OrderListManager', () => {
|
||||
expect(await screen.findByText('OrderList')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeOrderAmendment', () => {
|
||||
type Order = Parameters<typeof normalizeOrderAmendment>[0];
|
||||
type Market = Parameters<typeof normalizeOrderAmendment>[1];
|
||||
const order: Order = {
|
||||
id: '123',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
size: '100',
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
};
|
||||
const market: Market = {
|
||||
id: '456',
|
||||
decimalPlaces: 1,
|
||||
positionDecimalPlaces: 1,
|
||||
};
|
||||
|
||||
it('sets and formats order id, market id, expires and timeInForce as given', () => {
|
||||
const orderAmendment = normalizeOrderAmendment(order, market, '1', '1');
|
||||
expect(orderAmendment.orderId).toEqual('123');
|
||||
expect(orderAmendment.marketId).toEqual('456');
|
||||
expect(orderAmendment.expiresAt).toEqual(BigInt('1640995200000000000'));
|
||||
expect(orderAmendment.timeInForce).toEqual(
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['1.1', 1, '11'],
|
||||
['1.1', 2, '110'],
|
||||
['0.001', 8, '100000'],
|
||||
])('sets and formats price', (price, decimalPlaces, output) => {
|
||||
const orderAmendment = normalizeOrderAmendment(
|
||||
order,
|
||||
{ ...market, decimalPlaces },
|
||||
price,
|
||||
'1'
|
||||
);
|
||||
expect(orderAmendment.price).toEqual(output);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['9', 1, BigInt(-10)],
|
||||
['90', 2, BigInt(8900)],
|
||||
['0.001', 8, BigInt(99900)],
|
||||
])('sets and formats size delta', (size, positionDecimalPlaces, output) => {
|
||||
const orderAmendment = normalizeOrderAmendment(
|
||||
order,
|
||||
{ ...market, positionDecimalPlaces },
|
||||
'1',
|
||||
size
|
||||
);
|
||||
expect(orderAmendment.sizeDelta).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,21 +3,20 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
|
||||
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import type { Transaction, OrderAmendment } from '@vegaprotocol/wallet';
|
||||
import { TimeInForceMap, useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
normalizeOrderAmendment,
|
||||
useVegaTransactionStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import type { Exact } from 'type-fest';
|
||||
import { isNumeric, removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export enum Filter {
|
||||
'Open',
|
||||
@@ -26,18 +25,15 @@ export enum Filter {
|
||||
}
|
||||
|
||||
const FilterStatusValue = {
|
||||
[Filter.Open]: [
|
||||
Schema.OrderStatus.STATUS_ACTIVE,
|
||||
Schema.OrderStatus.STATUS_PARKED,
|
||||
],
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
[Filter.Closed]: [
|
||||
Schema.OrderStatus.STATUS_CANCELLED,
|
||||
Schema.OrderStatus.STATUS_EXPIRED,
|
||||
Schema.OrderStatus.STATUS_FILLED,
|
||||
Schema.OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
Schema.OrderStatus.STATUS_STOPPED,
|
||||
OrderStatus.STATUS_CANCELLED,
|
||||
OrderStatus.STATUS_EXPIRED,
|
||||
OrderStatus.STATUS_FILLED,
|
||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
OrderStatus.STATUS_STOPPED,
|
||||
],
|
||||
[Filter.Rejected]: [Schema.OrderStatus.STATUS_REJECTED],
|
||||
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
|
||||
};
|
||||
|
||||
export interface OrderListManagerProps {
|
||||
@@ -51,32 +47,6 @@ export interface OrderListManagerProps {
|
||||
storeKey?: string;
|
||||
}
|
||||
|
||||
export const normalizeOrderAmendment = <T extends Exact<OrderAmendment, T>>(
|
||||
order: Pick<Order, 'id' | 'timeInForce' | 'size' | 'expiresAt'>,
|
||||
market: Pick<Schema.Market, 'id' | 'decimalPlaces' | 'positionDecimalPlaces'>,
|
||||
price: string,
|
||||
size: string
|
||||
): OrderAmendment => ({
|
||||
orderId: order.id,
|
||||
marketId: market.id,
|
||||
price: removeDecimal(price, market.decimalPlaces),
|
||||
timeInForce: TimeInForceMap[order.timeInForce],
|
||||
sizeDelta: BigInt(
|
||||
size
|
||||
? new BigNumber(removeDecimal(size, market.positionDecimalPlaces))
|
||||
.minus(order.size)
|
||||
.toNumber()
|
||||
: 0
|
||||
),
|
||||
expiresAt: BigInt(
|
||||
order.expiresAt && isNumeric(new Date(order.expiresAt).getTime())
|
||||
? toNanoSeconds(order.expiresAt) // Wallet expects timestamp in nanoseconds
|
||||
: 0
|
||||
),
|
||||
peggedOffset: '',
|
||||
peggedReference: vegaProtos.PeggedReference.PEGGED_REFERENCE_UNSPECIFIED,
|
||||
});
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end px-[11px] py-2 absolute bottom-0 right-3 rounded">
|
||||
<Button
|
||||
@@ -165,7 +135,7 @@ export const OrderListManager = ({
|
||||
const cancelAll = useCallback(() => {
|
||||
create({
|
||||
orderCancellation: {},
|
||||
} as Transaction);
|
||||
});
|
||||
}, [create]);
|
||||
|
||||
return (
|
||||
@@ -220,12 +190,7 @@ export const OrderListManager = ({
|
||||
side: editOrder.side,
|
||||
marketId: editOrder.market.id,
|
||||
};
|
||||
create(
|
||||
{
|
||||
orderAmendment,
|
||||
},
|
||||
originalOrder
|
||||
);
|
||||
create({ orderAmendment }, originalOrder);
|
||||
setEditOrder(null);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,14 +2,13 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef } from 'react';
|
||||
import { memo, forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
SetFilter,
|
||||
@@ -24,11 +23,13 @@ import type {
|
||||
TypedDataAgGrid,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderActionsDropdown } from '../order-actions-dropdown';
|
||||
import { Filter } from '../order-list-manager';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
|
||||
export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
||||
marketId?: string;
|
||||
@@ -54,49 +55,40 @@ export const OrderListTable = memo<
|
||||
: filter === undefined || filter === Filter.Open
|
||||
? true
|
||||
: false;
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.code"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
field="size"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
cellClassRules={{
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_SELL,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(data.size, data.market.positionDecimalPlaces ?? 0)
|
||||
.multipliedBy(data.side === Schema.Side.SIDE_SELL ? -1 : 1)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value)) {
|
||||
if (!data?.market || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
@@ -107,33 +99,33 @@ export const OrderListTable = memo<
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
value,
|
||||
data.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="type"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
}}
|
||||
cellRenderer="OrderTypeCell"
|
||||
cellRendererParams={{
|
||||
},
|
||||
cellRenderer: 'OrderTypeCell',
|
||||
cellRendererParams: {
|
||||
onClick: onOrderTypeClick,
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="status"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderStatusMapping,
|
||||
readonly: filter !== undefined,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'status'>) => {
|
||||
@@ -145,8 +137,8 @@ export const OrderListTable = memo<
|
||||
}`;
|
||||
}
|
||||
return value ? Schema.OrderStatusMapping[value] : '';
|
||||
}}
|
||||
cellRenderer={({
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
@@ -156,45 +148,51 @@ export const OrderListTable = memo<
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
)}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Filled')}
|
||||
field="remaining"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
valueFormatter={({
|
||||
),
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Filled'),
|
||||
field: 'remaining',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(
|
||||
(BigInt(data.size) - BigInt(data.remaining)).toString(),
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
).toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>): string => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const dps = data.market.positionDecimalPlaces;
|
||||
const size = new BigNumber(data.size);
|
||||
const remaining = new BigNumber(value);
|
||||
const fills = size.minus(remaining);
|
||||
const { positionDecimalPlaces } = data.market;
|
||||
const filled = BigInt(data.size) - BigInt(data.remaining);
|
||||
return `${addDecimalsFormatNumber(
|
||||
fills.toString(),
|
||||
dps
|
||||
)}/${addDecimalsFormatNumber(size.toString(), dps)}`;
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="price"
|
||||
type="rightAligned"
|
||||
cellClass="font-mono text-right"
|
||||
valueFormatter={({
|
||||
filled.toString(),
|
||||
positionDecimalPlaces
|
||||
)}/${addDecimalsFormatNumber(data.size, positionDecimalPlaces)}`;
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
@@ -204,16 +202,16 @@ export const OrderListTable = memo<
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="timeInForce"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'timeInForce'>) => {
|
||||
@@ -235,13 +233,13 @@ export const OrderListTable = memo<
|
||||
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
|
||||
|
||||
return label;
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="createdAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
filter: DateRangeFilter,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
||||
return (
|
||||
@@ -249,12 +247,12 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : value}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'updatedAt'>) => {
|
||||
@@ -266,15 +264,15 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="amend"
|
||||
{...COL_DEFS.actions}
|
||||
minWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
maxWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
cellRenderer={({ data }: { data?: Order }) => {
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
colId: 'amend',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: Order }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
@@ -298,9 +296,37 @@ export const OrderListTable = memo<
|
||||
<OrderActionsDropdown id={data?.id} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
filter,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
props.isReadOnly,
|
||||
showAllActions,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ export type OrderObj = {
|
||||
timeInForce: OrderTimeInForce;
|
||||
price?: string;
|
||||
expiresAt?: string | undefined;
|
||||
persist?: boolean; // key used to determine if order should be kept in localStorage
|
||||
persist: boolean; // key used to determine if order should be kept in localStorage
|
||||
postOnly?: boolean;
|
||||
reduceOnly?: boolean;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -4,4 +4,3 @@ export * from './lib/positions-data-providers';
|
||||
export * from './lib/positions-table';
|
||||
export * from './lib/use-market-margin';
|
||||
export * from './lib/use-open-volume';
|
||||
export * from './lib/use-positions-data';
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
MarketMaybeWithData,
|
||||
MarketDataQueryVariables,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { allMarketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { allMarketsWithLiveDataProvider } from '@vegaprotocol/markets';
|
||||
import type {
|
||||
PositionsQuery,
|
||||
PositionFieldsFragment,
|
||||
@@ -144,8 +144,9 @@ export const update = (
|
||||
data: PositionFieldsFragment[] | null,
|
||||
deltas: PositionsSubscriptionSubscription['positions']
|
||||
) => {
|
||||
return produce(data || [], (draft) => {
|
||||
const updatedData = produce(data || [], (draft) => {
|
||||
deltas.forEach((delta) => {
|
||||
const { marketId, partyId, __typename, ...position } = delta;
|
||||
const index = draft.findIndex(
|
||||
(node) =>
|
||||
node.market.id === delta.marketId && node.party.id === delta.partyId
|
||||
@@ -154,29 +155,25 @@ export const update = (
|
||||
const currNode = draft[index];
|
||||
draft[index] = {
|
||||
...currNode,
|
||||
realisedPNL: delta.realisedPNL,
|
||||
unrealisedPNL: delta.unrealisedPNL,
|
||||
openVolume: delta.openVolume,
|
||||
averageEntryPrice: delta.averageEntryPrice,
|
||||
updatedAt: delta.updatedAt,
|
||||
lossSocializationAmount: delta.lossSocializationAmount,
|
||||
positionStatus: delta.positionStatus,
|
||||
...position,
|
||||
};
|
||||
} else {
|
||||
draft.unshift({
|
||||
...delta,
|
||||
...position,
|
||||
__typename: 'Position',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: delta.marketId,
|
||||
},
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: delta.partyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const getSubscriptionVariables = (
|
||||
@@ -242,41 +239,54 @@ export const rejoinPositionData = (
|
||||
| null => {
|
||||
if (positions && marketsData) {
|
||||
return positions.map((node) => {
|
||||
const market =
|
||||
marketsData?.find((market) => market.id === node.market.id) || null;
|
||||
return {
|
||||
...node,
|
||||
market:
|
||||
marketsData?.find((market) => market.id === node.market.id) || null,
|
||||
market,
|
||||
};
|
||||
});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const positionsMarketsProvider = makeDerivedDataProvider<
|
||||
string[],
|
||||
never,
|
||||
PositionsQueryVariables
|
||||
>([positionsDataProvider], ([positions]) => {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(positions as PositionFieldsFragment[]).map(
|
||||
(position) => position.market.id
|
||||
)
|
||||
)
|
||||
).sort();
|
||||
});
|
||||
|
||||
export const positionsMetricsProvider = makeDerivedDataProvider<
|
||||
Position[],
|
||||
Position[],
|
||||
PositionsQueryVariables
|
||||
PositionsQueryVariables & { marketIds: string[] }
|
||||
>(
|
||||
[
|
||||
positionsDataProvider,
|
||||
(callback, client, variables) =>
|
||||
positionsDataProvider(callback, client, { partyIds: variables.partyIds }),
|
||||
(callback, client, variables) =>
|
||||
accountsDataProvider(callback, client, {
|
||||
partyId: Array.isArray(variables.partyIds)
|
||||
? variables.partyIds[0]
|
||||
: variables.partyIds,
|
||||
}),
|
||||
(callback, client) =>
|
||||
allMarketsWithDataProvider(callback, client, undefined),
|
||||
(callback, client, variables) =>
|
||||
allMarketsWithLiveDataProvider(callback, client, {
|
||||
marketIds: variables.marketIds,
|
||||
}),
|
||||
],
|
||||
([positions, accounts, marketsData], variables) => {
|
||||
([positions, accounts, marketsData]) => {
|
||||
const positionsData = rejoinPositionData(positions, marketsData);
|
||||
if (!variables) {
|
||||
return [];
|
||||
}
|
||||
return sortBy(
|
||||
getMetrics(positionsData, accounts as Account[] | null),
|
||||
'marketName'
|
||||
);
|
||||
const metrics = getMetrics(positionsData, accounts as Account[] | null);
|
||||
return sortBy(metrics, 'marketName');
|
||||
},
|
||||
(data, delta, previousData) =>
|
||||
data.filter((row) => {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useRef } from 'react';
|
||||
import { usePositionsData } from './use-positions-data';
|
||||
import { useCallback } from 'react';
|
||||
import { PositionsTable } from './positions-table';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Transaction } from '@vegaprotocol/wallet';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
positionsMetricsProvider,
|
||||
positionsMarketsProvider,
|
||||
} from './positions-data-providers';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
interface PositionsManagerProps {
|
||||
partyIds: string[];
|
||||
@@ -25,42 +26,43 @@ export const PositionsManager = ({
|
||||
storeKey,
|
||||
}: PositionsManagerProps) => {
|
||||
const { pubKeys, pubKey } = useVegaWallet();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data, error } = usePositionsData(partyIds, gridRef);
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
const onClose = ({
|
||||
marketId,
|
||||
openVolume,
|
||||
}: {
|
||||
marketId: string;
|
||||
openVolume: string;
|
||||
}) =>
|
||||
create({
|
||||
batchMarketInstructions: {
|
||||
cancellations: [
|
||||
{
|
||||
marketId,
|
||||
orderId: '', // omit order id to cancel all active orders
|
||||
},
|
||||
],
|
||||
submissions: [
|
||||
{
|
||||
marketId: marketId,
|
||||
type: vegaProtos.Order.Type.TYPE_MARKET,
|
||||
timeInForce: vegaProtos.Order.TimeInForce.TIME_IN_FORCE_IOC,
|
||||
side: openVolume.startsWith('-')
|
||||
? vegaProtos.Side.SIDE_BUY
|
||||
: vegaProtos.Side.SIDE_SELL,
|
||||
size: BigInt(openVolume.replace('-', '')),
|
||||
reduceOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
} as Transaction);
|
||||
const onClose = useCallback(
|
||||
({ marketId, openVolume }: { marketId: string; openVolume: string }) =>
|
||||
create({
|
||||
batchMarketInstructions: {
|
||||
cancellations: [
|
||||
{
|
||||
marketId,
|
||||
orderId: '', // omit order id to cancel all active orders
|
||||
},
|
||||
],
|
||||
submissions: [
|
||||
{
|
||||
marketId: marketId,
|
||||
type: Schema.OrderType.TYPE_MARKET as const,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC as const,
|
||||
side: openVolume.startsWith('-')
|
||||
? Schema.Side.SIDE_BUY
|
||||
: Schema.Side.SIDE_SELL,
|
||||
size: openVolume.replace('-', ''),
|
||||
reduceOnly: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[create]
|
||||
);
|
||||
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
disabled: noBottomPlaceholder,
|
||||
const { data: marketIds } = useDataProvider({
|
||||
dataProvider: positionsMarketsProvider,
|
||||
variables: { partyIds },
|
||||
});
|
||||
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: positionsMetricsProvider,
|
||||
variables: { partyIds, marketIds: marketIds || [] },
|
||||
skip: !marketIds,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -69,11 +71,9 @@ export const PositionsManager = ({
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys}
|
||||
rowData={error ? [] : data}
|
||||
ref={gridRef}
|
||||
onMarketClick={onMarketClick}
|
||||
onClose={onClose}
|
||||
isReadOnly={isReadOnly}
|
||||
{...bottomPlaceholderProps}
|
||||
storeKey={storeKey}
|
||||
multipleKeys={partyIds.length > 1}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No positions')}
|
||||
|
||||
@@ -36,7 +36,6 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { getRowId } from './use-positions-data';
|
||||
import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionTableActions } from './position-actions-dropdown';
|
||||
@@ -86,6 +85,9 @@ export const AmountCell = ({ valueFormatted }: AmountCellProps) => {
|
||||
|
||||
AmountCell.displayName = 'AmountCell';
|
||||
|
||||
export const getRowId = ({ data }: { data: Position }) =>
|
||||
`${data.partyId}-${data.marketId}`;
|
||||
|
||||
export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
(
|
||||
{
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { usePositionsData } from './use-positions-data';
|
||||
import type { Position } from './positions-data-providers';
|
||||
|
||||
let mockData: Position[] = [
|
||||
{
|
||||
marketName: 'M1',
|
||||
marketId: 'market-0',
|
||||
openVolume: '1',
|
||||
},
|
||||
{
|
||||
marketName: 'M2',
|
||||
marketId: 'market-1',
|
||||
openVolume: '-1985',
|
||||
},
|
||||
{
|
||||
marketName: 'M3',
|
||||
marketId: 'market-2',
|
||||
openVolume: '0',
|
||||
},
|
||||
{
|
||||
marketName: 'M4',
|
||||
marketId: 'market-3',
|
||||
openVolume: '0',
|
||||
},
|
||||
{
|
||||
marketName: 'M5',
|
||||
marketId: 'market-4',
|
||||
openVolume: '3',
|
||||
},
|
||||
] as Position[];
|
||||
|
||||
let mockDataProviderData = {
|
||||
data: mockData,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
totalCount: undefined,
|
||||
};
|
||||
|
||||
let updateMock: jest.Mock;
|
||||
const mockDataProvider = jest.fn((args) => {
|
||||
updateMock = args.update;
|
||||
return mockDataProviderData;
|
||||
});
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||
useDataProvider: jest.fn((args) => mockDataProvider(args)),
|
||||
}));
|
||||
|
||||
describe('usePositionData Hook', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
const mockRefreshInfiniteCache = jest.fn();
|
||||
const mockGetRowNode = jest
|
||||
.fn()
|
||||
.mockImplementation((id: string) =>
|
||||
mockData.find((position) => position.marketId === id)
|
||||
);
|
||||
const partyIds = ['partyId'];
|
||||
const anUpdatedOne = {
|
||||
marketId: 'market-1',
|
||||
openVolume: '1',
|
||||
};
|
||||
const gridRef = {
|
||||
current: {
|
||||
api: {
|
||||
refreshInfiniteCache: mockRefreshInfiniteCache,
|
||||
getRowNode: mockGetRowNode,
|
||||
getModel: () => ({ getType: () => 'infinite' }),
|
||||
},
|
||||
} as unknown as AgGridReact,
|
||||
};
|
||||
|
||||
it('should return proper data', async () => {
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data?.length ?? 0).toEqual(5);
|
||||
});
|
||||
|
||||
it('should call mockRefreshInfiniteCache', async () => {
|
||||
renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
await waitFor(() => {
|
||||
updateMock({ delta: [anUpdatedOne] as Position[] });
|
||||
});
|
||||
|
||||
expect(mockRefreshInfiniteCache).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('no data should return null', () => {
|
||||
mockData = [];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(() => usePositionsData(partyIds, gridRef), {
|
||||
wrapper: MockedProvider,
|
||||
});
|
||||
expect(result.current.data).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import { positionsMetricsProvider } from './positions-data-providers';
|
||||
import type { PositionsQueryVariables } from './__generated__/Positions';
|
||||
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { GetRowsParams } from '@vegaprotocol/datagrid';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
export const getRowId = ({
|
||||
data,
|
||||
}: {
|
||||
data: Position & { isLastPlaceholder?: boolean; id?: string };
|
||||
}) =>
|
||||
data.isLastPlaceholder && data.id
|
||||
? data.id
|
||||
: `${data.partyId}-${data.marketId}`;
|
||||
|
||||
export const usePositionsData = (
|
||||
partyIds: string[],
|
||||
gridRef: RefObject<AgGridReact>
|
||||
) => {
|
||||
const variables = useMemo<PositionsQueryVariables>(
|
||||
() => ({ partyIds }),
|
||||
[partyIds]
|
||||
);
|
||||
const dataRef = useRef<Position[] | null>(null);
|
||||
const update = useCallback(
|
||||
({ data }: { data: Position[] | null }) => {
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
|
||||
const update: Position[] = [];
|
||||
const add: Position[] = [];
|
||||
data?.forEach((row) => {
|
||||
const rowNode = gridRef.current?.api?.getRowNode(
|
||||
getRowId({ data: row })
|
||||
);
|
||||
if (rowNode) {
|
||||
if (!isEqual(rowNode.data, row)) {
|
||||
update.push(row);
|
||||
}
|
||||
} else {
|
||||
add.push(row);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api?.applyTransaction({
|
||||
update,
|
||||
add,
|
||||
addIndex: 0,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider: positionsMetricsProvider,
|
||||
update,
|
||||
variables,
|
||||
});
|
||||
const getRows = useCallback(
|
||||
async ({ successCallback, startRow, endRow }: GetRowsParams<Position>) => {
|
||||
const rowsThisBlock = dataRef.current
|
||||
? dataRef.current.slice(startRow, endRow)
|
||||
: [];
|
||||
const lastRow = dataRef.current ? dataRef.current.length : 0;
|
||||
successCallback(rowsThisBlock, lastRow);
|
||||
},
|
||||
[]
|
||||
);
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
getRows,
|
||||
reload,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -2,9 +2,8 @@ import { useCallback, useState } from 'react';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { useVegaTransaction, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useVoteEvent } from './use-vote-event';
|
||||
import { VoteValue } from '@vegaprotocol/types';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { VoteEventFieldsFragment } from './__generated__/VoteSubsciption';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
|
||||
export type FinalizedVote = VoteEventFieldsFragment & { pubKey: string };
|
||||
|
||||
@@ -28,10 +27,7 @@ export const useVoteSubmit = () => {
|
||||
try {
|
||||
const res = await send(pubKey, {
|
||||
voteSubmission: {
|
||||
value:
|
||||
voteValue === VoteValue.VALUE_NO
|
||||
? vegaProtos.Vote.Value.VALUE_NO
|
||||
: vegaProtos.Vote.Value.VALUE_YES,
|
||||
value: voteValue,
|
||||
proposalId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
import { Icon } from '../icon';
|
||||
@@ -164,9 +164,9 @@ export const DropdownMenuSeparator = forwardRef<
|
||||
/**
|
||||
* Container element for submenus
|
||||
*/
|
||||
export const DropdownMenuSub = ({ ...subProps }) => (
|
||||
<DropdownMenuPrimitive.Sub {...subProps} />
|
||||
);
|
||||
export const DropdownMenuSub = (
|
||||
subProps: ComponentProps<typeof DropdownMenuPrimitive.Sub>
|
||||
) => <DropdownMenuPrimitive.Sub {...subProps} />;
|
||||
|
||||
/**
|
||||
* Container within a DropdownMenuSub specifically for the content
|
||||
@@ -200,9 +200,9 @@ export const DropdownMenuSubTrigger = forwardRef<
|
||||
* Portal to ensure menu portions are rendered outwith where they appear in the
|
||||
* DOM.
|
||||
*/
|
||||
export const DropdownMenuPortal = ({ ...portalProps }) => (
|
||||
<DropdownMenuPrimitive.Portal {...portalProps} />
|
||||
);
|
||||
export const DropdownMenuPortal = (
|
||||
portalProps: ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||
) => <DropdownMenuPrimitive.Portal {...portalProps} />;
|
||||
|
||||
/**
|
||||
* Wraps a regular DropdownMenuItem with copy to clip board functionality
|
||||
|
||||
@@ -45,7 +45,8 @@ export const Tabs = ({
|
||||
'cursor-default': isActive,
|
||||
'text-neutral-400 hover:text-neutral-500 dark:hover:text-neutral-300':
|
||||
!isActive,
|
||||
}
|
||||
},
|
||||
'flex items-center gap-2'
|
||||
);
|
||||
const borderClass = classNames(
|
||||
'absolute bottom-[-1px] left-0 w-full h-0 border-b',
|
||||
@@ -58,6 +59,7 @@ export const Tabs = ({
|
||||
value={child.props.id}
|
||||
className={triggerClass}
|
||||
>
|
||||
{child.props.indicator}
|
||||
{child.props.name}
|
||||
<span className={borderClass} />
|
||||
</TabsPrimitive.Trigger>
|
||||
@@ -87,6 +89,7 @@ interface TabProps {
|
||||
children: ReactNode;
|
||||
id: string;
|
||||
name: string;
|
||||
indicator?: ReactNode;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ export type ToastContent = JSX.Element | undefined;
|
||||
|
||||
type ToastState = 'initial' | 'showing' | 'expired';
|
||||
|
||||
type WithdrawalInfoMeta = { withdrawalId: string | undefined };
|
||||
|
||||
export type Toast = {
|
||||
id: string;
|
||||
intent: Intent;
|
||||
@@ -26,6 +28,9 @@ export type Toast = {
|
||||
onClose?: () => void;
|
||||
signal?: 'close';
|
||||
loader?: boolean;
|
||||
hidden?: boolean;
|
||||
// meta information
|
||||
meta?: WithdrawalInfoMeta | undefined;
|
||||
};
|
||||
|
||||
type ToastProps = Toast & {
|
||||
|
||||
@@ -13,11 +13,13 @@ import { Portal } from '@radix-ui/react-portal';
|
||||
type ToastsContainerProps = {
|
||||
toasts: Toasts;
|
||||
order: 'asc' | 'desc';
|
||||
showHidden?: boolean;
|
||||
};
|
||||
|
||||
export const ToastsContainer = ({
|
||||
toasts,
|
||||
order = 'asc',
|
||||
showHidden = false,
|
||||
}: ToastsContainerProps) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const closeAll = useToasts((store) => store.closeAll);
|
||||
@@ -41,6 +43,10 @@ export const ToastsContainer = ({
|
||||
};
|
||||
}, [count, order, toasts]);
|
||||
|
||||
const validToasts = Object.values(toasts).filter(
|
||||
(t) => !t.hidden || showHidden
|
||||
);
|
||||
|
||||
return (
|
||||
<Portal
|
||||
ref={ref as Ref<HTMLDivElement>}
|
||||
@@ -71,14 +77,16 @@ export const ToastsContainer = ({
|
||||
'flex-col-reverse': order === 'desc',
|
||||
})}
|
||||
>
|
||||
{toasts &&
|
||||
Object.values(toasts).map((toast) => {
|
||||
return (
|
||||
<li key={toast.id}>
|
||||
<Toast {...toast} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{validToasts.length > 0 &&
|
||||
validToasts
|
||||
.filter((t) => !t.hidden || showHidden)
|
||||
.map((toast) => {
|
||||
return (
|
||||
<li key={toast.id}>
|
||||
<Toast {...toast} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
title={t('Dismiss all toasts')}
|
||||
size="sm"
|
||||
@@ -89,7 +97,7 @@ export const ToastsContainer = ({
|
||||
'opacity-0 group-hover:opacity-50 hover:!opacity-100',
|
||||
'text-sm text-black dark:text-white bg-white dark:bg-black hover:!bg-white hover:dark:!bg-black',
|
||||
{
|
||||
hidden: Object.keys(toasts).length === 0,
|
||||
hidden: validToasts.length === 0,
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
|
||||
@@ -46,12 +46,20 @@ type Actions = {
|
||||
* Arbitrary removes all toasts
|
||||
*/
|
||||
removeAll: () => void;
|
||||
/**
|
||||
* Checks if a given toasts exists in the collection
|
||||
*/
|
||||
hasToast: (id: string) => boolean;
|
||||
/**
|
||||
* Closes toast by meta
|
||||
*/
|
||||
closeBy: (meta: Toast['meta']) => void;
|
||||
};
|
||||
|
||||
type ToastsStore = State & Actions;
|
||||
|
||||
export const useToasts = create<ToastsStore>()(
|
||||
immer((set) => ({
|
||||
immer((set, get) => ({
|
||||
toasts: {},
|
||||
count: 0,
|
||||
add: (toast) =>
|
||||
@@ -97,6 +105,18 @@ export const useToasts = create<ToastsStore>()(
|
||||
}
|
||||
}),
|
||||
removeAll: () => set({ toasts: {}, count: 0 }),
|
||||
hasToast: (id) => get().toasts[id] != null,
|
||||
closeBy: (meta) => {
|
||||
if (!meta) return;
|
||||
set((state) => {
|
||||
const found = Object.values(state.toasts).find((t) =>
|
||||
isEqual(t.meta, meta)
|
||||
);
|
||||
if (found) {
|
||||
found.signal = 'close';
|
||||
}
|
||||
});
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
@@ -45,13 +45,6 @@
|
||||
"options": {
|
||||
"command": "yarn tsc --project ./libs/wallet/tsconfig.spec.json"
|
||||
}
|
||||
},
|
||||
"generate": {
|
||||
"executor": "nx:run-commands",
|
||||
"outputs": [],
|
||||
"options": {
|
||||
"command": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./libs/wallet/src/__generated__ $WALLET_TRANSACTIONS_SOURCE --proto_path=$WALLET_TRANSACTIONS_ROOT --ts_proto_opt=onlyTypes=true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,98 @@
|
||||
import { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||
import type { SetOptional } from 'type-fest';
|
||||
import type { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import type { RecurringTransfer } from '@vegaprotocol/protos/dist/vega/commands/v1/RecurringTransfer';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { PeggedReference } from '@vegaprotocol/types';
|
||||
|
||||
export type OrderSubmission =
|
||||
vegaProtos.commands.v1.OrderSubmission.OrderSubmission;
|
||||
export type OriginalOrderCancellation =
|
||||
vegaProtos.commands.v1.OrderCancellation.OrderCancellation;
|
||||
export type LiquidityProvisionBody =
|
||||
vegaProtos.commands.v1.LiquidityProvisionSubmission.LiquidityProvisionSubmission;
|
||||
export type DelegateSubmission =
|
||||
vegaProtos.commands.v1.DelegateSubmission.DelegateSubmission;
|
||||
export type UndelegateSubmission =
|
||||
vegaProtos.commands.v1.UndelegateSubmission.UndelegateSubmission;
|
||||
export type OrderAmendment =
|
||||
vegaProtos.commands.v1.OrderAmendment.OrderAmendment;
|
||||
export type VoteSubmission =
|
||||
vegaProtos.commands.v1.VoteSubmission.VoteSubmission;
|
||||
export type OriginalWithdrawSubmission =
|
||||
vegaProtos.commands.v1.WithdrawSubmission.WithdrawSubmission;
|
||||
export type OriginalBatchMarketInstructions =
|
||||
vegaProtos.commands.v1.BatchMarketInstructions.BatchMarketInstructions;
|
||||
export type OriginalTransfer = vegaProtos.commands.v1.Transfer.Transfer;
|
||||
export interface LiquidityProvisionSubmission {
|
||||
liquidityProvisionSubmission: LiquidityProvisionBody;
|
||||
pubKey: string;
|
||||
propagate: boolean;
|
||||
}
|
||||
export interface LiquidityProvisionBody {
|
||||
marketId: string;
|
||||
commitmentAmount: string;
|
||||
fee: string;
|
||||
buys?: PeggedOrders[];
|
||||
sells?: PeggedOrders[];
|
||||
}
|
||||
|
||||
type WithdrawExt = vegaProtos.WithdrawExt.WithdrawExt;
|
||||
export interface PeggedOrders {
|
||||
offset: string;
|
||||
proportion: string;
|
||||
reference: PeggedReference;
|
||||
}
|
||||
|
||||
export interface DelegateSubmissionBody {
|
||||
delegateSubmission: {
|
||||
nodeId: string;
|
||||
amount: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UndelegateSubmissionBody {
|
||||
undelegateSubmission: {
|
||||
nodeId: string;
|
||||
amount: string;
|
||||
method: 'METHOD_NOW' | 'METHOD_AT_END_OF_EPOCH';
|
||||
};
|
||||
}
|
||||
|
||||
export interface OrderSubmission {
|
||||
marketId: string;
|
||||
reference?: string;
|
||||
type: Schema.OrderType;
|
||||
side: Schema.Side;
|
||||
timeInForce: Schema.OrderTimeInForce;
|
||||
size: string;
|
||||
price?: string;
|
||||
expiresAt?: string;
|
||||
postOnly?: boolean;
|
||||
reduceOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface OrderCancellation {
|
||||
orderId?: string;
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
export interface OrderAmendment {
|
||||
marketId: string;
|
||||
orderId: string;
|
||||
reference?: string;
|
||||
timeInForce: Schema.OrderTimeInForce;
|
||||
sizeDelta?: number;
|
||||
price?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
export interface OrderSubmissionBody {
|
||||
orderSubmission: OrderSubmission;
|
||||
}
|
||||
|
||||
export interface OrderCancellationBody {
|
||||
orderCancellation: OrderCancellation;
|
||||
}
|
||||
|
||||
export interface OrderAmendmentBody {
|
||||
orderAmendment: OrderAmendment;
|
||||
}
|
||||
|
||||
export interface VoteSubmissionBody {
|
||||
voteSubmission: {
|
||||
value: Schema.VoteValue;
|
||||
proposalId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WithdrawSubmissionBody {
|
||||
withdrawSubmission: {
|
||||
amount: string;
|
||||
asset: string;
|
||||
ext: {
|
||||
erc20: {
|
||||
receiverAddress: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface ProposalNewMarketTerms {
|
||||
newMarket: {
|
||||
@@ -233,71 +302,52 @@ export interface ProposalSubmission {
|
||||
| ProposalUpdateAssetTerms;
|
||||
}
|
||||
|
||||
// generated type doesn't match in many places with current implementation. It has to be overwrite here
|
||||
// and re-exported for saving consistency.
|
||||
export type WithdrawSubmission = Omit<OriginalWithdrawSubmission, 'ext'> &
|
||||
WithdrawExt;
|
||||
|
||||
export type OrderCancellation = SetOptional<
|
||||
OriginalOrderCancellation,
|
||||
'orderId' | 'marketId'
|
||||
>;
|
||||
export type BatchMarketInstructions = SetOptional<
|
||||
OriginalBatchMarketInstructions,
|
||||
'submissions' | 'amendments' | 'cancellations'
|
||||
> & {
|
||||
submissions?: OrderSubmission[];
|
||||
};
|
||||
|
||||
export type Transfer = SetOptional<OriginalTransfer, 'reference' | 'kind'> & {
|
||||
oneOff?: {
|
||||
deliverOn?: number; // omit for immediate
|
||||
};
|
||||
recurring?: SetOptional<RecurringTransfer, 'dispatchStrategy'>;
|
||||
};
|
||||
|
||||
export interface LiquidityProvisionSubmission {
|
||||
liquidityProvisionSubmission: LiquidityProvisionBody;
|
||||
pubKey: string;
|
||||
propagate: boolean;
|
||||
}
|
||||
|
||||
export interface DelegateSubmissionBody {
|
||||
delegateSubmission: DelegateSubmission;
|
||||
}
|
||||
|
||||
export interface UndelegateSubmissionBody {
|
||||
undelegateSubmission: UndelegateSubmission;
|
||||
}
|
||||
|
||||
export interface OrderSubmissionBody {
|
||||
orderSubmission: OrderSubmission;
|
||||
}
|
||||
|
||||
export interface OrderCancellationBody {
|
||||
orderCancellation: OrderCancellation;
|
||||
}
|
||||
|
||||
export interface OrderAmendmentBody {
|
||||
orderAmendment: OrderAmendment;
|
||||
}
|
||||
|
||||
export interface VoteSubmissionBody {
|
||||
voteSubmission: VoteSubmission;
|
||||
}
|
||||
|
||||
export interface WithdrawSubmissionBody {
|
||||
withdrawSubmission: WithdrawSubmission;
|
||||
}
|
||||
|
||||
export interface ProposalSubmissionBody {
|
||||
proposalSubmission: ProposalSubmission;
|
||||
}
|
||||
|
||||
export interface BatchMarketInstructionSubmissionBody {
|
||||
batchMarketInstructions: BatchMarketInstructions;
|
||||
batchMarketInstructions: {
|
||||
// Will be processed in this order and the total amount of instructions is
|
||||
// restricted by the net param spam.protection.max.batchSize
|
||||
cancellations?: OrderCancellation[];
|
||||
amendments?: OrderAmendment[];
|
||||
// Note: If multiple orders are submitted the first order ID is determined by hashing the signature of the transaction
|
||||
// (see determineId function). For each subsequent order's ID, a hash of the previous orders ID is used
|
||||
submissions?: OrderSubmission[];
|
||||
};
|
||||
}
|
||||
|
||||
interface TransferBase {
|
||||
fromAccountType: Schema.AccountType;
|
||||
to: string;
|
||||
toAccountType: Schema.AccountType;
|
||||
asset: string;
|
||||
amount: string;
|
||||
reference?: string;
|
||||
}
|
||||
|
||||
export interface OneOffTransfer extends TransferBase {
|
||||
oneOff: {
|
||||
deliverOn?: number; // omit for immediate
|
||||
};
|
||||
}
|
||||
|
||||
export interface RecurringTransfer extends TransferBase {
|
||||
recurring: {
|
||||
factor: string;
|
||||
startEpoch: number;
|
||||
endEpoch?: number;
|
||||
dispatchStrategy?: {
|
||||
assetForMetric: string;
|
||||
metric: Schema.DispatchMetric;
|
||||
markets?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type Transfer = OneOffTransfer | RecurringTransfer;
|
||||
|
||||
export interface TransferBody {
|
||||
transfer: Transfer;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import '../../../setup-test';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -5,11 +5,6 @@ import { ClientErrors } from './connectors';
|
||||
import { VegaTxStatus, orderErrorResolve } from './use-vega-transaction';
|
||||
import { useVegaTransactionStore } from './use-vega-transaction-store';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(BigInt.prototype as any).toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
export const useVegaTransactionManager = () => {
|
||||
const { sendTx, pubKey, disconnect } = useVegaWallet();
|
||||
const processed = useRef<Set<number>>(new Set());
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { useVegaTransactionStore } from './use-vega-transaction-store';
|
||||
import { VegaTxStatus } from './use-vega-transaction';
|
||||
import type { VegaStoredTxState } from './use-vega-transaction-store';
|
||||
import type {
|
||||
OrderAmendmentBody,
|
||||
OrderCancellationBody,
|
||||
WithdrawSubmissionBody,
|
||||
} from './connectors/vega-connector';
|
||||
import {
|
||||
OrderStatus,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
Side,
|
||||
} from '@vegaprotocol/types';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import type {
|
||||
WithdrawSubmission,
|
||||
OrderCancellation,
|
||||
OrderCancellationBody,
|
||||
WithdrawSubmissionBody,
|
||||
OrderAmendment,
|
||||
} from './connectors';
|
||||
|
||||
jest.mock('./utils', () => ({
|
||||
...jest.requireActual('./utils'),
|
||||
@@ -22,9 +19,7 @@ jest.mock('./utils', () => ({
|
||||
}));
|
||||
|
||||
describe('useVegaTransactionStore', () => {
|
||||
const orderCancellation: OrderCancellationBody = {
|
||||
orderCancellation: {} as OrderCancellation,
|
||||
};
|
||||
const orderCancellation: OrderCancellationBody = { orderCancellation: {} };
|
||||
const withdrawSubmission: WithdrawSubmissionBody = {
|
||||
withdrawSubmission: {
|
||||
amount: 'amount',
|
||||
@@ -34,16 +29,19 @@ describe('useVegaTransactionStore', () => {
|
||||
receiverAddress: 'receiverAddress',
|
||||
},
|
||||
},
|
||||
} as unknown as WithdrawSubmission,
|
||||
},
|
||||
};
|
||||
|
||||
const orderAmendment: OrderAmendment = {
|
||||
orderId: '6a4fcd0ba478df2f284ef5f6d3c64a478cb8043d3afe36f66f92c0ed92631e64',
|
||||
marketId:
|
||||
'3aa2a828687cc3d59e92445d294891cbbd40e2165bbfb15674158ef5d4e8848d',
|
||||
price: '1122',
|
||||
timeInForce: vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTC,
|
||||
sizeDelta: BigInt(0),
|
||||
const orderAmendment: OrderAmendmentBody = {
|
||||
orderAmendment: {
|
||||
orderId:
|
||||
'6a4fcd0ba478df2f284ef5f6d3c64a478cb8043d3afe36f66f92c0ed92631e64',
|
||||
marketId:
|
||||
'3aa2a828687cc3d59e92445d294891cbbd40e2165bbfb15674158ef5d4e8848d',
|
||||
price: '1122',
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
sizeDelta: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const originalOrder = {
|
||||
@@ -101,15 +99,9 @@ describe('useVegaTransactionStore', () => {
|
||||
});
|
||||
|
||||
it('updates an order with order amendment', () => {
|
||||
useVegaTransactionStore
|
||||
.getState()
|
||||
.create({ orderAmendment }, originalOrder);
|
||||
useVegaTransactionStore
|
||||
.getState()
|
||||
.create({ orderAmendment }, originalOrder);
|
||||
useVegaTransactionStore
|
||||
.getState()
|
||||
.create({ orderAmendment }, originalOrder);
|
||||
useVegaTransactionStore.getState().create(orderAmendment, originalOrder);
|
||||
useVegaTransactionStore.getState().create(orderAmendment, originalOrder);
|
||||
useVegaTransactionStore.getState().create(orderAmendment, originalOrder);
|
||||
const transaction = useVegaTransactionStore.getState().transactions[1];
|
||||
useVegaTransactionStore
|
||||
.getState()
|
||||
@@ -135,8 +127,8 @@ describe('useVegaTransactionStore', () => {
|
||||
orderId:
|
||||
'6a4fcd0ba478df2f284ef5f6d3c64a478cb8043d3afe36f66f92c0ed92631e64',
|
||||
price: '1122',
|
||||
sizeDelta: BigInt(0),
|
||||
timeInForce: vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTC,
|
||||
sizeDelta: 0,
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
},
|
||||
};
|
||||
expect(
|
||||
|
||||
@@ -131,34 +131,30 @@ export const useVegaTransactionStore = create<VegaTransactionStore>()(
|
||||
set(
|
||||
produce((state: VegaTransactionStore) => {
|
||||
const transaction = state.transactions.find((transaction) => {
|
||||
if (
|
||||
!transaction ||
|
||||
transaction.status !== VegaTxStatus.Pending ||
|
||||
!transaction?.body
|
||||
) {
|
||||
if (!transaction || transaction.status !== VegaTxStatus.Pending) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
isOrderSubmissionTransaction(transaction.body) &&
|
||||
isOrderSubmissionTransaction(transaction?.body) &&
|
||||
transaction.signature &&
|
||||
order.id === determineId(transaction.signature)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
isOrderCancellationTransaction(transaction.body) &&
|
||||
isOrderCancellationTransaction(transaction?.body) &&
|
||||
order.id === transaction.body.orderCancellation.orderId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
isOrderAmendmentTransaction(transaction.body) &&
|
||||
isOrderAmendmentTransaction(transaction?.body) &&
|
||||
order.id === transaction.body.orderAmendment.orderId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
isBatchMarketInstructionsTransaction(transaction.body) &&
|
||||
isBatchMarketInstructionsTransaction(transaction?.body) &&
|
||||
transaction.signature &&
|
||||
order.id === determineId(transaction.signature)
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { determineId } from './utils';
|
||||
|
||||
import {
|
||||
determineId,
|
||||
normalizeOrderAmendment,
|
||||
normalizeOrderSubmission,
|
||||
} from './utils';
|
||||
import type { OrderSubmissionBody } from './connectors/vega-connector';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
describe('determineId', () => {
|
||||
it('produces a known result for an ID', () => {
|
||||
const res = determineId(
|
||||
@@ -10,3 +15,113 @@ describe('determineId', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeOrderSubmission', () => {
|
||||
it('sets and formats price only for limit orders', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{ price: '100' } as unknown as OrderSubmissionBody['orderSubmission'],
|
||||
2,
|
||||
1
|
||||
).price
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
price: '100',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
} as unknown as OrderSubmissionBody['orderSubmission'],
|
||||
2,
|
||||
1
|
||||
).price
|
||||
).toEqual('10000');
|
||||
});
|
||||
|
||||
it('sets and formats expiresAt only for time in force orders', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
} as OrderSubmissionBody['orderSubmission'],
|
||||
2,
|
||||
1
|
||||
).expiresAt
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
} as OrderSubmissionBody['orderSubmission'],
|
||||
2,
|
||||
1
|
||||
).expiresAt
|
||||
).toEqual('1640995200000000000');
|
||||
});
|
||||
|
||||
it('formats size', () => {
|
||||
expect(
|
||||
normalizeOrderSubmission(
|
||||
{
|
||||
size: '100',
|
||||
} as OrderSubmissionBody['orderSubmission'],
|
||||
2,
|
||||
1
|
||||
).size
|
||||
).toEqual('1000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeOrderAmendment', () => {
|
||||
type Order = Parameters<typeof normalizeOrderAmendment>[0];
|
||||
type Market = Parameters<typeof normalizeOrderAmendment>[1];
|
||||
const order: Order = {
|
||||
id: '123',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
size: '100',
|
||||
expiresAt: '2022-01-01T00:00:00.000Z',
|
||||
};
|
||||
const market: Market = {
|
||||
id: '456',
|
||||
decimalPlaces: 1,
|
||||
positionDecimalPlaces: 1,
|
||||
};
|
||||
|
||||
it('sets and formats order id, market id, expires and timeInForce as given', () => {
|
||||
const orderAmendment = normalizeOrderAmendment(order, market, '1', '1');
|
||||
expect(orderAmendment.orderId).toEqual('123');
|
||||
expect(orderAmendment.marketId).toEqual('456');
|
||||
expect(orderAmendment.expiresAt).toEqual('1640995200000000000');
|
||||
expect(orderAmendment.timeInForce).toEqual(
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['1.1', 1, '11'],
|
||||
['1.1', 2, '110'],
|
||||
['0.001', 8, '100000'],
|
||||
])('sets and formats price', (price, decimalPlaces, output) => {
|
||||
const orderAmendment = normalizeOrderAmendment(
|
||||
order,
|
||||
{ ...market, decimalPlaces },
|
||||
price,
|
||||
'1'
|
||||
);
|
||||
expect(orderAmendment.price).toEqual(output);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['9', 1, -10],
|
||||
['90', 2, 8900],
|
||||
['0.001', 8, 99900],
|
||||
])('sets and formats size delta', (size, positionDecimalPlaces, output) => {
|
||||
const orderAmendment = normalizeOrderAmendment(
|
||||
order,
|
||||
{ ...market, positionDecimalPlaces },
|
||||
'1',
|
||||
size
|
||||
);
|
||||
expect(orderAmendment.sizeDelta).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
+70
-53
@@ -1,11 +1,16 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { removeDecimal, toNanoSeconds } from '@vegaprotocol/utils';
|
||||
import type { Market, Order } from '@vegaprotocol/types';
|
||||
import { OrderTimeInForce, OrderType, AccountType } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { sha3_256 } from 'js-sha3';
|
||||
import type { Transaction } from './connectors';
|
||||
import { vega as vegaProtos } from '@vegaprotocol/protos';
|
||||
import type { Side } from '@vegaprotocol/protos/dist/vega/Side';
|
||||
import type { TimeInForce } from '@vegaprotocol/protos/dist/vega/Order/TimeInForce';
|
||||
import type { Type as ProtoOrderType } from '@vegaprotocol/protos/dist/vega/Order';
|
||||
import type {
|
||||
OrderAmendment,
|
||||
OrderSubmission,
|
||||
Transaction,
|
||||
Transfer,
|
||||
} from './connectors';
|
||||
import type { Exact } from 'type-fest';
|
||||
|
||||
/**
|
||||
* Creates an ID in the same way that core does on the backend. This way we
|
||||
@@ -24,53 +29,65 @@ export const encodeTransaction = (tx: Transaction): string => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SideMap: Readonly<Record<Schema.Side, Side>> = {
|
||||
[Schema.Side.SIDE_BUY]: vegaProtos.Side.SIDE_BUY,
|
||||
[Schema.Side.SIDE_SELL]: vegaProtos.Side.SIDE_SELL,
|
||||
[Schema.Side.SIDE_UNSPECIFIED]: vegaProtos.Side.SIDE_UNSPECIFIED,
|
||||
};
|
||||
export const normalizeOrderSubmission = (
|
||||
order: OrderSubmission,
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
): OrderSubmission => ({
|
||||
marketId: order.marketId,
|
||||
reference: order.reference,
|
||||
type: order.type,
|
||||
side: order.side,
|
||||
timeInForce: order.timeInForce,
|
||||
price:
|
||||
order.type === OrderType.TYPE_LIMIT && order.price
|
||||
? removeDecimal(order.price, decimalPlaces)
|
||||
: undefined,
|
||||
size: removeDecimal(order.size, positionDecimalPlaces),
|
||||
expiresAt:
|
||||
order.expiresAt && order.timeInForce === OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
? toNanoSeconds(order.expiresAt)
|
||||
: undefined,
|
||||
postOnly: order.postOnly,
|
||||
reduceOnly: order.reduceOnly,
|
||||
});
|
||||
|
||||
export const SideRevertMap: Record<Side, Schema.Side> = Object.entries(
|
||||
SideMap
|
||||
).reduce((agg, item) => {
|
||||
agg[item[1]] = item[0] as Schema.Side;
|
||||
return agg;
|
||||
}, {} as Record<Side, Schema.Side>);
|
||||
export const normalizeOrderAmendment = <T extends Exact<OrderAmendment, T>>(
|
||||
order: Pick<Order, 'id' | 'timeInForce' | 'size' | 'expiresAt'>,
|
||||
market: Pick<Market, 'id' | 'decimalPlaces' | 'positionDecimalPlaces'>,
|
||||
price: string,
|
||||
size: string
|
||||
): OrderAmendment => ({
|
||||
orderId: order.id,
|
||||
marketId: market.id,
|
||||
price: removeDecimal(price, market.decimalPlaces),
|
||||
timeInForce: order.timeInForce,
|
||||
sizeDelta: size
|
||||
? new BigNumber(removeDecimal(size, market.positionDecimalPlaces))
|
||||
.minus(order.size)
|
||||
.toNumber()
|
||||
: 0,
|
||||
expiresAt: order.expiresAt
|
||||
? toNanoSeconds(order.expiresAt) // Wallet expects timestamp in nanoseconds
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export const TimeInForceMap: Readonly<
|
||||
Record<Schema.OrderTimeInForce, TimeInForce>
|
||||
> = {
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_FOK]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_FOK,
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_GFA]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GFA,
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_GFN]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GFN,
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_GTC]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTC,
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_GTT]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_GTT,
|
||||
[Schema.OrderTimeInForce.TIME_IN_FORCE_IOC]:
|
||||
vegaProtos.Order.TimeInForce.TIME_IN_FORCE_IOC,
|
||||
};
|
||||
|
||||
export const TimeInForceRevertMap: Record<
|
||||
TimeInForce,
|
||||
Schema.OrderTimeInForce
|
||||
> = Object.entries(TimeInForceMap).reduce((agg, item) => {
|
||||
agg[item[1]] = item[0] as Schema.OrderTimeInForce;
|
||||
return agg;
|
||||
}, {} as Record<TimeInForce, Schema.OrderTimeInForce>);
|
||||
|
||||
export const TypeMap: Readonly<Record<Schema.OrderType, ProtoOrderType.Type>> =
|
||||
{
|
||||
[Schema.OrderType.TYPE_LIMIT]: vegaProtos.Order.Type.TYPE_LIMIT,
|
||||
[Schema.OrderType.TYPE_MARKET]: vegaProtos.Order.Type.TYPE_MARKET,
|
||||
[Schema.OrderType.TYPE_NETWORK]: vegaProtos.Order.Type.TYPE_NETWORK,
|
||||
export const normalizeTransfer = <T extends Exact<Transfer, T>>(
|
||||
address: string,
|
||||
amount: string,
|
||||
asset: {
|
||||
id: string;
|
||||
decimals: number;
|
||||
}
|
||||
): Transfer => {
|
||||
return {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: address,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
// oneOff or recurring required otherwise wallet will error
|
||||
// default oneOff is immediate transfer
|
||||
oneOff: {},
|
||||
};
|
||||
|
||||
export const TypeRevertMap: Record<ProtoOrderType.Type, Schema.OrderType> =
|
||||
Object.entries(TypeMap).reduce((agg, item) => {
|
||||
agg[item[1]] = item[0] as Schema.OrderType;
|
||||
return agg;
|
||||
}, {} as Record<ProtoOrderType.Type, Schema.OrderType>);
|
||||
};
|
||||
|
||||
@@ -27,3 +27,4 @@ export * from './lib/web3-connectors';
|
||||
export * from './lib/web3-provider';
|
||||
export * from './lib/withdrawal-approval-dialog';
|
||||
export * from './lib/withdrawal-approval-status';
|
||||
export * from './lib/default-web3-provider';
|
||||
|
||||
@@ -16,3 +16,6 @@ export const getChainName = (chainId: number | null | undefined) => {
|
||||
*/
|
||||
export const WALLETCONNECT_PROJECT_ID =
|
||||
process.env['NX_WALLETCONNECT_PROJECT_ID'] || '';
|
||||
|
||||
export const ETHEREUM_PROVIDER_URL =
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL'] || '';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ethers } from 'ethers';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export type DefaultWeb3ProviderContextShape = {
|
||||
provider?: ethers.providers.JsonRpcProvider;
|
||||
};
|
||||
export const DefaultWeb3ProviderContext = createContext<
|
||||
DefaultWeb3ProviderContextShape | undefined
|
||||
>(undefined);
|
||||
|
||||
export const useDefaultWeb3Provider = () => {
|
||||
const context = useContext(DefaultWeb3ProviderContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'useDefaultWeb3Provider must be used within DefaultWeb3ProviderContext'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -2,23 +2,40 @@ import { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useEthereumConfig } from './use-ethereum-config';
|
||||
import { useDefaultWeb3Provider } from './default-web3-provider';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
|
||||
export const useBridgeContract = () => {
|
||||
const { provider } = useWeb3React();
|
||||
export const useBridgeContract = (allowDefaultProvider = false) => {
|
||||
const { provider: activeProvider } = useWeb3React();
|
||||
const { provider: defaultProvider } = useDefaultWeb3Provider();
|
||||
const { config } = useEthereumConfig();
|
||||
const logger = localLoggerFactory({ application: 'web3' });
|
||||
|
||||
const provider = useMemo(() => {
|
||||
if (!activeProvider && allowDefaultProvider) {
|
||||
logger.info('will use default web3 provider');
|
||||
return defaultProvider;
|
||||
}
|
||||
return activeProvider;
|
||||
}, [activeProvider, allowDefaultProvider, defaultProvider, logger]);
|
||||
|
||||
// this has to be memoized, otherwise it ticks like crazy
|
||||
const signer = useMemo(() => {
|
||||
return !activeProvider && allowDefaultProvider
|
||||
? undefined
|
||||
: activeProvider?.getSigner();
|
||||
}, [activeProvider, allowDefaultProvider]);
|
||||
|
||||
const contract = useMemo(() => {
|
||||
if (!provider || !config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const signer = provider.getSigner();
|
||||
|
||||
return new CollateralBridge(
|
||||
config.collateral_bridge_contract.address,
|
||||
signer || provider
|
||||
);
|
||||
}, [provider, config]);
|
||||
}, [provider, signer, config]);
|
||||
|
||||
return contract;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { MultisigControl } from '@vegaprotocol/smart-contracts';
|
||||
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
||||
import type { Token } from '@vegaprotocol/smart-contracts';
|
||||
|
||||
import type { DepositBusEventFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
DepositBusEventFieldsFragment,
|
||||
WithdrawalBusEventFieldsFragment,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
import type { EthTxState } from './use-ethereum-transaction';
|
||||
import { EthTxStatus } from './use-ethereum-transaction';
|
||||
@@ -27,6 +30,7 @@ export interface EthStoredTxState extends EthTxState {
|
||||
requiresConfirmation: boolean; // whether or not the tx needs external confirmation (IE from a subscription even)
|
||||
assetId?: string;
|
||||
deposit?: DepositBusEventFieldsFragment;
|
||||
withdrawal?: WithdrawalBusEventFieldsFragment;
|
||||
}
|
||||
|
||||
export interface EthTransactionStore {
|
||||
@@ -37,7 +41,8 @@ export interface EthTransactionStore {
|
||||
args: string[],
|
||||
assetId?: string,
|
||||
requiredConfirmations?: number,
|
||||
requiresConfirmation?: boolean
|
||||
requiresConfirmation?: boolean,
|
||||
withdrawal?: EthStoredTxState['withdrawal']
|
||||
) => number;
|
||||
update: (
|
||||
id: EthStoredTxState['id'],
|
||||
@@ -62,7 +67,8 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
|
||||
args: string[] = [],
|
||||
assetId?: string,
|
||||
requiredConfirmations = 1,
|
||||
requiresConfirmation = false
|
||||
requiresConfirmation = false,
|
||||
withdrawal = undefined
|
||||
) => {
|
||||
const transactions = get().transactions;
|
||||
const now = new Date();
|
||||
@@ -82,6 +88,7 @@ export const useEthTransactionStore = create<EthTransactionStore>()(
|
||||
requiredConfirmations,
|
||||
requiresConfirmation,
|
||||
assetId,
|
||||
withdrawal,
|
||||
};
|
||||
set({ transactions: transactions.concat(transaction) });
|
||||
return transaction.id;
|
||||
|
||||
@@ -162,9 +162,10 @@ const isFinal = (tx: EthStoredTxState) =>
|
||||
[EthTxStatus.Confirmed, EthTxStatus.Error].includes(tx.status);
|
||||
|
||||
export const useEthereumTransactionToasts = () => {
|
||||
const [setToast, removeToast] = useToasts((store) => [
|
||||
const [setToast, removeToast, closeToastBy] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.remove,
|
||||
store.closeBy,
|
||||
]);
|
||||
|
||||
const dismissTx = useEthTransactionStore((state) => state.dismiss);
|
||||
@@ -173,8 +174,16 @@ export const useEthereumTransactionToasts = () => {
|
||||
(tx: EthStoredTxState) => () => {
|
||||
dismissTx(tx.id);
|
||||
removeToast(`eth-${tx.id}`);
|
||||
// closes related "Funds released" toast after successful withdrawal
|
||||
if (
|
||||
isWithdrawTransaction(tx) &&
|
||||
tx.status === EthTxStatus.Confirmed &&
|
||||
tx.withdrawal
|
||||
) {
|
||||
closeToastBy({ withdrawalId: tx.withdrawal.id });
|
||||
}
|
||||
},
|
||||
[dismissTx, removeToast]
|
||||
[closeToastBy, dismissTx, removeToast]
|
||||
);
|
||||
|
||||
const fromEthTransaction = useCallback(
|
||||
|
||||
@@ -298,14 +298,19 @@ describe('useEthWithdrawApprovalsManager', () => {
|
||||
mockEthTransactionStoreState.mockReturnValue({ create });
|
||||
render();
|
||||
await waitForNextTick();
|
||||
expect(create).toBeCalledWith({}, 'withdraw_asset', [
|
||||
erc20WithdrawalApproval.assetSource,
|
||||
erc20WithdrawalApproval.amount,
|
||||
erc20WithdrawalApproval.targetAddress,
|
||||
erc20WithdrawalApproval.creation,
|
||||
erc20WithdrawalApproval.nonce,
|
||||
erc20WithdrawalApproval.signatures,
|
||||
]);
|
||||
expect(create).toHaveBeenCalled();
|
||||
expect(create.mock.calls[0][0]).toEqual({});
|
||||
expect(create.mock.calls[0][1]).toEqual('withdraw_asset');
|
||||
expect(create.mock.calls[0][2]).toEqual(
|
||||
expect.arrayContaining([
|
||||
erc20WithdrawalApproval.assetSource,
|
||||
erc20WithdrawalApproval.amount,
|
||||
erc20WithdrawalApproval.targetAddress,
|
||||
erc20WithdrawalApproval.creation,
|
||||
erc20WithdrawalApproval.nonce,
|
||||
erc20WithdrawalApproval.signatures,
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('detect wrong chainId', () => {
|
||||
|
||||
@@ -91,7 +91,8 @@ export const useEthWithdrawApprovalsManager = () => {
|
||||
if (threshold && amount.isGreaterThan(threshold)) {
|
||||
const delaySecs = await getDelay();
|
||||
const completeTimestamp =
|
||||
new Date(withdrawal.createdTimestamp).getTime() + delaySecs * 1000;
|
||||
new Date(withdrawal.createdTimestamp).getTime() +
|
||||
(delaySecs as number) * 1000;
|
||||
const now = Date.now();
|
||||
if (now < completeTimestamp) {
|
||||
update(transaction.id, {
|
||||
@@ -139,7 +140,11 @@ export const useEthWithdrawApprovalsManager = () => {
|
||||
approval.creation,
|
||||
approval.nonce,
|
||||
approval.signatures,
|
||||
]
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
transaction.withdrawal || undefined
|
||||
);
|
||||
})().catch((err) => {
|
||||
localLoggerFactory({ application: 'web3' }).error(
|
||||
|
||||
@@ -3,21 +3,44 @@ import { useCallback } from 'react';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
|
||||
/**
|
||||
* Gets the delay in seconds thats required if the withdrawal amount is
|
||||
* over the withdrawal threshold (contract.get_withdraw_threshold)
|
||||
* The withdraw delay is a global value set on the contract bridge which may be
|
||||
* changed via a proposal therefore it can be cached for a set amount of time
|
||||
* (MAX_AGE) and re-retrieved when necessary.
|
||||
*/
|
||||
|
||||
const MAX_AGE = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
type TimestampedDelay = { value: number | undefined; ts: number };
|
||||
const DELAY: TimestampedDelay = { value: undefined, ts: 0 };
|
||||
|
||||
/**
|
||||
* Returns a function that gets the delay in seconds that's required if the
|
||||
* withdrawal amount is over the withdrawal threshold
|
||||
* (contract.get_withdraw_threshold)
|
||||
*/
|
||||
export const useGetWithdrawDelay = () => {
|
||||
const contract = useBridgeContract();
|
||||
const contract = useBridgeContract(true);
|
||||
const logger = localLoggerFactory({ application: 'web3' });
|
||||
|
||||
const getDelay = useCallback(async () => {
|
||||
const logger = localLoggerFactory({ application: 'web3' });
|
||||
try {
|
||||
logger.info('get withdraw delay', { contract: contract?.toString() });
|
||||
const res = await contract?.default_withdraw_delay();
|
||||
return res.toNumber();
|
||||
} catch (err) {
|
||||
logger.error('get withdraw delay', err);
|
||||
if (DELAY.value != null && Date.now() - DELAY.ts <= MAX_AGE) {
|
||||
return DELAY.value;
|
||||
}
|
||||
}, [contract]);
|
||||
if (!contract) {
|
||||
logger.info('could not get withdraw delay: no bridge contract');
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const res = await contract?.default_withdraw_delay();
|
||||
logger.info(`retrieved withdraw delay: ${res} seconds`);
|
||||
DELAY.value = res.toNumber();
|
||||
DELAY.ts = Date.now();
|
||||
return res.toNumber() as number;
|
||||
} catch (err) {
|
||||
logger.error('could not get withdraw delay', err);
|
||||
return undefined;
|
||||
}
|
||||
}, [contract, logger]);
|
||||
|
||||
return getDelay;
|
||||
};
|
||||
|
||||
@@ -3,33 +3,74 @@ import { useBridgeContract } from './use-bridge-contract';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import type { WithdrawalBusEventFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
|
||||
type Asset = Pick<
|
||||
WithdrawalBusEventFieldsFragment['asset'],
|
||||
'source' | 'decimals'
|
||||
>;
|
||||
|
||||
/**
|
||||
* Returns a function to get the threshold amount for a withdrawal. If a withdrawal amount
|
||||
* is greater than this value it will incur a delay before being able to be completed. The delay is set
|
||||
* on the smart contract and can be retrieved using contract.default_withdraw_delay
|
||||
* The withdraw threshold is a value set on the contract bridge per asset which
|
||||
* may be changed via a proposal therefore it can be cached for a set amount of
|
||||
* time (MAX_AGE) and re-retrieved when necessary.
|
||||
*/
|
||||
|
||||
const MAX_AGE = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
export const BUILTIN_ASSET_ADDRESS = 'builtin';
|
||||
export const BUILTIN_ASSET_THRESHOLD = new BigNumber(Infinity);
|
||||
type TimestampedThreshold = { value: BigNumber; ts: number };
|
||||
const THRESHOLDS: Record<string, TimestampedThreshold> = {};
|
||||
const setThreshold = (address: string, value: BigNumber) =>
|
||||
(THRESHOLDS[address] = { value, ts: Date.now() });
|
||||
|
||||
export const addr = (asset: Asset | undefined) =>
|
||||
asset && asset.source.__typename === 'ERC20'
|
||||
? asset.source.contractAddress
|
||||
: BUILTIN_ASSET_ADDRESS;
|
||||
|
||||
/**
|
||||
* Returns a function to get the threshold amount for a withdrawal.
|
||||
* If a withdrawal amount is greater than this value it will incur a delay
|
||||
* before being able to be completed. The delay is set on the smart contract and
|
||||
* can be retrieved using contract.default_withdraw_delay
|
||||
*/
|
||||
export const useGetWithdrawThreshold = () => {
|
||||
const contract = useBridgeContract();
|
||||
const logger = localLoggerFactory({ application: 'web3' });
|
||||
const contract = useBridgeContract(true);
|
||||
|
||||
const getThreshold = useCallback(
|
||||
async (
|
||||
asset:
|
||||
| Pick<WithdrawalBusEventFieldsFragment['asset'], 'source' | 'decimals'>
|
||||
| undefined
|
||||
) => {
|
||||
if (!contract || asset?.source.__typename !== 'ERC20') {
|
||||
return new BigNumber(Infinity);
|
||||
async (asset: Asset | undefined) => {
|
||||
const contractAddress = addr(asset);
|
||||
// return cached value if still valid
|
||||
const thr = THRESHOLDS[contractAddress];
|
||||
if (thr && Date.now() - thr.ts <= MAX_AGE) {
|
||||
return thr.value;
|
||||
}
|
||||
if (!contract || !asset || contractAddress === BUILTIN_ASSET_ADDRESS) {
|
||||
setThreshold(BUILTIN_ASSET_ADDRESS, BUILTIN_ASSET_THRESHOLD);
|
||||
return BUILTIN_ASSET_THRESHOLD;
|
||||
}
|
||||
try {
|
||||
const res = await contract.get_withdraw_threshold(contractAddress);
|
||||
const value = new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||
const threshold = value.isEqualTo(0)
|
||||
? new BigNumber(Infinity)
|
||||
: value.minus(new BigNumber(addDecimal('1', asset.decimals)));
|
||||
logger.info(
|
||||
`retrieved withdraw threshold for ${addr(
|
||||
asset
|
||||
)}: ${threshold.toString()}`
|
||||
);
|
||||
setThreshold(contractAddress, threshold);
|
||||
return threshold;
|
||||
} catch (err) {
|
||||
logger.error('could not get the withdraw thresholds', err);
|
||||
return undefined;
|
||||
}
|
||||
const res = await contract.get_withdraw_threshold(
|
||||
asset.source.contractAddress
|
||||
);
|
||||
const value = new BigNumber(addDecimal(res.toString(), asset.decimals));
|
||||
const threshold = value.isEqualTo(0)
|
||||
? new BigNumber(Infinity)
|
||||
: value.minus(new BigNumber(addDecimal('1', asset.decimals)));
|
||||
return threshold;
|
||||
},
|
||||
[contract]
|
||||
[contract, logger]
|
||||
);
|
||||
|
||||
return getThreshold;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user