Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d353f24f01 | ||
|
|
5755347a31 | ||
|
|
ade1dff30d | ||
|
|
5ca65260c9 | ||
|
|
c276106cfc | ||
|
|
a1ec426a6e | ||
|
|
e20202f895 | ||
|
|
58cc074538 | ||
|
|
852c9bb8f4 | ||
|
|
efe9b7a0e7 | ||
|
|
695a059dde | ||
|
|
5d7e4d273a |
@@ -24,8 +24,10 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_REFERRALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_GOVERNANCE_TRANSFERS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"rationale": {
|
||||
"title": "Governance cancel transfer proposal",
|
||||
"description": "Rejected cancel transfer proposal"
|
||||
},
|
||||
"terms": {
|
||||
"cancelTransfer": {
|
||||
"changes": {
|
||||
"transferId": "invalid transfer id"
|
||||
}
|
||||
},
|
||||
"closingTimestamp": 0,
|
||||
"enactmentTimestamp": 0
|
||||
}
|
||||
}
|
||||
@@ -27,12 +27,16 @@ import {
|
||||
} from '../../../../governance-e2e/src/support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
|
||||
import {
|
||||
depositAsset,
|
||||
switchVegaWalletPubKey,
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
} from '../../support/wallet-functions';
|
||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||
import {
|
||||
createGovernanceTransferProposalTxBody,
|
||||
createSuccessorMarketProposalTxBody,
|
||||
} from '../../support/proposal.functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const participationNotMet = 'token-participation-not-met';
|
||||
@@ -51,6 +55,7 @@ const openProposals = 'open-proposals';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
const governanceTransferToggle = 'proposal-transfer-details';
|
||||
const marketProposalType = 'proposal-type';
|
||||
|
||||
describe(
|
||||
@@ -520,5 +525,78 @@ describe(
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to see governance transfer proposal', function () {
|
||||
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
|
||||
depositAsset(vegaAssetAddress, '1000', 18);
|
||||
cy.getByTestId('currency-title', Cypress.env('txTimeout')).should(
|
||||
'contain.text',
|
||||
'Collateral'
|
||||
);
|
||||
cy.VegaWalletTopUpNetworkAccount('100');
|
||||
cy.VegaWalletSubmitProposal(createGovernanceTransferProposalTxBody());
|
||||
cy.reload();
|
||||
getProposalFromTitle('Governance transfer proposal').within(() => {
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
|
||||
cy.getByTestId(governanceTransferToggle).click();
|
||||
cy.getByTestId('proposal-transfer-details-table').within(() => {
|
||||
getProposalInformationFromTable('Source Type')
|
||||
.invoke('text')
|
||||
.and('eq', 'Network Treasury');
|
||||
getProposalInformationFromTable('Destination')
|
||||
.invoke('text')
|
||||
.and('eq', Cypress.env('vegaWalletPublicKey'));
|
||||
getProposalInformationFromTable('Asset')
|
||||
.invoke('text')
|
||||
.and('eq', 'VEGA');
|
||||
getProposalInformationFromTable('Fraction Of Balance')
|
||||
.invoke('text')
|
||||
.and('eq', '50%');
|
||||
getProposalInformationFromTable('Amount')
|
||||
.invoke('text')
|
||||
.and('eq', '100.00');
|
||||
getProposalInformationFromTable('Transfer Type')
|
||||
.invoke('text')
|
||||
.and('eq', 'All or nothing');
|
||||
getProposalInformationFromTable('Kind')
|
||||
.invoke('text')
|
||||
.and('eq', 'One off');
|
||||
});
|
||||
});
|
||||
|
||||
it(' Able to see cancel transfer proposal - rejected', function () {
|
||||
const proposalPath = 'src/fixtures/proposals/cancel-transfer-raw.json';
|
||||
const enactmentTimestamp =
|
||||
createTenDigitUnixTimeStampForSpecifiedDays(11);
|
||||
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(10);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
closingTimestamp: closingTimestamp,
|
||||
submit: false,
|
||||
});
|
||||
cy.getByTestId('proposal-submit').should('be.visible').click();
|
||||
cy.getByTestId('dialog-title').should('have.text', 'Proposal rejected');
|
||||
cy.getByTestId('icon-cross').last().click();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get('[href="/proposals/rejected"]').click();
|
||||
getProposalFromTitle('Governance cancel transfer proposal').within(() => {
|
||||
cy.getByTestId(marketProposalType).should(
|
||||
'have.text',
|
||||
'CancelTransfer'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'CancelTransfer');
|
||||
getProposalInformationFromTable('Error details')
|
||||
.invoke('text')
|
||||
.and('eq', 'Governance transfer invalid transfer id not found');
|
||||
getProposalInformationFromTable('transferId')
|
||||
.invoke('text')
|
||||
.and('eq', 'invalid transfer id');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
import { addDays, addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { upgradeProposalsData } from '../fixtures/mocks/network-upgrade';
|
||||
import { proposalsData } from '../fixtures/mocks/proposals';
|
||||
import { nodeData } from '../fixtures/mocks/nodes';
|
||||
import { AccountType, GovernanceTransferType } from '@vegaprotocol/types';
|
||||
|
||||
export function createUpdateNetworkProposalTxBody(): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
@@ -358,6 +359,46 @@ export function createSuccessorMarketProposalTxBody(
|
||||
};
|
||||
}
|
||||
|
||||
export function createGovernanceTransferProposalTxBody(): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
const MIN_ENACT_SEC = 7;
|
||||
|
||||
const closingDate = addDays(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addDays(closingDate, MIN_ENACT_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
const destination = Cypress.env('vegaWalletPublicKey');
|
||||
return {
|
||||
proposalSubmission: {
|
||||
rationale: {
|
||||
title: 'Governance transfer proposal',
|
||||
description: 'E2E test for transfer proposal test',
|
||||
},
|
||||
terms: {
|
||||
newTransfer: {
|
||||
changes: {
|
||||
fractionOfBalance: '0.5',
|
||||
amount: '100' + '0'.repeat(18),
|
||||
sourceType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
source: '',
|
||||
transferType:
|
||||
GovernanceTransferType.GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING,
|
||||
destinationType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
destination,
|
||||
asset:
|
||||
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b',
|
||||
oneOff: {
|
||||
deliverOn: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mockNetworkUpgradeProposal() {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||
|
||||
@@ -41,6 +41,9 @@ export async function depositAsset(
|
||||
) {
|
||||
// Approve asset
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
// Wait needed to allow Eth chain to catch up
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(4000);
|
||||
cy.wrap(
|
||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||
transactionTimeout
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable */
|
||||
process.env.TZ = 'GMT';
|
||||
export default {
|
||||
displayName: 'governance',
|
||||
preset: '../../jest.preset.js',
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #000;
|
||||
}
|
||||
.pre-loader .pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader .pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(0) {
|
||||
animation-delay: 0ms;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -0.1s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 1s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.9s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
animation-delay: -1.4s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
animation-delay: 1.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -2.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
animation-delay: 1.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -1.95s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 2.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(17) {
|
||||
animation-delay: -0.85s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(18) {
|
||||
animation-delay: 1.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(19) {
|
||||
animation-delay: -1.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(20) {
|
||||
animation-delay: 5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(21) {
|
||||
animation-delay: -5.25s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(22) {
|
||||
animation-delay: 4.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(23) {
|
||||
animation-delay: -5.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(24) {
|
||||
animation-delay: 4.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(25) {
|
||||
animation-delay: -5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
html.dark .pre-loader .loader-item {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: black;
|
||||
}
|
||||
.pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@for $i from 0 through 25 {
|
||||
.loader-item:nth-child(#{$i}) {
|
||||
@if $i % 2 == 0 {
|
||||
animation-delay: #{$i * 50 * random(5)}ms;
|
||||
animation-direction: reverse;
|
||||
} @else {
|
||||
animation-delay: #{$i * -50 * random(5)}ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
.loader-item {
|
||||
animation: flickering 0.4s linear alternate infinite;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
html.dark {
|
||||
.pre-loader {
|
||||
.loader-item {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-8
@@ -1,6 +1,7 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
AppLoader,
|
||||
DocsLinks,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
@@ -9,15 +10,10 @@ import {
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MaintenancePage } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Web3Provider } from './web3-provider';
|
||||
|
||||
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
loading: () => <>Loading...</>,
|
||||
});
|
||||
|
||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||
const {
|
||||
error,
|
||||
VEGA_URL,
|
||||
@@ -47,13 +43,13 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
skeleton={<DynamicLoader />}
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not initialize app')} error={error} />
|
||||
}
|
||||
>
|
||||
<NodeGuard
|
||||
skeleton={<DynamicLoader />}
|
||||
skeleton={<AppLoader />}
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Web3Provider>
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
export * from './app-loader';
|
||||
export * from './bootstrapper';
|
||||
export * from './web3-provider';
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Preloader = () => {
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
body{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
html.dark body{
|
||||
background: #000;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className="pre-loader">
|
||||
<Loader />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preloader;
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Bootstrapper } from '../components/bootstrapper';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
@@ -52,6 +52,7 @@ import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { NavHeader } from '../components/navbar/nav-header';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
import { Routes as AppRoutes } from '../lib/links';
|
||||
import { SSRLoader } from './ssr-loader';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -144,14 +145,14 @@ function VegaTradingApp(props: AppProps) {
|
||||
// Prevent HashRouter from being server side rendered as it
|
||||
// relies on presence of document object
|
||||
if (status === 'default') {
|
||||
return <DynamicLoader />;
|
||||
return <SSRLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<AppLoader>
|
||||
<Bootstrapper>
|
||||
<AppBody {...props} />
|
||||
</AppLoader>
|
||||
</Bootstrapper>
|
||||
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
|
||||
</HashRouter>
|
||||
);
|
||||
|
||||
@@ -22,14 +22,17 @@ export default function Document() {
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" content="/favicon.ico" />
|
||||
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
|
||||
{/* scripts */}
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<Html>
|
||||
<body className="bg-white dark:bg-vega-cdark-900 text-default font-alpha">
|
||||
<body
|
||||
// Nextjs will set body to display none until js runs. Because the entire app is client rendered
|
||||
// and delivered via ipfs we override this to show a server side render loading animation until the
|
||||
// js is downloaded and react takes over rendering
|
||||
style={{ display: 'block' }}
|
||||
className="bg-white dark:bg-vega-cdark-900 text-default font-alpha"
|
||||
>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
export const SSRLoader = () => {
|
||||
const randomDelay = () => {
|
||||
return parseFloat((Math.random() * (4 - 1) + 1).toFixed(2));
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<style jsx>{`
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<div style={{ width: 50, height: 50, display: 'flex', flexWrap: 'wrap' }}>
|
||||
{new Array(25).fill(null).map((_, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
animation: 'flickering 0.4s linear alternate infinite',
|
||||
animationDelay: `-${randomDelay()}s`,
|
||||
animationDirection: i % 2 === 0 ? 'reverse' : 'alternate',
|
||||
background: 'black',
|
||||
opacity: Math.random() > 0.5 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,116 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #000;
|
||||
}
|
||||
.pre-loader .pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader .pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
.pre-loader .loader-item {
|
||||
background: #000;
|
||||
}
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(0) {
|
||||
animation-delay: 0ms;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -50ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
animation-delay: -1.4s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
animation-delay: 1.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -0.55s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
animation-delay: 1.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -0.65s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 0.7s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -3.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
html.dark .pre-loader .loader-item {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import { addImportNodeWallets } from './lib/commands/import-node-wallets';
|
||||
import { addVegaWalletTopUpRewardsPool } from './lib/commands/vega-wallet-top-up-rewards-pool';
|
||||
import { addAssociateTokensToVegaWallet } from './lib/commands/associate-tokens-to-vega-wallet';
|
||||
import { addMockChainId } from './lib/commands/mock-chain-id';
|
||||
import { addVegaWalletTopUpNetworkAccount } from './lib/commands/vega-wallet-fund-network-account';
|
||||
|
||||
addGetTestIdcommand();
|
||||
addMockGQLCommand();
|
||||
@@ -52,6 +53,7 @@ addImportNodeWallets();
|
||||
addVegaWalletTopUpRewardsPool();
|
||||
addAssociateTokensToVegaWallet();
|
||||
addMockChainId();
|
||||
addVegaWalletTopUpNetworkAccount();
|
||||
|
||||
export {
|
||||
mockConnectWallet,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { TransferBody } from '@vegaprotocol/wallet';
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
VegaWalletTopUpNetworkAccount(amount: string): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addVegaWalletTopUpNetworkAccount() {
|
||||
Cypress.Commands.add('VegaWalletTopUpNetworkAccount', (amount: string) => {
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
const assetAddress =
|
||||
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b';
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
|
||||
const transactionBody: TransferBody = {
|
||||
transfer: {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
asset: assetAddress,
|
||||
amount: amount + '0'.repeat(18),
|
||||
oneOff: {
|
||||
deliverOn: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
cy.highlight('Topping network account');
|
||||
|
||||
sendVegaTx(vegaPubKey, transactionBody);
|
||||
});
|
||||
}
|
||||
@@ -29,11 +29,6 @@ export function addVegaWalletTopUpRewardsPool() {
|
||||
.then((epochText) => {
|
||||
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
||||
transferEndEpoch = transferStartEpoch + 100;
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log(transferStartEpoch);
|
||||
console.log(transferEndEpoch);
|
||||
/* eslint-enable */
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FeesBreakdown, getAsset, getQuoteName } from '@vegaprotocol/markets';
|
||||
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
@@ -8,7 +8,11 @@ import type { Market } from '@vegaprotocol/markets';
|
||||
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||
|
||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||
import {
|
||||
formatNumberPercentage,
|
||||
formatRange,
|
||||
formatValue,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||
@@ -22,15 +26,23 @@ import {
|
||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import {
|
||||
sumFees,
|
||||
sumFeesDiscounts,
|
||||
useEstimateFees,
|
||||
} from '../../hooks/use-estimate-fees';
|
||||
import { KeyValue } from './key-value';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionChevron,
|
||||
AccordionPanel,
|
||||
Intent,
|
||||
Pill,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { FeesBreakdown } from '../fees-breakdown';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
@@ -50,7 +62,18 @@ export const DealTicketFeeDetails = ({
|
||||
const feeEstimate = useEstimateFees(order, isMarketInAuction);
|
||||
const asset = getAsset(market);
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const totalFees = feeEstimate?.fees && sumFees(feeEstimate?.fees);
|
||||
const feesDiscounts =
|
||||
feeEstimate?.fees && sumFeesDiscounts(feeEstimate?.fees);
|
||||
|
||||
const totalPercentageDiscount =
|
||||
feesDiscounts &&
|
||||
totalFees &&
|
||||
feesDiscounts.total !== '0' &&
|
||||
totalFees !== '0' &&
|
||||
new BigNumber(feesDiscounts.total)
|
||||
.dividedBy(BigNumber.sum(totalFees, feesDiscounts.total))
|
||||
.times(100);
|
||||
return (
|
||||
<KeyValue
|
||||
label={t('Fees')}
|
||||
@@ -59,8 +82,19 @@ export const DealTicketFeeDetails = ({
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
<>
|
||||
{totalPercentageDiscount && (
|
||||
<Pill size="xxs" intent={Intent.Warning} className="mr-1">
|
||||
-{formatNumberPercentage(totalPercentageDiscount, 2)}
|
||||
</Pill>
|
||||
)}
|
||||
{feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(
|
||||
feeEstimate?.totalFeeAmount,
|
||||
assetDecimals,
|
||||
quantum
|
||||
)}`}
|
||||
</>
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface KeyValuePros {
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
formattedValue?: ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ export const KeyValue = ({
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const displayValue = (
|
||||
<>
|
||||
{formattedValue || '-'} {symbol || ''}
|
||||
</>
|
||||
);
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="font-mono ml-auto">
|
||||
{displayValue}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { sumFeesFactors } from '@vegaprotocol/markets';
|
||||
import type { TradeFee, FeeFactors } from '@vegaprotocol/types';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { sumFees, sumFeesDiscounts } from '../../hooks';
|
||||
|
||||
const formatValue = (
|
||||
value: string | number | null | undefined,
|
||||
decimals: number
|
||||
): string =>
|
||||
value && !isNaN(Number(value))
|
||||
? addDecimalsFormatNumber(value, decimals)
|
||||
: '-';
|
||||
|
||||
const FeesBreakdownItem = ({
|
||||
label,
|
||||
factor,
|
||||
value,
|
||||
symbol,
|
||||
decimals,
|
||||
}: {
|
||||
label: string;
|
||||
factor?: BigNumber;
|
||||
value: string;
|
||||
symbol?: string;
|
||||
decimals: number;
|
||||
}) => (
|
||||
<>
|
||||
<dt className="col-span-2">{label}</dt>
|
||||
{factor && (
|
||||
<dd className="text-right col-span-1">
|
||||
{formatNumberPercentage(new BigNumber(factor).times(100), 2)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
{formatValue(value, decimals)} {symbol || ''}
|
||||
</dd>
|
||||
</>
|
||||
);
|
||||
|
||||
export const FeesBreakdown = ({
|
||||
fees,
|
||||
feeFactors,
|
||||
symbol,
|
||||
decimals,
|
||||
}: {
|
||||
fees?: TradeFee;
|
||||
feeFactors?: FeeFactors;
|
||||
symbol?: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
if (!fees) return null;
|
||||
const totalFees = sumFees(fees);
|
||||
const {
|
||||
total: totalDiscount,
|
||||
referral: referralDiscount,
|
||||
volume: volumeDiscount,
|
||||
} = sumFeesDiscounts(fees);
|
||||
if (totalFees === '0') return null;
|
||||
return (
|
||||
<dl className="grid grid-cols-6">
|
||||
<FeesBreakdownItem
|
||||
label={t('Infrastructure fee')}
|
||||
factor={
|
||||
feeFactors?.infrastructureFee
|
||||
? new BigNumber(feeFactors?.infrastructureFee)
|
||||
: undefined
|
||||
}
|
||||
value={fees.infrastructureFee}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
|
||||
<FeesBreakdownItem
|
||||
label={t('Liquidity fee')}
|
||||
factor={
|
||||
feeFactors?.liquidityFee
|
||||
? new BigNumber(feeFactors?.liquidityFee)
|
||||
: undefined
|
||||
}
|
||||
value={fees.liquidityFee}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
|
||||
<FeesBreakdownItem
|
||||
label={t('Maker fee')}
|
||||
factor={
|
||||
feeFactors?.makerFee ? new BigNumber(feeFactors?.makerFee) : undefined
|
||||
}
|
||||
value={fees.makerFee}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
{volumeDiscount && volumeDiscount !== '0' && (
|
||||
<FeesBreakdownItem
|
||||
label={t('Volume discount')}
|
||||
factor={new BigNumber(volumeDiscount).dividedBy(
|
||||
BigNumber.sum(totalFees, totalDiscount)
|
||||
)}
|
||||
value={volumeDiscount}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
)}
|
||||
{referralDiscount && referralDiscount !== '0' && (
|
||||
<FeesBreakdownItem
|
||||
label={t('Referral discount')}
|
||||
factor={new BigNumber(referralDiscount).dividedBy(
|
||||
BigNumber.sum(totalFees, totalDiscount)
|
||||
)}
|
||||
value={referralDiscount}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
)}
|
||||
<FeesBreakdownItem
|
||||
label={t('Total fees')}
|
||||
factor={feeFactors ? sumFeesFactors(feeFactors) : undefined}
|
||||
value={totalFees}
|
||||
symbol={symbol}
|
||||
decimals={decimals}
|
||||
/>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-validation';
|
||||
export * from './fees-breakdown';
|
||||
export * from './trading-mode-tooltip';
|
||||
|
||||
@@ -22,6 +22,12 @@ query EstimateFees(
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
makerFeeReferralDiscount
|
||||
makerFeeVolumeDiscount
|
||||
infrastructureFeeReferralDiscount
|
||||
infrastructureFeeVolumeDiscount
|
||||
liquidityFeeReferralDiscount
|
||||
liquidityFeeVolumeDiscount
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
|
||||
+7
-1
@@ -15,7 +15,7 @@ export type EstimateFeesQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } };
|
||||
export type EstimateFeesQuery = { __typename?: 'Query', estimateFees: { __typename?: 'FeeEstimate', totalFeeAmount: string, fees: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } };
|
||||
|
||||
|
||||
export const EstimateFeesDocument = gql`
|
||||
@@ -34,6 +34,12 @@ export const EstimateFeesDocument = gql`
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
makerFeeReferralDiscount
|
||||
makerFeeVolumeDiscount
|
||||
infrastructureFeeReferralDiscount
|
||||
infrastructureFeeVolumeDiscount
|
||||
liquidityFeeReferralDiscount
|
||||
liquidityFeeVolumeDiscount
|
||||
}
|
||||
totalFeeAmount
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
const data: EstimateFeesQuery = {
|
||||
estimateFees: {
|
||||
totalFeeAmount: '12',
|
||||
totalFeeAmount: '120',
|
||||
fees: {
|
||||
infrastructureFee: '2',
|
||||
liquidityFee: '4',
|
||||
makerFee: '6',
|
||||
infrastructureFee: '20',
|
||||
infrastructureFeeReferralDiscount: '2',
|
||||
infrastructureFeeVolumeDiscount: '4',
|
||||
liquidityFee: '40',
|
||||
liquidityFeeReferralDiscount: '6',
|
||||
liquidityFeeVolumeDiscount: '8',
|
||||
makerFee: '60',
|
||||
makerFeeReferralDiscount: '10',
|
||||
makerFeeVolumeDiscount: '12',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -67,11 +73,17 @@ describe('useEstimateFees', () => {
|
||||
)
|
||||
);
|
||||
expect(result.current).toEqual({
|
||||
totalFeeAmount: '6',
|
||||
totalFeeAmount: '60',
|
||||
fees: {
|
||||
infrastructureFee: '1',
|
||||
liquidityFee: '2',
|
||||
makerFee: '3',
|
||||
infrastructureFee: '10',
|
||||
infrastructureFeeReferralDiscount: '1',
|
||||
infrastructureFeeVolumeDiscount: '2',
|
||||
liquidityFee: '20',
|
||||
liquidityFeeReferralDiscount: '3',
|
||||
liquidityFeeVolumeDiscount: '4',
|
||||
makerFee: '30',
|
||||
makerFeeReferralDiscount: '5',
|
||||
makerFeeVolumeDiscount: '6',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,32 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||
|
||||
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
|
||||
export const sumFeesDiscounts = (
|
||||
fees: EstimateFeesQuery['estimateFees']['fees']
|
||||
) => {
|
||||
const volume = (
|
||||
BigInt(fees.makerFeeVolumeDiscount || '0') +
|
||||
BigInt(fees.infrastructureFeeVolumeDiscount || '0') +
|
||||
BigInt(fees.liquidityFeeVolumeDiscount || '0')
|
||||
).toString();
|
||||
const referral = (
|
||||
BigInt(fees.makerFeeReferralDiscount || '0') +
|
||||
BigInt(fees.infrastructureFeeReferralDiscount || '0') +
|
||||
BigInt(fees.liquidityFeeReferralDiscount || '0')
|
||||
).toString();
|
||||
return {
|
||||
volume,
|
||||
referral,
|
||||
total: (BigInt(volume) + BigInt(referral)).toString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const sumFees = (fees: EstimateFeesQuery['estimateFees']['fees']) =>
|
||||
(
|
||||
BigInt(fees.makerFee || '0') +
|
||||
BigInt(fees.infrastructureFee || '0') +
|
||||
BigInt(fees.liquidityFee || '0')
|
||||
).toString();
|
||||
|
||||
export const useEstimateFees = (
|
||||
order?: OrderSubmissionBody['orderSubmission'],
|
||||
@@ -43,6 +69,26 @@ export const useEstimateFees = (
|
||||
),
|
||||
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
|
||||
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
|
||||
infrastructureFeeReferralDiscount:
|
||||
data.estimateFees.fees.infrastructureFeeReferralDiscount &&
|
||||
divideByTwo(
|
||||
data.estimateFees.fees.infrastructureFeeReferralDiscount
|
||||
),
|
||||
infrastructureFeeVolumeDiscount:
|
||||
data.estimateFees.fees.infrastructureFeeVolumeDiscount &&
|
||||
divideByTwo(data.estimateFees.fees.infrastructureFeeVolumeDiscount),
|
||||
liquidityFeeReferralDiscount:
|
||||
data.estimateFees.fees.liquidityFeeReferralDiscount &&
|
||||
divideByTwo(data.estimateFees.fees.liquidityFeeReferralDiscount),
|
||||
liquidityFeeVolumeDiscount:
|
||||
data.estimateFees.fees.liquidityFeeVolumeDiscount &&
|
||||
divideByTwo(data.estimateFees.fees.liquidityFeeVolumeDiscount),
|
||||
makerFeeReferralDiscount:
|
||||
data.estimateFees.fees.makerFeeReferralDiscount &&
|
||||
divideByTwo(data.estimateFees.fees.makerFeeReferralDiscount),
|
||||
makerFeeVolumeDiscount:
|
||||
data.estimateFees.fees.makerFeeVolumeDiscount &&
|
||||
divideByTwo(data.estimateFees.fees.makerFeeVolumeDiscount),
|
||||
},
|
||||
}
|
||||
: data?.estimateFees;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNodeSwitcherStore } from '../../hooks/use-node-switcher-store';
|
||||
|
||||
export const AppFailure = ({
|
||||
@@ -10,17 +10,15 @@ export const AppFailure = ({
|
||||
error?: string | null;
|
||||
}) => {
|
||||
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
|
||||
const nonIdealWrapperClasses =
|
||||
'h-full min-h-screen flex items-center justify-center';
|
||||
return (
|
||||
<div className={nonIdealWrapperClasses}>
|
||||
<Splash>
|
||||
<div className="text-center">
|
||||
<p className="text-xl mb-4">{title}</p>
|
||||
{error && <p className="text-sm mb-8">{error}</p>}
|
||||
<p className="mb-4 text-xl">{title}</p>
|
||||
{error && <p className="mb-8 text-sm">{error}</p>}
|
||||
<Button onClick={() => setNodeSwitcher(true)}>
|
||||
{t('Change node')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const AppLoader = () => {
|
||||
return (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { AppLoader } from './app-loader';
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './app-failure';
|
||||
export * from './app-loader';
|
||||
export * from './network-loader';
|
||||
export * from './network-switcher';
|
||||
export * from './node-guard';
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
fragment TradeFeeFields on TradeFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
makerFeeReferralDiscount
|
||||
makerFeeVolumeDiscount
|
||||
infrastructureFeeReferralDiscount
|
||||
infrastructureFeeVolumeDiscount
|
||||
liquidityFeeReferralDiscount
|
||||
liquidityFeeVolumeDiscount
|
||||
}
|
||||
|
||||
fragment FillFields on Trade {
|
||||
id
|
||||
market {
|
||||
@@ -16,14 +28,10 @@ fragment FillFields on Trade {
|
||||
id
|
||||
}
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +69,10 @@ fragment FillUpdateFields on TradeUpdate {
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-19
@@ -3,9 +3,11 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type FillFieldsFragment = { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
|
||||
export type TradeFeeFieldsFragment = { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null };
|
||||
|
||||
export type FillEdgeFragment = { __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } };
|
||||
export type FillFieldsFragment = { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } };
|
||||
|
||||
export type FillEdgeFragment = { __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } };
|
||||
|
||||
export type FillsQueryVariables = Types.Exact<{
|
||||
filter?: Types.InputMaybe<Types.TradesFilter>;
|
||||
@@ -13,17 +15,30 @@ export type FillsQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||
|
||||
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
|
||||
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } };
|
||||
|
||||
export type FillsEventSubscriptionVariables = Types.Exact<{
|
||||
filter: Types.TradesSubscriptionFilter;
|
||||
}>;
|
||||
|
||||
|
||||
export type FillsEventSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } }> | null };
|
||||
export type FillsEventSubscription = { __typename?: 'Subscription', tradesStream?: Array<{ __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string, makerFeeReferralDiscount?: string | null, makerFeeVolumeDiscount?: string | null, infrastructureFeeReferralDiscount?: string | null, infrastructureFeeVolumeDiscount?: string | null, liquidityFeeReferralDiscount?: string | null, liquidityFeeVolumeDiscount?: string | null } }> | null };
|
||||
|
||||
export const TradeFeeFieldsFragmentDoc = gql`
|
||||
fragment TradeFeeFields on TradeFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
makerFeeReferralDiscount
|
||||
makerFeeVolumeDiscount
|
||||
infrastructureFeeReferralDiscount
|
||||
infrastructureFeeVolumeDiscount
|
||||
liquidityFeeReferralDiscount
|
||||
liquidityFeeVolumeDiscount
|
||||
}
|
||||
`;
|
||||
export const FillFieldsFragmentDoc = gql`
|
||||
fragment FillFields on Trade {
|
||||
id
|
||||
@@ -43,17 +58,13 @@ export const FillFieldsFragmentDoc = gql`
|
||||
id
|
||||
}
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${TradeFeeFieldsFragmentDoc}`;
|
||||
export const FillEdgeFragmentDoc = gql`
|
||||
fragment FillEdge on TradeEdge {
|
||||
node {
|
||||
@@ -76,17 +87,13 @@ export const FillUpdateFieldsFragmentDoc = gql`
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
...TradeFeeFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${TradeFeeFieldsFragmentDoc}`;
|
||||
export const FillsDocument = gql`
|
||||
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||
trades(filter: $filter, pagination: $pagination) {
|
||||
|
||||
@@ -4,36 +4,38 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
import { FillsTable, getFeesBreakdown } from './fills-table';
|
||||
import {
|
||||
FeesDiscountBreakdownTooltip,
|
||||
FillsTable,
|
||||
getFeesBreakdown,
|
||||
getTotalFeesDiscounts,
|
||||
} from './fills-table';
|
||||
import { generateFill } from './test-helpers';
|
||||
import type { TradeFeeFieldsFragment } from './__generated__/Fills';
|
||||
|
||||
describe('FillsTable', () => {
|
||||
let defaultFill: PartialDeep<Trade>;
|
||||
|
||||
beforeEach(() => {
|
||||
defaultFill = {
|
||||
price: '100',
|
||||
size: '300000',
|
||||
market: {
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'test market',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
settlementAsset: {
|
||||
decimals: 2,
|
||||
symbol: 'BTC',
|
||||
},
|
||||
},
|
||||
const partyId = 'party-id';
|
||||
const defaultFill: PartialDeep<Trade> = {
|
||||
price: '100',
|
||||
size: '300000',
|
||||
market: {
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'test market',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
settlementAsset: {
|
||||
decimals: 2,
|
||||
symbol: 'BTC',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
},
|
||||
},
|
||||
createdAt: new Date('2022-02-02T14:00:00').toISOString(),
|
||||
};
|
||||
describe('FillsTable', () => {
|
||||
it('correct columns are rendered', async () => {
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
|
||||
@@ -47,6 +49,7 @@ describe('FillsTable', () => {
|
||||
'Notional',
|
||||
'Role',
|
||||
'Fee',
|
||||
'Fee Discount',
|
||||
'Date',
|
||||
'', // action column
|
||||
];
|
||||
@@ -55,7 +58,6 @@ describe('FillsTable', () => {
|
||||
});
|
||||
|
||||
it('formats cells correctly for buyer fill', async () => {
|
||||
const partyId = 'party-id';
|
||||
const buyerFill = generateFill({
|
||||
...defaultFill,
|
||||
buyer: {
|
||||
@@ -68,7 +70,9 @@ describe('FillsTable', () => {
|
||||
liquidityFee: '2',
|
||||
},
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
buyerFill.market?.tradableInstrument.instrument.code || '',
|
||||
@@ -77,6 +81,7 @@ describe('FillsTable', () => {
|
||||
'3.00 BTC',
|
||||
'Maker',
|
||||
'2.00 BTC',
|
||||
'0.27 BTC',
|
||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||
'', // action column
|
||||
];
|
||||
@@ -89,7 +94,6 @@ describe('FillsTable', () => {
|
||||
});
|
||||
|
||||
it('should format cells correctly for seller fill', async () => {
|
||||
const partyId = 'party-id';
|
||||
const buyerFill = generateFill({
|
||||
...defaultFill,
|
||||
seller: {
|
||||
@@ -102,7 +106,9 @@ describe('FillsTable', () => {
|
||||
liquidityFee: '1',
|
||||
},
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
@@ -112,6 +118,7 @@ describe('FillsTable', () => {
|
||||
'3.00 BTC',
|
||||
'Taker',
|
||||
'0.03 BTC',
|
||||
'0.27 BTC',
|
||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||
'', // action column
|
||||
];
|
||||
@@ -124,7 +131,6 @@ describe('FillsTable', () => {
|
||||
});
|
||||
|
||||
it('should format cells correctly for side unspecified', async () => {
|
||||
const partyId = 'party-id';
|
||||
const buyerFill = generateFill({
|
||||
...defaultFill,
|
||||
seller: {
|
||||
@@ -137,7 +143,9 @@ describe('FillsTable', () => {
|
||||
liquidityFee: '1',
|
||||
},
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
@@ -147,6 +155,7 @@ describe('FillsTable', () => {
|
||||
'3.00 BTC',
|
||||
'-',
|
||||
'0.03 BTC',
|
||||
'0.27 BTC',
|
||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||
'', // action column
|
||||
];
|
||||
@@ -158,30 +167,31 @@ describe('FillsTable', () => {
|
||||
expect(amountCell).toHaveClass('text-market-red');
|
||||
});
|
||||
|
||||
it('should render correct maker or taker role', async () => {
|
||||
const partyId = 'party-id';
|
||||
it('should render correct taker role', async () => {
|
||||
const takerFill = generateFill({
|
||||
seller: {
|
||||
id: partyId,
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_SELL,
|
||||
});
|
||||
const { rerender } = render(
|
||||
<FillsTable partyId={partyId} rowData={[takerFill]} />
|
||||
);
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||
});
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole('gridcell')
|
||||
.find((c) => c.getAttribute('col-id') === 'aggressor')
|
||||
).toHaveTextContent('Taker');
|
||||
});
|
||||
|
||||
it('should render correct maker role', async () => {
|
||||
const makerFill = generateFill({
|
||||
seller: {
|
||||
id: partyId,
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_BUY,
|
||||
});
|
||||
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
|
||||
render(<FillsTable partyId={partyId} rowData={[makerFill]} />);
|
||||
|
||||
expect(
|
||||
screen
|
||||
@@ -191,22 +201,19 @@ describe('FillsTable', () => {
|
||||
});
|
||||
|
||||
it('should render tooltip over fees', async () => {
|
||||
const partyId = 'party-id';
|
||||
const takerFill = generateFill({
|
||||
seller: {
|
||||
id: partyId,
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_SELL,
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||
});
|
||||
|
||||
const feeCell = screen
|
||||
.getAllByRole('gridcell')
|
||||
.find(
|
||||
(c) =>
|
||||
c.getAttribute('col-id') ===
|
||||
'market.tradableInstrument.instrument.product'
|
||||
);
|
||||
.find((c) => c.getAttribute('col-id') === 'fee');
|
||||
|
||||
expect(feeCell).toBeInTheDocument();
|
||||
await userEvent.hover(feeCell as HTMLElement);
|
||||
@@ -215,8 +222,29 @@ describe('FillsTable', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render tooltip over fees discounts', async () => {
|
||||
const takerFill = generateFill({
|
||||
seller: {
|
||||
id: partyId,
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_SELL,
|
||||
});
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||
});
|
||||
|
||||
const cell = screen
|
||||
.getAllByRole('gridcell')
|
||||
.find((c) => c.getAttribute('col-id') === 'fee-discount');
|
||||
|
||||
expect(cell).toBeInTheDocument();
|
||||
await userEvent.hover(cell as HTMLElement);
|
||||
expect(
|
||||
await screen.findByTestId('fee-discount-breakdown-tooltip')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||
const partyId = 'party-id';
|
||||
const negativeDecimalPositionFill = generateFill({
|
||||
...defaultFill,
|
||||
market: {
|
||||
@@ -235,36 +263,83 @@ describe('FillsTable', () => {
|
||||
.find((c) => c.getAttribute('col-id') === 'size');
|
||||
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFeesBreakdown', () => {
|
||||
it('should return correct fees breakdown for a taker', () => {
|
||||
const fees = {
|
||||
makerFee: '1000',
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
};
|
||||
const expectedBreakdown = {
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
makerFee: '1000',
|
||||
totalFee: '6000',
|
||||
};
|
||||
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
|
||||
describe('FeesDiscountBreakdownTooltip', () => {
|
||||
it('shows all discounts', () => {
|
||||
const data = generateFill({
|
||||
...defaultFill,
|
||||
buyer: {
|
||||
id: partyId,
|
||||
},
|
||||
});
|
||||
|
||||
it('should return correct fees breakdown for a maker', () => {
|
||||
const fees = {
|
||||
makerFee: '1000',
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
};
|
||||
const expectedBreakdown = {
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
makerFee: '-1000',
|
||||
totalFee: '4000',
|
||||
};
|
||||
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
|
||||
const props = {
|
||||
data,
|
||||
partyId,
|
||||
value: data.market,
|
||||
} as Parameters<typeof FeesDiscountBreakdownTooltip>['0'];
|
||||
const { container } = render(<FeesDiscountBreakdownTooltip {...props} />);
|
||||
const dt = container.querySelectorAll('dt');
|
||||
const dd = container.querySelectorAll('dd');
|
||||
const expected = [
|
||||
{ label: 'Infrastructure Fee Referral Discount', value: '0.05 BTC' },
|
||||
{ label: 'Infrastructure Fee Volume Discount', value: '0.06 BTC' },
|
||||
{ label: 'Liquidity Fee Referral Discount', value: '0.01 BTC' },
|
||||
{ label: 'Liquidity Fee Volume Discount', value: '0.02 BTC' },
|
||||
{ label: 'Maker Fee Referral Discount', value: '0.03 BTC' },
|
||||
{ label: 'Maker Fee Volume Discount', value: '0.04 BTC' },
|
||||
];
|
||||
expected.forEach(({ label, value }, i) => {
|
||||
expect(dt[i]).toHaveTextContent(label);
|
||||
expect(dd[i]).toHaveTextContent(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFeesBreakdown', () => {
|
||||
it('should return correct fees breakdown for a taker', () => {
|
||||
const fees = {
|
||||
makerFee: '1000',
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
};
|
||||
const expectedBreakdown = {
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
makerFee: '1000',
|
||||
totalFee: '6000',
|
||||
};
|
||||
expect(getFeesBreakdown('Taker', fees)).toEqual(expectedBreakdown);
|
||||
});
|
||||
|
||||
it('should return correct fees breakdown for a maker', () => {
|
||||
const fees = {
|
||||
makerFee: '1000',
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
};
|
||||
const expectedBreakdown = {
|
||||
infrastructureFee: '2000',
|
||||
liquidityFee: '3000',
|
||||
makerFee: '-1000',
|
||||
totalFee: '4000',
|
||||
};
|
||||
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalFeesDiscounts', () => {
|
||||
it('should return correct total value', () => {
|
||||
const fees = {
|
||||
infrastructureFeeReferralDiscount: '1',
|
||||
infrastructureFeeVolumeDiscount: '2',
|
||||
liquidityFeeReferralDiscount: '3',
|
||||
liquidityFeeVolumeDiscount: '4',
|
||||
makerFeeReferralDiscount: '5',
|
||||
makerFeeVolumeDiscount: '6',
|
||||
};
|
||||
expect(getTotalFeesDiscounts(fees as TradeFeeFieldsFragment)).toEqual(
|
||||
(1 + 2 + 3 + 4 + 5 + 6).toString()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,10 @@ import type {
|
||||
import { forwardRef } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
import type { FillFieldsFragment } from './__generated__/Fills';
|
||||
import type {
|
||||
FillFieldsFragment,
|
||||
TradeFeeFieldsFragment,
|
||||
} from './__generated__/Fills';
|
||||
import { FillActionsDropdown } from './fill-actions-dropdown';
|
||||
import { getAsset } from '@vegaprotocol/markets';
|
||||
|
||||
@@ -87,13 +90,34 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
},
|
||||
{
|
||||
headerName: t('Fee'),
|
||||
field: 'market.tradableInstrument.instrument.product',
|
||||
colId: 'fee',
|
||||
field: 'market',
|
||||
valueFormatter: formatFee(partyId),
|
||||
type: 'rightAligned',
|
||||
tooltipField: 'market.tradableInstrument.instrument.product',
|
||||
tooltipField: 'market',
|
||||
tooltipComponent: FeesBreakdownTooltip,
|
||||
tooltipComponentParams: { partyId },
|
||||
},
|
||||
{
|
||||
headerName: t('Fee Discount'),
|
||||
colId: 'fee-discount',
|
||||
field: 'market',
|
||||
valueFormatter: formatFeeDiscount(partyId),
|
||||
type: 'rightAligned',
|
||||
// return null to disable tooltip if fee discount is 0 or empty
|
||||
tooltipValueGetter: ({ valueFormatted, value }) => {
|
||||
return valueFormatted && /[1-9]/.test(valueFormatted)
|
||||
? valueFormatted
|
||||
: null;
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
valueFormatted,
|
||||
}: VegaICellRendererParams<Trade, 'market'>) =>
|
||||
`${valueFormatted} ${(value && getAsset(value))?.symbol}`,
|
||||
tooltipComponent: FeesDiscountBreakdownTooltip,
|
||||
tooltipComponentParams: { partyId },
|
||||
},
|
||||
{
|
||||
headerName: t('Date'),
|
||||
field: 'createdAt',
|
||||
@@ -214,14 +238,11 @@ const formatRole = (partyId: string) => {
|
||||
|
||||
const formatFee = (partyId: string) => {
|
||||
return ({
|
||||
value,
|
||||
value: market,
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
Trade,
|
||||
'market.tradableInstrument.instrument.product'
|
||||
>) => {
|
||||
if (!value || !data || !data?.market) return '-';
|
||||
const asset = getAsset(data.market);
|
||||
}: VegaValueFormatterParams<Trade, 'market'>) => {
|
||||
if (!market || !data) return '-';
|
||||
const asset = getAsset(market);
|
||||
const { fees: feesObj, role } = getRoleAndFees({ data, partyId });
|
||||
if (!feesObj) return '-';
|
||||
|
||||
@@ -231,6 +252,21 @@ const formatFee = (partyId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
const formatFeeDiscount = (partyId: string) => {
|
||||
return ({
|
||||
value: market,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Trade, 'market'>) => {
|
||||
if (!market || !data) return '-';
|
||||
const asset = getAsset(market);
|
||||
const { fees } = getRoleAndFees({ data, partyId });
|
||||
if (!fees) return '-';
|
||||
|
||||
const total = getTotalFeesDiscounts(fees);
|
||||
return addDecimalsFormatNumber(total, asset.decimals);
|
||||
};
|
||||
};
|
||||
|
||||
export const isEmptyFeeObj = (feeObj: Schema.TradeFee) => {
|
||||
if (!feeObj) return true;
|
||||
return (
|
||||
@@ -251,50 +287,50 @@ export const getRoleAndFees = ({
|
||||
partyId?: string;
|
||||
}) => {
|
||||
let role: Role;
|
||||
let feesObj;
|
||||
let fees;
|
||||
if (data?.buyer.id === partyId) {
|
||||
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||
role = TAKER;
|
||||
feesObj = data?.buyerFee;
|
||||
fees = data?.buyerFee;
|
||||
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||
role = MAKER;
|
||||
feesObj = data?.sellerFee;
|
||||
fees = data?.sellerFee;
|
||||
} else {
|
||||
role = '-';
|
||||
feesObj = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
||||
fees = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
||||
}
|
||||
} else if (data?.seller.id === partyId) {
|
||||
if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||
role = TAKER;
|
||||
feesObj = data?.sellerFee;
|
||||
fees = data?.sellerFee;
|
||||
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||
role = MAKER;
|
||||
feesObj = data?.buyerFee;
|
||||
fees = data?.buyerFee;
|
||||
} else {
|
||||
role = '-';
|
||||
feesObj = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
||||
fees = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
||||
}
|
||||
} else {
|
||||
return { role: '-', feesObj: '-' };
|
||||
return { role: '-', fees: undefined };
|
||||
}
|
||||
return { role, fees: feesObj };
|
||||
return { role, fees };
|
||||
};
|
||||
|
||||
const FeesBreakdownTooltip = ({
|
||||
data,
|
||||
value,
|
||||
value: market,
|
||||
partyId,
|
||||
}: ITooltipParams & { partyId?: string }) => {
|
||||
if (!value?.settlementAsset || !data) {
|
||||
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
|
||||
if (!market || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const asset = value.settlementAsset;
|
||||
const asset = getAsset(market);
|
||||
|
||||
const { role, fees: feesObj } = getRoleAndFees({ data, partyId }) ?? {};
|
||||
if (!feesObj) return null;
|
||||
const { role, fees } = getRoleAndFees({ data, partyId }) ?? {};
|
||||
if (!fees) return null;
|
||||
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
|
||||
getFeesBreakdown(role, feesObj);
|
||||
getFeesBreakdown(role, fees);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -344,14 +380,91 @@ const FeesBreakdownTooltip = ({
|
||||
);
|
||||
};
|
||||
|
||||
const FeesDiscountBreakdownTooltipItem = ({
|
||||
value,
|
||||
label,
|
||||
asset,
|
||||
}: {
|
||||
value?: string | null;
|
||||
label: string;
|
||||
asset: ReturnType<typeof getAsset>;
|
||||
}) =>
|
||||
value ? (
|
||||
<>
|
||||
<dt className="col-span-1">{label}</dt>
|
||||
<dd className="text-right col-span-1">
|
||||
{addDecimalsFormatNumber(value, asset.decimals)} {asset.symbol}
|
||||
</dd>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
export const FeesDiscountBreakdownTooltip = ({
|
||||
data,
|
||||
partyId,
|
||||
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
|
||||
if (!data || !data.market) {
|
||||
return null;
|
||||
}
|
||||
const asset = getAsset(data.market);
|
||||
|
||||
const { fees } = getRoleAndFees({ data, partyId }) ?? {};
|
||||
if (!fees) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="fee-discount-breakdown-tooltip"
|
||||
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
|
||||
>
|
||||
<dl className="grid grid-cols-2 gap-x-1">
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.infrastructureFeeReferralDiscount}
|
||||
label={t('Infrastructure Fee Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.infrastructureFeeVolumeDiscount}
|
||||
label={t('Infrastructure Fee Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.liquidityFeeReferralDiscount}
|
||||
label={t('Liquidity Fee Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.liquidityFeeVolumeDiscount}
|
||||
label={t('Liquidity Fee Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.makerFeeReferralDiscount}
|
||||
label={t('Maker Fee Referral Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
<FeesDiscountBreakdownTooltipItem
|
||||
value={fees.makerFeeVolumeDiscount}
|
||||
label={t('Maker Fee Volume Discount')}
|
||||
asset={asset}
|
||||
/>
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getTotalFeesDiscounts = (fees: TradeFeeFieldsFragment) => {
|
||||
return (
|
||||
BigInt(fees.infrastructureFeeReferralDiscount || '0') +
|
||||
BigInt(fees.infrastructureFeeVolumeDiscount || '0') +
|
||||
BigInt(fees.liquidityFeeReferralDiscount || '0') +
|
||||
BigInt(fees.liquidityFeeVolumeDiscount || '0') +
|
||||
BigInt(fees.makerFeeReferralDiscount || '0') +
|
||||
BigInt(fees.makerFeeVolumeDiscount || '0')
|
||||
).toString();
|
||||
};
|
||||
|
||||
export const getFeesBreakdown = (
|
||||
role: Role,
|
||||
feesObj: {
|
||||
__typename?: 'TradeFee' | undefined;
|
||||
makerFee: string;
|
||||
infrastructureFee: string;
|
||||
liquidityFee: string;
|
||||
}
|
||||
feesObj: TradeFeeFieldsFragment
|
||||
) => {
|
||||
const makerFee =
|
||||
role === MAKER
|
||||
|
||||
@@ -28,12 +28,24 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
|
||||
makerFee: '100',
|
||||
infrastructureFee: '100',
|
||||
liquidityFee: '100',
|
||||
liquidityFeeReferralDiscount: '1',
|
||||
liquidityFeeVolumeDiscount: '2',
|
||||
makerFeeReferralDiscount: '3',
|
||||
makerFeeVolumeDiscount: '4',
|
||||
infrastructureFeeReferralDiscount: '5',
|
||||
infrastructureFeeVolumeDiscount: '6',
|
||||
},
|
||||
sellerFee: {
|
||||
__typename: 'TradeFee',
|
||||
makerFee: '200',
|
||||
infrastructureFee: '200',
|
||||
liquidityFee: '200',
|
||||
liquidityFeeReferralDiscount: '2',
|
||||
liquidityFeeVolumeDiscount: '3',
|
||||
makerFeeReferralDiscount: '4',
|
||||
makerFeeVolumeDiscount: '5',
|
||||
infrastructureFeeReferralDiscount: '6',
|
||||
infrastructureFeeVolumeDiscount: '7',
|
||||
},
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
|
||||
@@ -161,6 +161,50 @@ describe('LedgerExportForm', () => {
|
||||
expect(screen.queryByTestId('download-spinner')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Time zone sentence should be properly displayed', () => {
|
||||
let timeZoneOffset = -120;
|
||||
Date.prototype.getTimezoneOffset = jest.fn(() => timeZoneOffset);
|
||||
|
||||
const { rerender } = render(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/^The downloaded file uses the UTC/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Your time zone is UTC-02:00\.$/)
|
||||
).toBeInTheDocument();
|
||||
|
||||
timeZoneOffset = 270;
|
||||
rerender(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText(/Your time zone is UTC\+04:30\.$/)
|
||||
).toBeInTheDocument();
|
||||
|
||||
timeZoneOffset = 0;
|
||||
rerender(
|
||||
<LedgerExportForm
|
||||
partyId={partyId}
|
||||
vegaUrl={vegaUrl}
|
||||
assets={assetsMock}
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.queryByText(/^The downloaded file uses the UTC/)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDownloadUrl', () => {
|
||||
|
||||
@@ -15,6 +15,16 @@ import { subDays } from 'date-fns';
|
||||
|
||||
const DEFAULT_EXPORT_FILE_NAME = 'ledger_entries.csv';
|
||||
|
||||
const toHoursAndMinutes = (totalMinutes: number) => {
|
||||
const minutes = totalMinutes % 60;
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
return `${hours > 0 ? '+' : '-'}${padTo2Digits(hours)}:${padTo2Digits(
|
||||
minutes
|
||||
)}`;
|
||||
};
|
||||
|
||||
const padTo2Digits = (num: number) => Math.abs(num).toString().padStart(2, '0');
|
||||
|
||||
const getProtoHost = (vegaurl: string) => {
|
||||
const loc = new URL(vegaurl);
|
||||
return `${loc.protocol}//${loc.host}`;
|
||||
@@ -120,6 +130,8 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
|
||||
return (
|
||||
<form onSubmit={startDownload} className="p-4 w-[350px]">
|
||||
<h2 className="mb-4">{t('Export ledger entries')}</h2>
|
||||
@@ -166,6 +178,14 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
{t('Download')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
{offset && (
|
||||
<p className="text-xs text-neutral-400 mt-1">
|
||||
{t(
|
||||
'The downloaded file uses the UTC time zone for all listed times. Your time zone is UTC%s.',
|
||||
[toHoursAndMinutes(offset)]
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { totalFeesPercentage } from '../../market-utils';
|
||||
import type { TradeFee, FeeFactors } from '@vegaprotocol/types';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const FeesCell = ({ feeFactors }: { feeFactors: FeeFactors }) => (
|
||||
<Tooltip description={<FeesBreakdownPercentage feeFactors={feeFactors} />}>
|
||||
<span>{totalFeesPercentage(feeFactors) ?? '-'}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const FeesBreakdownPercentage = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors?: FeeFactors;
|
||||
}) => {
|
||||
if (!feeFactors) return null;
|
||||
return (
|
||||
<dl className="grid grid-cols-2 gap-x-2">
|
||||
<dt>{t('Infrastructure fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Liquidity fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
<dt>{t('Maker fee')}</dt>
|
||||
<dd className="text-right">
|
||||
{formatNumberPercentage(new BigNumber(feeFactors.makerFee).times(100))}
|
||||
</dd>
|
||||
<dt>{t('Total fees')}</dt>
|
||||
<dd className="text-right">{totalFeesPercentage(feeFactors)}</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeesBreakdown = ({
|
||||
fees,
|
||||
feeFactors,
|
||||
symbol,
|
||||
decimals,
|
||||
}: {
|
||||
fees?: TradeFee;
|
||||
feeFactors?: FeeFactors;
|
||||
symbol?: string;
|
||||
decimals: number;
|
||||
}) => {
|
||||
if (!fees) return null;
|
||||
const totalFees = new BigNumber(fees.makerFee)
|
||||
.plus(fees.infrastructureFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.toString();
|
||||
if (totalFees === '0') return null;
|
||||
const formatValue = (value: string | number | null | undefined): string => {
|
||||
return value && !isNaN(Number(value))
|
||||
? addDecimalsFormatNumber(value, decimals)
|
||||
: '-';
|
||||
};
|
||||
return (
|
||||
<dl className="grid grid-cols-6">
|
||||
<dt className="col-span-2">{t('Infrastructure fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right col-span-1">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
{formatValue(fees.infrastructureFee)} {symbol || ''}
|
||||
</dd>
|
||||
<dt className="col-span-2">{t('Liquidity fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right col-span-1">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
{formatValue(fees.liquidityFee)} {symbol || ''}
|
||||
</dd>
|
||||
<dt className="col-span-2">{t('Maker fee')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right col-span-1">
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.makerFee).times(100)
|
||||
)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
{formatValue(fees.makerFee)} {symbol || ''}
|
||||
</dd>
|
||||
<dt className="col-span-2">{t('Total fees')}</dt>
|
||||
{feeFactors && (
|
||||
<dd className="text-right col-span-1">
|
||||
{totalFeesPercentage(feeFactors)}
|
||||
</dd>
|
||||
)}
|
||||
<dd className="text-right col-span-3">
|
||||
{formatValue(totalFees)} {symbol || ''}
|
||||
</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './fees-breakdown';
|
||||
export * from './last-24h-price-change';
|
||||
export * from './last-24h-volume';
|
||||
export * from './market-info';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Fragment, useMemo, useState } from 'react';
|
||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { marketDataProvider } from '../../market-data-provider';
|
||||
import { totalFeesPercentage } from '../../market-utils';
|
||||
import { totalFeesFactorsPercentage } from '../../market-utils';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionChevron,
|
||||
@@ -91,7 +91,7 @@ export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => (
|
||||
makerFee: market.fees.factors.makerFee,
|
||||
infrastructureFee: market.fees.factors.infrastructureFee,
|
||||
liquidityFee: market.fees.factors.liquidityFee,
|
||||
totalFees: totalFeesPercentage(market.fees.factors),
|
||||
totalFees: totalFeesFactorsPercentage(market.fees.factors),
|
||||
}}
|
||||
asPercentage={true}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider';
|
||||
import {
|
||||
calcTradedFactor,
|
||||
filterAndSortMarkets,
|
||||
totalFeesPercentage,
|
||||
totalFeesFactorsPercentage,
|
||||
} from './market-utils';
|
||||
const { MarketState, MarketTradingMode } = Schema;
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('mapDataToMarketList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('totalFees', () => {
|
||||
describe('totalFeesFactorsPercentage', () => {
|
||||
const createFee = (...f: number[]): Market['fees']['factors'] => ({
|
||||
__typename: 'FeeFactors',
|
||||
infrastructureFee: f[0].toString(),
|
||||
@@ -78,7 +78,7 @@ describe('totalFees', () => {
|
||||
{ i: createFee(0.01, 0.056782, 0.003), o: '6.9782%' },
|
||||
{ i: createFee(0.01, 0.056782, 0), o: '6.6782%' },
|
||||
])('adds fees correctly', ({ i, o }) => {
|
||||
expect(totalFeesPercentage(i)).toEqual(o);
|
||||
expect(totalFeesFactorsPercentage(i)).toEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -49,18 +49,17 @@ export const getQuoteName = (market: Partial<Market>) => {
|
||||
throw new Error('Failed to retrieve quoteName. Invalid product type');
|
||||
};
|
||||
|
||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
||||
export const sumFeesFactors = (fees: Market['fees']['factors']) => {
|
||||
return fees
|
||||
? new BigNumber(fees.makerFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.plus(fees.infrastructureFee)
|
||||
.times(100)
|
||||
: undefined;
|
||||
};
|
||||
|
||||
export const totalFeesPercentage = (fees: Market['fees']['factors']) => {
|
||||
const total = fees && totalFees(fees);
|
||||
return total ? formatNumberPercentage(total) : undefined;
|
||||
export const totalFeesFactorsPercentage = (fees: Market['fees']['factors']) => {
|
||||
const total = fees && sumFeesFactors(fees);
|
||||
return total ? formatNumberPercentage(total.times(100)) : undefined;
|
||||
};
|
||||
|
||||
export const filterAndSortMarkets = (markets: MarketMaybeWithData[]) => {
|
||||
|
||||
@@ -123,11 +123,9 @@ export const OrderViewDialog = ({
|
||||
{t('rejection reason')}
|
||||
</div>
|
||||
<div data-testid={`order-rejection-reason-value`}>
|
||||
{
|
||||
Schema.OrderRejectionReasonMapping[
|
||||
order.rejectionReason as Schema.OrderRejectionReason
|
||||
]
|
||||
}
|
||||
{Schema.OrderRejectionReasonMapping[
|
||||
order.rejectionReason as Schema.OrderRejectionReason
|
||||
] || order.rejectionReason}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
|
||||
@@ -30,11 +30,8 @@ export const Loader = ({ size = 'large', forceTheme }: LoaderProps) => {
|
||||
const generate = useMemo(() => pseudoRandom(1), []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col items-center pre-loader-center"
|
||||
data-testid="loader"
|
||||
>
|
||||
<div className={`${wrapperClasses} flex flex-wrap pre-loader-wrapper`}>
|
||||
<div className="flex flex-col items-center" data-testid="loader">
|
||||
<div className={`${wrapperClasses} flex flex-wrap`}>
|
||||
{new Array(items).fill(null).map((_, i) => {
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -235,6 +235,39 @@ interface ProposalUpdateAssetTerms {
|
||||
enactmentTimestamp: number;
|
||||
}
|
||||
|
||||
interface ProposalTransferTerms {
|
||||
newTransfer: {
|
||||
changes: {
|
||||
fractionOfBalance: string;
|
||||
amount: string;
|
||||
sourceType: string;
|
||||
source: string;
|
||||
transferType: string;
|
||||
destinationType: string;
|
||||
destination: string;
|
||||
asset: string;
|
||||
recurring?: {
|
||||
startEpoch: number;
|
||||
endEpoch: number;
|
||||
};
|
||||
oneOff?: {
|
||||
deliverOn: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
closingTimestamp: number;
|
||||
enactmentTimestamp: number;
|
||||
validationTimestamp?: number;
|
||||
}
|
||||
|
||||
interface ProposalCancelTransferTerms {
|
||||
cancelTransfer: {
|
||||
changes: {
|
||||
transferId: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface DataSourceSpecBinding {
|
||||
settlementDataProperty: string;
|
||||
tradingTerminationProperty: string;
|
||||
@@ -342,7 +375,9 @@ export interface ProposalSubmission {
|
||||
| ProposalUpdateMarketTerms
|
||||
| ProposalNetworkParameterTerms
|
||||
| ProposalNewAssetTerms
|
||||
| ProposalUpdateAssetTerms;
|
||||
| ProposalUpdateAssetTerms
|
||||
| ProposalTransferTerms
|
||||
| ProposalCancelTransferTerms;
|
||||
}
|
||||
|
||||
export interface ProposalSubmissionBody {
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "13.3.0",
|
||||
"pennant": "1.13.2",
|
||||
"pennant": "1.13.4",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -9,3 +9,4 @@ When I enter on ledger entries tab in portfolio page
|
||||
- in the form **Must** see inputs for select time period, in which reports will be downloaded (<a name="7007-LEEN-003" href="#7007-LEEN-003">7007-LEEN-003</a>)
|
||||
- default preselected period **Must** be the last 7 days (<a name="7007-LEEN-004" href="#7007-LEEN-004">7007-LEEN-004</a>)
|
||||
- during download a loader component **Must** be visible and all interactive elements in the form **Must** be disabled (<a name="7007-LEEN-005" href="#7007-LEEN-005">7007-LEEN-005</a>)
|
||||
- **Must** see a note about time in file are in UTC and timezone of the user relative to UTC (<a name="7007-LEEN-006" href="#7007-LEEN-006">7007-LEEN-006</a>)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# View my funding payment(s)
|
||||
|
||||
- **Must** be able to view my funding payment from the trading page (<a name="7008-FUND-001" href="#7008-FUND-001">7008-FUND-001</a>)
|
||||
- **Must** be able to view my funding payment from the portfolio page (<a name="7008-FUND-002" href="#7008-FUND-002">7008-FUND-002</a>)
|
||||
|
||||
- **Must** be able to view the following columns (<a name="7008-FUND-003" href="#7008-FUND-003">7008-FUND-003</a>):
|
||||
|
||||
- Market
|
||||
- Amount
|
||||
- Date
|
||||
|
||||
- **Must** The columns 'Amount' must change color and have a + or - suffix depending on being positive/negative (<a name="7008-FUND-004" href="#7008-FUND-004">7008-FUND-004</a>)
|
||||
- **Must** be able to sort each column by asc and dsc (<a name="7008-FUND-005" href="#7008-FUND-005">7008-FUND-005</a>)
|
||||
- **Must** be able to drag and drop column names to re order (<a name="7008-FUND-006" href="#7008-FUND-006">7008-FUND-006</a>)
|
||||
- **Must** be able to remove columns by dragging headers out of the table (<a name="7008-FUND-007" href="#7008-FUND-007">7008-FUND-007</a>)
|
||||
- **Must** be able to resize the width of the columns (<a name="7008-FUND-008" href="#7008-FUND-008">7008-FUND-008</a>)
|
||||
- **Must** retain previous column sizing on refresh (<a name="7008-FUND-009" href="#7008-FUND-009">7008-FUND-009</a>)
|
||||
|
||||
# Chart
|
||||
|
||||
- **Must** see Funding tab on trading page for perp market only (<a name="7008-FUND-010" href="#7008-FUND-010">7008-FUND-010</a>)
|
||||
- **Must** see funding rate on hover (<a name="7008-FUND-011" href="#7008-FUND-011">7008-FUND-011</a>)
|
||||
+7
-5
@@ -69,21 +69,23 @@ The specs no longer exist in this repo and can be found [here](https://github.co
|
||||
## `6000` Markets and analysis
|
||||
|
||||
- `6001-MARK` [Find markets](6001-MARK-find_markets.md)
|
||||
- `6002-MARD` [View market specification](6002-MDET-market-details.md) `TODO`
|
||||
- `6003-ORDB` [Analyze Order book](6003-ORDB-order_book.md) `TODO`
|
||||
- `6002-MARD` [View market specification](6002-MDET-market-details.md)
|
||||
- `6003-ORDB` [Analyze Order book](6003-ORDB-order_book.md)
|
||||
- `6004-PHIS` [Analyze price history](6004-PHIS-price_history.md) `TODO`
|
||||
- `6005-THIS` [Analyze trade history](6005-THIS-trade_history.md) `TODO`
|
||||
- `6005-THIS` [Analyze trade history](6005-THIS-trade_history.md)
|
||||
- `6006-DEPC` [View depth chart](6006-DEPC-chart.md)
|
||||
- `6007-CHAR` [View chart](6007-CHAR-chart.md)
|
||||
|
||||
## `7000` Collateral, Orders, Positions and Fills
|
||||
## `7000` Collateral, Orders, Positions, Fills and Funding
|
||||
|
||||
- `7001-COLL` [View my collateral / accounts](7001-COLL-collateral.md)
|
||||
- `7002-SORD` [Submit an order](7002-SORD-submit_orders.md)
|
||||
- `7003-MORD` [Manage my orders](7003-MORD-manage_orders.md)
|
||||
- `7004-POSI` [View my positions](7004-POSI-positions.md) `TODO`
|
||||
- `7004-POSI` [View my positions](7004-POSI-positions.md)
|
||||
- `7005-FILL` [View my trades/fills](7005-FILL-fills.md)
|
||||
- `7006-FEES` [View my trading fees](7006-FEES-fees.md) `TODO`
|
||||
- `7007-LEEN` [View ledger entries](7006-FEES-fees.md)
|
||||
- `7008-FUND` [View my funding payment](7006-FEES-fees.md)
|
||||
|
||||
## `8000` Understand transactions and blocks
|
||||
|
||||
|
||||
@@ -20500,10 +20500,10 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pennant@1.13.2:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.13.2.tgz#8ae0cf23ecee35a7e8baddaa9477e94006581e3b"
|
||||
integrity sha512-gdzTUfuvR3Jse247+++mUdJJcDrExtoxljZ6TjVjnvOD7Y0Cx7yiA47VKzztcw5JmoEHbMoDeX3ySznAjIpi7Q==
|
||||
pennant@1.13.4:
|
||||
version "1.13.4"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.13.4.tgz#33a5f3413634a2341a7b91c917f023c59ecc44c7"
|
||||
integrity sha512-sqwkUiYHxmS97RY8jToMfgR9ePcEr5PWVQu9BPrhdUIa1Q/NztE36SWM5tVIsPPTx3pPIHYTNEVJm02Ubl8cZQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@d3fc/d3fc-technical-indicator" "^8.0.1"
|
||||
|
||||
Reference in New Issue
Block a user