Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cab087449b | ||
|
|
01842a78b0 | ||
|
|
c27f08c672 | ||
|
|
58cc074538 | ||
|
|
852c9bb8f4 | ||
|
|
efe9b7a0e7 | ||
|
|
695a059dde | ||
|
|
5d7e4d273a | ||
|
|
b137e024c6 | ||
|
|
6651ee52b4 | ||
|
|
0d14722c5d | ||
|
|
4968b537b8 | ||
|
|
5f5c45f3b9 | ||
|
|
6b10df015d | ||
|
|
6d7e24732b | ||
|
|
b33cf04e16 | ||
|
|
66ebb26b0f | ||
|
|
51c88d2fce | ||
|
|
2786a984b0 | ||
|
|
39c53a7326 | ||
|
|
3371e8e2cc | ||
|
|
5843a36851 | ||
|
|
68b4909dff | ||
|
|
da8e382454 | ||
|
|
81ebefc329 | ||
|
|
1ed7d6fda2 | ||
|
|
a2e4f34b0d |
@@ -208,7 +208,7 @@ jobs:
|
|||||||
name: '(CI) console python'
|
name: '(CI) console python'
|
||||||
uses: ./.github/workflows/console-test-run.yml
|
uses: ./.github/workflows/console-test-run.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }} && ${{ contains(fromJSON(needs.build-sources.outputs.projects), 'trading') }}
|
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading')
|
||||||
with:
|
with:
|
||||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
|||||||
import {
|
import {
|
||||||
LiquidityPriceRangeInfoPanel,
|
LiquidityPriceRangeInfoPanel,
|
||||||
LiquiditySLAParametersInfoPanel,
|
LiquiditySLAParametersInfoPanel,
|
||||||
|
MarginScalingFactorsPanel,
|
||||||
PriceMonitoringBoundsInfoPanel,
|
PriceMonitoringBoundsInfoPanel,
|
||||||
SuccessionLineInfoPanel,
|
SuccessionLineInfoPanel,
|
||||||
getDataSourceSpecForSettlementData,
|
getDataSourceSpecForSettlementData,
|
||||||
@@ -17,7 +18,6 @@ import {
|
|||||||
OracleInfoPanel,
|
OracleInfoPanel,
|
||||||
RiskFactorsInfoPanel,
|
RiskFactorsInfoPanel,
|
||||||
RiskModelInfoPanel,
|
RiskModelInfoPanel,
|
||||||
RiskParametersInfoPanel,
|
|
||||||
SettlementAssetInfoPanel,
|
SettlementAssetInfoPanel,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||||
@@ -69,8 +69,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
|||||||
<MetadataInfoPanel market={market} />
|
<MetadataInfoPanel market={market} />
|
||||||
<h2 className={headerClassName}>{t('Risk model')}</h2>
|
<h2 className={headerClassName}>{t('Risk model')}</h2>
|
||||||
<RiskModelInfoPanel market={market} />
|
<RiskModelInfoPanel market={market} />
|
||||||
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
|
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
|
||||||
<RiskParametersInfoPanel market={market} />
|
<MarginScalingFactorsPanel market={market} />
|
||||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||||
<RiskFactorsInfoPanel market={market} />
|
<RiskFactorsInfoPanel market={market} />
|
||||||
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
|
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
|
NX_REFERRALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_GOVERNANCE_TRANSFERS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|
||||||
|
|
||||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
|||||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://etherscan.io
|
|||||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=false
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=false
|
||||||
|
NX_REFERRALS=false
|
||||||
|
NX_VOLUME_DISCOUNTS=false
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
|||||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -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';
|
} from '../../../../governance-e2e/src/support/staking.functions';
|
||||||
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
|
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
|
||||||
import {
|
import {
|
||||||
|
depositAsset,
|
||||||
switchVegaWalletPubKey,
|
switchVegaWalletPubKey,
|
||||||
vegaWalletSetSpecifiedApprovalAmount,
|
vegaWalletSetSpecifiedApprovalAmount,
|
||||||
} from '../../support/wallet-functions';
|
} from '../../support/wallet-functions';
|
||||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||||
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
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 proposalListItem = '[data-testid="proposals-list-item"]';
|
||||||
const participationNotMet = 'token-participation-not-met';
|
const participationNotMet = 'token-participation-not-met';
|
||||||
@@ -51,6 +55,7 @@ const openProposals = 'open-proposals';
|
|||||||
const viewProposalButton = 'view-proposal-btn';
|
const viewProposalButton = 'view-proposal-btn';
|
||||||
const proposalTermsToggle = 'proposal-json-toggle';
|
const proposalTermsToggle = 'proposal-json-toggle';
|
||||||
const marketDataToggle = 'proposal-market-data-toggle';
|
const marketDataToggle = 'proposal-market-data-toggle';
|
||||||
|
const governanceTransferToggle = 'proposal-transfer-details';
|
||||||
const marketProposalType = 'proposal-type';
|
const marketProposalType = 'proposal-type';
|
||||||
|
|
||||||
describe(
|
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 type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||||
import { upgradeProposalsData } from '../fixtures/mocks/network-upgrade';
|
import { upgradeProposalsData } from '../fixtures/mocks/network-upgrade';
|
||||||
import { proposalsData } from '../fixtures/mocks/proposals';
|
import { proposalsData } from '../fixtures/mocks/proposals';
|
||||||
import { nodeData } from '../fixtures/mocks/nodes';
|
import { nodeData } from '../fixtures/mocks/nodes';
|
||||||
|
import { AccountType, GovernanceTransferType } from '@vegaprotocol/types';
|
||||||
|
|
||||||
export function createUpdateNetworkProposalTxBody(): ProposalSubmissionBody {
|
export function createUpdateNetworkProposalTxBody(): ProposalSubmissionBody {
|
||||||
const MIN_CLOSE_SEC = 5;
|
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() {
|
export function mockNetworkUpgradeProposal() {
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export async function depositAsset(
|
|||||||
) {
|
) {
|
||||||
// Approve asset
|
// Approve asset
|
||||||
const faucet = new Token(assetEthAddress, signer);
|
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(
|
cy.wrap(
|
||||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||||
transactionTimeout
|
transactionTimeout
|
||||||
|
|||||||
@@ -35,4 +35,6 @@ NX_SUCCESSOR_MARKETS=true
|
|||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=false
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=false
|
||||||
|
NX_REFERRALS=false
|
||||||
NX_GOVERNANCE_TRANSFERS=false
|
NX_GOVERNANCE_TRANSFERS=false
|
||||||
|
NX_VOLUME_DISCOUNTS=false
|
||||||
|
|||||||
@@ -35,3 +35,5 @@ NX_SUCCESSOR_MARKETS=false
|
|||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -27,3 +27,5 @@ NX_SUCCESSOR_MARKETS=true
|
|||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -26,3 +26,5 @@ NX_SUCCESSOR_MARKETS=false
|
|||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=false
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=false
|
||||||
|
NX_REFERRALS=false
|
||||||
|
NX_VOLUME_DISCOUNTS=false
|
||||||
|
|||||||
@@ -25,3 +25,5 @@ NX_SUCCESSOR_MARKETS=false
|
|||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=false
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=false
|
||||||
|
NX_REFERRALS=false
|
||||||
|
NX_VOLUME_DISCOUNTS=false
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ NX_SUCCESSOR_MARKETS=true
|
|||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
NX_GOVERNANCE_TRANSFERS=true
|
NX_GOVERNANCE_TRANSFERS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -28,3 +28,5 @@ NX_SUCCESSOR_MARKETS=true
|
|||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=true
|
||||||
NX_PRODUCT_PERPETUALS=true
|
NX_PRODUCT_PERPETUALS=true
|
||||||
NX_UPDATE_MARKET_STATE=true
|
NX_UPDATE_MARKET_STATE=true
|
||||||
|
NX_REFERRALS=true
|
||||||
|
NX_VOLUME_DISCOUNTS=true
|
||||||
|
|||||||
@@ -24,3 +24,5 @@ NX_SUCCESSOR_MARKETS=false
|
|||||||
NX_METAMASK_SNAPS=false
|
NX_METAMASK_SNAPS=false
|
||||||
NX_PRODUCT_PERPETUALS=false
|
NX_PRODUCT_PERPETUALS=false
|
||||||
NX_UPDATE_MARKET_STATE=false
|
NX_UPDATE_MARKET_STATE=false
|
||||||
|
NX_REFERRALS=false
|
||||||
|
NX_VOLUME_DISCOUNTS=false
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
process.env.TZ = 'GMT';
|
||||||
export default {
|
export default {
|
||||||
displayName: 'governance',
|
displayName: 'governance',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
|
|||||||
@@ -709,6 +709,8 @@
|
|||||||
"NewMarketProposal": "New market proposal",
|
"NewMarketProposal": "New market proposal",
|
||||||
"UpdateMarketProposal": "Update market proposal",
|
"UpdateMarketProposal": "Update market proposal",
|
||||||
"UpdateMarketStateProposal": "Update market state proposal",
|
"UpdateMarketStateProposal": "Update market state proposal",
|
||||||
|
"UpdateReferralProgramProposal": "Update referral program proposal",
|
||||||
|
"UpdateVolumeDiscountProgramProposal": "Update volume discount program proposal",
|
||||||
"MarketChange": "Market change",
|
"MarketChange": "Market change",
|
||||||
"MarketStateChange": "Market state change",
|
"MarketStateChange": "Market state change",
|
||||||
"MarketDetails": "Market details",
|
"MarketDetails": "Market details",
|
||||||
@@ -732,6 +734,8 @@
|
|||||||
"NewMarketSpotProduct": "New market - spot",
|
"NewMarketSpotProduct": "New market - spot",
|
||||||
"UpdateMarket": "Update market",
|
"UpdateMarket": "Update market",
|
||||||
"UpdateMarketState": "Update market state",
|
"UpdateMarketState": "Update market state",
|
||||||
|
"UpdateReferralProgram": "Update referral program",
|
||||||
|
"UpdateVolumeDiscountProgram": "Update volume discount program",
|
||||||
"NewAsset": "New asset",
|
"NewAsset": "New asset",
|
||||||
"UpdateAsset": "Update asset",
|
"UpdateAsset": "Update asset",
|
||||||
"AssetID": "Asset ID",
|
"AssetID": "Asset ID",
|
||||||
@@ -890,5 +894,25 @@
|
|||||||
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
|
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
|
||||||
"HowToProposeRawStep3": "3. Submit on-chain below",
|
"HowToProposeRawStep3": "3. Submit on-chain below",
|
||||||
"proposalTransferDetails": "New governance transfer details",
|
"proposalTransferDetails": "New governance transfer details",
|
||||||
"proposalCancelTransferDetails": "Cancel governance transfer details"
|
"proposalCancelTransferDetails": "Cancel governance transfer details",
|
||||||
|
"BenefitTiers": "Benefit tiers",
|
||||||
|
"BenefitTierMinimumEpochs": "Minimum epochs",
|
||||||
|
"BenefitTierMinimumEpochsDescription": "The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit",
|
||||||
|
"BenefitTierMinimumRunningNotionalTakerVolume": "Minimum running notional taker volume",
|
||||||
|
"BenefitTierMinimumRunningNotionalTakerVolumeDescription": "The minimum running notional for the given benefit tier",
|
||||||
|
"BenefitTierReferralDiscountFactor": "Referral discount factor",
|
||||||
|
"BenefitTierReferralDiscountFactorDescription": "The proportion of the referee's taker fees to be discounted",
|
||||||
|
"BenefitTierReferralRewardFactor": "Referral reward factor",
|
||||||
|
"BenefitTierReferralRewardFactorDescription": "The proportion of the referee's taker fees to be rewarded to the referrer",
|
||||||
|
"StakingTiers": "Staking tiers",
|
||||||
|
"StakingTierMinimumStakedTokens": "Minimum staked tokens",
|
||||||
|
"StakingTierMinimumStakedTokensDescription": "Required number of governance tokens ($VEGA) a referrer must have staked to receive the multiplier",
|
||||||
|
"StakingTierReferralRewardMultiplier": "Referral reward multiplier",
|
||||||
|
"StakingTierReferralRewardMultiplierDescription": "Multiplier applied to the referral reward factor when calculating referral rewards due to the referrer",
|
||||||
|
"WindowLength": "Window length",
|
||||||
|
"WindowLengthDescription": "Number of epochs over which to evaluate a referral set's running volume",
|
||||||
|
"EndOfProgramTimestamp": "End of program",
|
||||||
|
"EndOfProgramTimestampDescription": "Time after which when the current epoch ends, the programs will end and benefits will be disabled.",
|
||||||
|
"BenefitTierVolumeDiscountFactor": "Volume discount factor",
|
||||||
|
"BenefitTierVolumeDiscountFactorDescription": "Discount given to those in this benefit tier"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
|||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
|
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+10
@@ -98,6 +98,16 @@ export const ProposalHeader = ({
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'UpdateReferralProgram': {
|
||||||
|
proposalType = 'UpdateReferralProgram';
|
||||||
|
fallbackTitle = t('UpdateReferralProgramProposal');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'UpdateVolumeDiscountProgram': {
|
||||||
|
proposalType = 'UpdateVolumeDiscountProgram';
|
||||||
|
fallbackTitle = t('UpdateVolumeDiscountProgramProposal');
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'NewAsset': {
|
case 'NewAsset': {
|
||||||
proposalType = 'NewAsset';
|
proposalType = 'NewAsset';
|
||||||
fallbackTitle = t('NewAssetProposal');
|
fallbackTitle = t('NewAssetProposal');
|
||||||
|
|||||||
+5
-3
@@ -12,12 +12,12 @@ import {
|
|||||||
PriceMonitoringBoundsInfoPanel,
|
PriceMonitoringBoundsInfoPanel,
|
||||||
RiskFactorsInfoPanel,
|
RiskFactorsInfoPanel,
|
||||||
RiskModelInfoPanel,
|
RiskModelInfoPanel,
|
||||||
RiskParametersInfoPanel,
|
|
||||||
SettlementAssetInfoPanel,
|
SettlementAssetInfoPanel,
|
||||||
getDataSourceSpecForSettlementSchedule,
|
getDataSourceSpecForSettlementSchedule,
|
||||||
getDataSourceSpecForSettlementData,
|
getDataSourceSpecForSettlementData,
|
||||||
getDataSourceSpecForTradingTermination,
|
getDataSourceSpecForTradingTermination,
|
||||||
getSigners,
|
getSigners,
|
||||||
|
MarginScalingFactorsPanel,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -219,8 +219,10 @@ export const ProposalMarketData = ({
|
|||||||
parentMarket={parentMarketData}
|
parentMarket={parentMarketData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h2 className={marketDataHeaderStyles}>{t('Risk parameters')}</h2>
|
<h2 className={marketDataHeaderStyles}>
|
||||||
<RiskParametersInfoPanel
|
{t('Margin scaling factors')}
|
||||||
|
</h2>
|
||||||
|
<MarginScalingFactorsPanel
|
||||||
market={marketData}
|
market={marketData}
|
||||||
parentMarket={parentMarketData}
|
parentMarket={parentMarketData}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from './proposal-referral-program-details';
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import {
|
||||||
|
formatMinimumRunningNotionalTakerVolume,
|
||||||
|
formatReferralDiscountFactor,
|
||||||
|
formatReferralRewardFactor,
|
||||||
|
formatMinimumStakedTokens,
|
||||||
|
formatReferralRewardMultiplier,
|
||||||
|
ProposalReferralProgramDetails,
|
||||||
|
} from './proposal-referral-program-details';
|
||||||
|
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||||
|
|
||||||
|
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
useAppState: () => ({
|
||||||
|
appState: {
|
||||||
|
decimals: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProposalReferralProgramDetails helper functions', () => {
|
||||||
|
it('should format minimum running notional taker volume correctly', () => {
|
||||||
|
const input = '1000';
|
||||||
|
const formatted = formatMinimumRunningNotionalTakerVolume(input);
|
||||||
|
expect(formatted).toBe('1,000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format referral discount factor correctly', () => {
|
||||||
|
const input = '0.05';
|
||||||
|
const formatted = formatReferralDiscountFactor(input);
|
||||||
|
expect(formatted).toBe('5.00%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format referral reward factor correctly', () => {
|
||||||
|
const input = '0.1';
|
||||||
|
const formatted = formatReferralRewardFactor(input);
|
||||||
|
expect(formatted).toBe('10.00%');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format minimum staked tokens correctly', () => {
|
||||||
|
const input = '15';
|
||||||
|
const decimals = 18;
|
||||||
|
const formatted = formatMinimumStakedTokens(input, decimals);
|
||||||
|
expect(formatted).toBe('0.000000000000000015');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format referral reward multiplier correctly', () => {
|
||||||
|
const input = '3';
|
||||||
|
const formatted = formatReferralRewardMultiplier(input);
|
||||||
|
expect(formatted).toBe('3x');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockReferralProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateReferralProgram',
|
||||||
|
benefitTiers: [
|
||||||
|
{
|
||||||
|
minimumEpochs: 6,
|
||||||
|
minimumRunningNotionalTakerVolume: '10000',
|
||||||
|
referralDiscountFactor: '0.001',
|
||||||
|
referralRewardFactor: '0.001',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumEpochs: 24,
|
||||||
|
minimumRunningNotionalTakerVolume: '500000',
|
||||||
|
referralDiscountFactor: '0.005',
|
||||||
|
referralRewardFactor: '0.005',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumEpochs: 48,
|
||||||
|
minimumRunningNotionalTakerVolume: '1000000',
|
||||||
|
referralDiscountFactor: '0.01',
|
||||||
|
referralRewardFactor: '0.01',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
endOfProgram: '2026-10-03T10:34:34Z',
|
||||||
|
windowLength: 3,
|
||||||
|
stakingTiers: [
|
||||||
|
{
|
||||||
|
minimumStakedTokens: '1',
|
||||||
|
referralRewardMultiplier: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumStakedTokens: '2',
|
||||||
|
referralRewardMultiplier: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumStakedTokens: '5',
|
||||||
|
referralRewardMultiplier: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ProposalReferralProgramDetails />', () => {
|
||||||
|
it('should not render if proposal is null', () => {
|
||||||
|
render(<ProposalReferralProgramDetails proposal={null} />);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-referral-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render if __typename is not UpdateReferralProgram', () => {
|
||||||
|
const updateMarketProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(<ProposalReferralProgramDetails proposal={updateMarketProposal} />);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-referral-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render if there are no relevant fields', () => {
|
||||||
|
const incompleteProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateReferralProgram',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ProposalReferralProgramDetails proposal={incompleteProposal} />);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-referral-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render relevant fields if present', () => {
|
||||||
|
render(<ProposalReferralProgramDetails proposal={mockReferralProposal} />);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-referral-program-window-length')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-referral-program-end-of-program-timestamp')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-referral-program-benefit-tiers')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-referral-program-benefit-tiers')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import {
|
||||||
|
KeyValueTable,
|
||||||
|
KeyValueTableRow,
|
||||||
|
RoundedWrapper,
|
||||||
|
Tooltip,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { formatNumber } from '../../../../lib/format-number';
|
||||||
|
import {
|
||||||
|
formatDateWithLocalTimezone,
|
||||||
|
formatNumberPercentage,
|
||||||
|
toBigNum,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||||
|
|
||||||
|
interface ProposalReferralProgramDetailsProps {
|
||||||
|
proposal: ProposalQuery['proposal'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatEndOfProgramTimestamp = (value: string) => {
|
||||||
|
return formatDateWithLocalTimezone(new Date(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatMinimumRunningNotionalTakerVolume = (value: string) => {
|
||||||
|
return formatNumber(toBigNum(value, 0), 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatReferralDiscountFactor = (value: string) => {
|
||||||
|
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatReferralRewardFactor = (value: string) => {
|
||||||
|
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatMinimumStakedTokens = (value: string, decimals: number) => {
|
||||||
|
return formatNumber(toBigNum(value, decimals));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatReferralRewardMultiplier = (value: string) => {
|
||||||
|
return `${value}x`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProposalReferralProgramDetails = ({
|
||||||
|
proposal,
|
||||||
|
}: ProposalReferralProgramDetailsProps) => {
|
||||||
|
const {
|
||||||
|
appState: { decimals },
|
||||||
|
} = useAppState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
if (proposal?.terms?.change?.__typename !== 'UpdateReferralProgram') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||||
|
const stakingTiers = proposal?.terms?.change?.stakingTiers;
|
||||||
|
const windowLength = proposal?.terms?.change?.windowLength;
|
||||||
|
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgram;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!benefitTiers &&
|
||||||
|
!stakingTiers &&
|
||||||
|
!windowLength &&
|
||||||
|
!endOfProgramTimestamp
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="proposal-referral-program-details">
|
||||||
|
<RoundedWrapper paddingBottom={true}>
|
||||||
|
{windowLength && (
|
||||||
|
<div data-testid="proposal-referral-program-window-length">
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip description={t('WindowLengthDescription')}>
|
||||||
|
<span>{t('WindowLength')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{windowLength}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{endOfProgramTimestamp && (
|
||||||
|
<div
|
||||||
|
className="mb-6"
|
||||||
|
data-testid="proposal-referral-program-end-of-program-timestamp"
|
||||||
|
>
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip description={t('EndOfProgramTimestampDescription')}>
|
||||||
|
<span>{t('EndOfProgramTimestamp')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatEndOfProgramTimestamp(endOfProgramTimestamp)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{benefitTiers && (
|
||||||
|
<div
|
||||||
|
className="mb-6"
|
||||||
|
data-testid="proposal-referral-program-benefit-tiers"
|
||||||
|
>
|
||||||
|
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||||
|
{t('BenefitTiers')}
|
||||||
|
</h3>
|
||||||
|
<KeyValueTable>
|
||||||
|
{benefitTiers
|
||||||
|
.sort((a, b) => a.minimumEpochs - b.minimumEpochs)
|
||||||
|
.map((benefitTier, index) => (
|
||||||
|
<div className="mb-4" key={index}>
|
||||||
|
<h4 className="font-semibold uppercase">
|
||||||
|
Tier {index + 1}
|
||||||
|
</h4>
|
||||||
|
{benefitTier.minimumEpochs && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t('BenefitTierMinimumEpochsDescription')}
|
||||||
|
>
|
||||||
|
<span>{t('BenefitTierMinimumEpochs')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{benefitTier.minimumEpochs}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{benefitTier.minimumRunningNotionalTakerVolume && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'BenefitTierMinimumRunningNotionalTakerVolumeDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t('BenefitTierMinimumRunningNotionalTakerVolume')}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatMinimumRunningNotionalTakerVolume(
|
||||||
|
benefitTier.minimumRunningNotionalTakerVolume
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{benefitTier.referralDiscountFactor && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'BenefitTierReferralDiscountFactorDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('BenefitTierReferralDiscountFactor')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatReferralDiscountFactor(
|
||||||
|
benefitTier.referralDiscountFactor
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{benefitTier.referralRewardFactor && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'BenefitTierReferralRewardFactorDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('BenefitTierReferralRewardFactor')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatReferralRewardFactor(
|
||||||
|
benefitTier.referralRewardFactor
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{stakingTiers && (
|
||||||
|
<div data-testid="proposal-referral-program-staking-tiers">
|
||||||
|
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||||
|
{t('StakingTiers')}
|
||||||
|
</h3>
|
||||||
|
<KeyValueTable>
|
||||||
|
{stakingTiers
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
Number(a.minimumStakedTokens) -
|
||||||
|
Number(b.minimumStakedTokens)
|
||||||
|
)
|
||||||
|
.map((stakingTier, index) => (
|
||||||
|
<div className="mb-4" key={index}>
|
||||||
|
{stakingTier.referralRewardMultiplier && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'StakingTierReferralRewardMultiplierDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t('StakingTierReferralRewardMultiplier')}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatReferralRewardMultiplier(
|
||||||
|
stakingTier.referralRewardMultiplier
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{stakingTier.minimumStakedTokens && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'StakingTierMinimumStakedTokensFactorDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('StakingTierMinimumStakedTokens')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatMinimumStakedTokens(
|
||||||
|
stakingTier.minimumStakedTokens,
|
||||||
|
decimals
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</RoundedWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from './proposal-volume-discount-program-details';
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { ProposalVolumeDiscountProgramDetails } from './proposal-volume-discount-program-details';
|
||||||
|
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||||
|
|
||||||
|
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
useAppState: () => ({
|
||||||
|
appState: {
|
||||||
|
decimals: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockReferralProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateVolumeDiscountProgram',
|
||||||
|
benefitTiers: [
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '10000',
|
||||||
|
volumeDiscountFactor: '0.05',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '50000',
|
||||||
|
volumeDiscountFactor: '0.1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '100000',
|
||||||
|
volumeDiscountFactor: '0.15',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '250000',
|
||||||
|
volumeDiscountFactor: '0.2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '500000',
|
||||||
|
volumeDiscountFactor: '0.25',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '1000000',
|
||||||
|
volumeDiscountFactor: '0.3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '1500000',
|
||||||
|
volumeDiscountFactor: '0.35',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
minimumRunningNotionalTakerVolume: '2000000',
|
||||||
|
volumeDiscountFactor: '0.4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
endOfProgramTimestamp: '1970-01-01T00:00:01.791568493Z',
|
||||||
|
windowLength: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProposalVolumeDiscountProgramDetails', () => {
|
||||||
|
it('should not render if proposal is null', () => {
|
||||||
|
render(<ProposalVolumeDiscountProgramDetails proposal={null} />);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render if __typename is not UpdateVolumeDiscountProgram', () => {
|
||||||
|
const updateMarketProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(
|
||||||
|
<ProposalVolumeDiscountProgramDetails proposal={updateMarketProposal} />
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render if there are no relevant fields', () => {
|
||||||
|
const incompleteProposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateVolumeDiscountProgram',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ProposalVolumeDiscountProgramDetails proposal={incompleteProposal} />
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('proposal-volume-discount-program-details')
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render relevant fields if present', () => {
|
||||||
|
render(
|
||||||
|
<ProposalVolumeDiscountProgramDetails proposal={mockReferralProposal} />
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-volume-discount-program-window-length')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId(
|
||||||
|
'proposal-volume-discount-program-end-of-program-timestamp'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('proposal-volume-discount-program-benefit-tiers')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import {
|
||||||
|
KeyValueTable,
|
||||||
|
KeyValueTableRow,
|
||||||
|
RoundedWrapper,
|
||||||
|
Tooltip,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
formatEndOfProgramTimestamp,
|
||||||
|
formatMinimumRunningNotionalTakerVolume,
|
||||||
|
} from '../proposal-referral-program-details';
|
||||||
|
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
interface ProposalReferralProgramDetailsProps {
|
||||||
|
proposal: ProposalQuery['proposal'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatVolumeDiscountFactor = (value: string) => {
|
||||||
|
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProposalVolumeDiscountProgramDetails = ({
|
||||||
|
proposal,
|
||||||
|
}: ProposalReferralProgramDetailsProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
if (proposal?.terms?.change?.__typename !== 'UpdateVolumeDiscountProgram') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const benefitTiers = proposal?.terms?.change?.benefitTiers;
|
||||||
|
const windowLength = proposal?.terms?.change?.windowLength;
|
||||||
|
const endOfProgramTimestamp = proposal?.terms?.change?.endOfProgramTimestamp;
|
||||||
|
|
||||||
|
if (!benefitTiers && !windowLength && !endOfProgramTimestamp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="proposal-volume-discount-program-details">
|
||||||
|
<RoundedWrapper paddingBottom={true}>
|
||||||
|
{windowLength && (
|
||||||
|
<div data-testid="proposal-volume-discount-program-window-length">
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip description={t('WindowLengthDescription')}>
|
||||||
|
<span>{t('WindowLength')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{windowLength}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{endOfProgramTimestamp && (
|
||||||
|
<div
|
||||||
|
className="mb-6"
|
||||||
|
data-testid="proposal-volume-discount-program-end-of-program-timestamp"
|
||||||
|
>
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip description={t('EndOfProgramTimestampDescription')}>
|
||||||
|
<span>{t('EndOfProgramTimestamp')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatEndOfProgramTimestamp(endOfProgramTimestamp)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{benefitTiers && (
|
||||||
|
<div
|
||||||
|
className="mb-6"
|
||||||
|
data-testid="proposal-volume-discount-program-benefit-tiers"
|
||||||
|
>
|
||||||
|
<h3 className="mb-3 uppercase font-semibold text-lg">
|
||||||
|
{t('BenefitTiers')}
|
||||||
|
</h3>
|
||||||
|
<KeyValueTable>
|
||||||
|
{benefitTiers
|
||||||
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
Number(a.minimumRunningNotionalTakerVolume) -
|
||||||
|
Number(b.minimumRunningNotionalTakerVolume)
|
||||||
|
)
|
||||||
|
.map((benefitTier, index) => (
|
||||||
|
<div className="mb-4" key={index}>
|
||||||
|
<h4 className="font-semibold uppercase">
|
||||||
|
Tier {index + 1}
|
||||||
|
</h4>
|
||||||
|
{benefitTier.minimumRunningNotionalTakerVolume && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'BenefitTierMinimumRunningNotionalTakerVolumeDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t('BenefitTierMinimumRunningNotionalTakerVolume')}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatMinimumRunningNotionalTakerVolume(
|
||||||
|
benefitTier.minimumRunningNotionalTakerVolume
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{benefitTier.volumeDiscountFactor && (
|
||||||
|
<KeyValueTableRow>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'BenefitTierVolumeDiscountFactorDescription'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('BenefitTierVolumeDiscountFactor')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
{formatVolumeDiscountFactor(
|
||||||
|
benefitTier.volumeDiscountFactor
|
||||||
|
)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</KeyValueTable>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</RoundedWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,6 +6,8 @@ import { ProposalDescription } from '../proposal-description';
|
|||||||
import { ProposalChangeTable } from '../proposal-change-table';
|
import { ProposalChangeTable } from '../proposal-change-table';
|
||||||
import { ProposalJson } from '../proposal-json';
|
import { ProposalJson } from '../proposal-json';
|
||||||
import { ProposalAssetDetails } from '../proposal-asset-details';
|
import { ProposalAssetDetails } from '../proposal-asset-details';
|
||||||
|
import { ProposalReferralProgramDetails } from '../proposal-referral-program-details';
|
||||||
|
import { ProposalVolumeDiscountProgramDetails } from '../proposal-volume-discount-program-details';
|
||||||
import { UserVote } from '../vote-details';
|
import { UserVote } from '../vote-details';
|
||||||
import { ListAsset } from '../list-asset';
|
import { ListAsset } from '../list-asset';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
@@ -113,6 +115,15 @@ export const Proposal = ({
|
|||||||
// TODO: check minVoterBalance for 'CancelTransfer'
|
// TODO: check minVoterBalance for 'CancelTransfer'
|
||||||
minVoterBalance =
|
minVoterBalance =
|
||||||
networkParams.governance_proposal_freeform_minVoterBalance;
|
networkParams.governance_proposal_freeform_minVoterBalance;
|
||||||
|
break;
|
||||||
|
case 'UpdateReferralProgram':
|
||||||
|
minVoterBalance =
|
||||||
|
networkParams.governance_proposal_referralProgram_minVoterBalance;
|
||||||
|
break;
|
||||||
|
case 'UpdateVolumeDiscountProgram':
|
||||||
|
minVoterBalance =
|
||||||
|
networkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +231,18 @@ export const Proposal = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{proposal.terms.change.__typename === 'UpdateReferralProgram' && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalReferralProgramDetails proposal={proposal} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{proposal.terms.change.__typename === 'UpdateVolumeDiscountProgram' && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<ProposalVolumeDiscountProgramDetails proposal={proposal} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{governanceTransferDetails}
|
{governanceTransferDetails}
|
||||||
|
|
||||||
<div className="mb-10">
|
<div className="mb-10">
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export const useProposalNetworkParams = ({
|
|||||||
NetworkParams.governance_proposal_market_requiredMajority,
|
NetworkParams.governance_proposal_market_requiredMajority,
|
||||||
NetworkParams.governance_proposal_market_requiredParticipation,
|
NetworkParams.governance_proposal_market_requiredParticipation,
|
||||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||||
|
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||||
|
NetworkParams.governance_proposal_referralProgram_requiredParticipation,
|
||||||
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
||||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||||
NetworkParams.governance_proposal_asset_requiredParticipation,
|
NetworkParams.governance_proposal_asset_requiredParticipation,
|
||||||
@@ -26,6 +28,8 @@ export const useProposalNetworkParams = ({
|
|||||||
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||||
|
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||||
|
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fallback = {
|
const fallback = {
|
||||||
@@ -91,6 +95,22 @@ export const useProposalNetworkParams = ({
|
|||||||
params.governance_proposal_freeform_requiredParticipation
|
params.governance_proposal_freeform_requiredParticipation
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
case 'UpdateReferralProgram':
|
||||||
|
return {
|
||||||
|
requiredMajority:
|
||||||
|
params.governance_proposal_referralProgram_requiredMajority,
|
||||||
|
requiredParticipation: new BigNumber(
|
||||||
|
params.governance_proposal_referralProgram_requiredParticipation
|
||||||
|
),
|
||||||
|
};
|
||||||
|
case 'UpdateVolumeDiscountProgram':
|
||||||
|
return {
|
||||||
|
requiredMajority:
|
||||||
|
params.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||||
|
requiredParticipation: new BigNumber(
|
||||||
|
params.governance_proposal_VolumeDiscountProgram_requiredParticipation
|
||||||
|
),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,48 @@ fragment UpdateMarketState on Proposal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment UpdateReferralProgram on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateReferralProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumEpochs
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
referralDiscountFactor
|
||||||
|
referralRewardFactor
|
||||||
|
}
|
||||||
|
endOfProgram: endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
stakingTiers {
|
||||||
|
minimumStakedTokens
|
||||||
|
referralRewardMultiplier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment UpdateVolumeDiscountProgram on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateVolumeDiscountProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
volumeDiscountFactor
|
||||||
|
}
|
||||||
|
endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query Proposal(
|
query Proposal(
|
||||||
$proposalId: ID!
|
$proposalId: ID!
|
||||||
$includeNewMarketProductField: Boolean!
|
$includeNewMarketProductField: Boolean!
|
||||||
$includeUpdateMarketState: Boolean!
|
$includeUpdateMarketState: Boolean!
|
||||||
|
$includeUpdateReferralProgram: Boolean!
|
||||||
|
$includeUpdateVolumeDiscountProgram: Boolean!
|
||||||
) {
|
) {
|
||||||
proposal(id: $proposalId) {
|
proposal(id: $proposalId) {
|
||||||
id
|
id
|
||||||
@@ -64,6 +102,9 @@ query Proposal(
|
|||||||
errorDetails
|
errorDetails
|
||||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||||
|
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||||
|
...UpdateVolumeDiscountProgram
|
||||||
|
@include(if: $includeUpdateVolumeDiscountProgram)
|
||||||
terms {
|
terms {
|
||||||
closingDatetime
|
closingDatetime
|
||||||
enactmentDatetime
|
enactmentDatetime
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -36,6 +36,8 @@ export const ProposalContainer = () => {
|
|||||||
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
|
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
|
||||||
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
|
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
|
||||||
NetworkParams.governance_proposal_freeform_minVoterBalance,
|
NetworkParams.governance_proposal_freeform_minVoterBalance,
|
||||||
|
NetworkParams.governance_proposal_referralProgram_minVoterBalance,
|
||||||
|
NetworkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance,
|
||||||
NetworkParams.spam_protection_voting_min_tokens,
|
NetworkParams.spam_protection_voting_min_tokens,
|
||||||
NetworkParams.governance_proposal_market_requiredMajority,
|
NetworkParams.governance_proposal_market_requiredMajority,
|
||||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||||
@@ -44,6 +46,8 @@ export const ProposalContainer = () => {
|
|||||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||||
|
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||||
|
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -57,6 +61,8 @@ export const ProposalContainer = () => {
|
|||||||
proposalId: params.proposalId || '',
|
proposalId: params.proposalId || '',
|
||||||
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
|
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||||
|
includeUpdateVolumeDiscountProgram: !!FLAGS.VOLUME_DISCOUNTS,
|
||||||
},
|
},
|
||||||
skip: !params.proposalId,
|
skip: !params.proposalId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,42 @@ fragment UpdateMarketStates on Proposal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment UpdateReferralPrograms on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateReferralProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumEpochs
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
referralDiscountFactor
|
||||||
|
referralRewardFactor
|
||||||
|
}
|
||||||
|
endOfProgram: endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
stakingTiers {
|
||||||
|
minimumStakedTokens
|
||||||
|
referralRewardMultiplier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment UpdateVolumeDiscountPrograms on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateVolumeDiscountProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
volumeDiscountFactor
|
||||||
|
}
|
||||||
|
endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fragment ProposalFields on Proposal {
|
fragment ProposalFields on Proposal {
|
||||||
id
|
id
|
||||||
rationale {
|
rationale {
|
||||||
@@ -127,6 +163,8 @@ fragment ProposalFields on Proposal {
|
|||||||
query Proposals(
|
query Proposals(
|
||||||
$includeNewMarketProductFields: Boolean!
|
$includeNewMarketProductFields: Boolean!
|
||||||
$includeUpdateMarketStates: Boolean!
|
$includeUpdateMarketStates: Boolean!
|
||||||
|
$includeUpdateReferralPrograms: Boolean!
|
||||||
|
$includeUpdateVolumeDiscountPrograms: Boolean!
|
||||||
) {
|
) {
|
||||||
proposalsConnection {
|
proposalsConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -134,6 +172,9 @@ query Proposals(
|
|||||||
...ProposalFields
|
...ProposalFields
|
||||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||||
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
||||||
|
...UpdateReferralPrograms @include(if: $includeUpdateReferralPrograms)
|
||||||
|
...UpdateVolumeDiscountPrograms
|
||||||
|
@include(if: $includeUpdateVolumeDiscountPrograms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-3
@@ -7,15 +7,21 @@ export type NewMarketProductFieldsFragment = { __typename?: 'Proposal', terms: {
|
|||||||
|
|
||||||
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||||
|
|
||||||
|
export type UpdateReferralProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: string, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
|
||||||
|
|
||||||
|
export type UpdateVolumeDiscountProgramsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } } };
|
||||||
|
|
||||||
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
|
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
|
||||||
|
|
||||||
export type ProposalsQueryVariables = Types.Exact<{
|
export type ProposalsQueryVariables = Types.Exact<{
|
||||||
includeNewMarketProductFields: Types.Scalars['Boolean'];
|
includeNewMarketProductFields: Types.Scalars['Boolean'];
|
||||||
includeUpdateMarketStates: Types.Scalars['Boolean'];
|
includeUpdateMarketStates: Types.Scalars['Boolean'];
|
||||||
|
includeUpdateReferralPrograms: Types.Scalars['Boolean'];
|
||||||
|
includeUpdateVolumeDiscountPrograms: Types.Scalars['Boolean'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram', windowLength: number, endOfProgram: string, benefitTiers: Array<{ __typename?: 'BenefitTier', minimumEpochs: number, minimumRunningNotionalTakerVolume: string, referralDiscountFactor: string, referralRewardFactor: string }>, stakingTiers: Array<{ __typename?: 'StakingTier', minimumStakedTokens: string, referralRewardMultiplier: string }> } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram', endOfProgramTimestamp: any, windowLength: number, benefitTiers: Array<{ __typename?: 'VolumeBenefitTier', minimumRunningNotionalTakerVolume: string, volumeDiscountFactor: string }> } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
|
||||||
|
|
||||||
export const NewMarketProductFieldsFragmentDoc = gql`
|
export const NewMarketProductFieldsFragmentDoc = gql`
|
||||||
fragment NewMarketProductFields on Proposal {
|
fragment NewMarketProductFields on Proposal {
|
||||||
@@ -64,6 +70,44 @@ export const UpdateMarketStatesFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const UpdateReferralProgramsFragmentDoc = gql`
|
||||||
|
fragment UpdateReferralPrograms on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateReferralProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumEpochs
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
referralDiscountFactor
|
||||||
|
referralRewardFactor
|
||||||
|
}
|
||||||
|
endOfProgram: endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
stakingTiers {
|
||||||
|
minimumStakedTokens
|
||||||
|
referralRewardMultiplier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const UpdateVolumeDiscountProgramsFragmentDoc = gql`
|
||||||
|
fragment UpdateVolumeDiscountPrograms on Proposal {
|
||||||
|
terms {
|
||||||
|
change {
|
||||||
|
... on UpdateVolumeDiscountProgram {
|
||||||
|
benefitTiers {
|
||||||
|
minimumRunningNotionalTakerVolume
|
||||||
|
volumeDiscountFactor
|
||||||
|
}
|
||||||
|
endOfProgramTimestamp
|
||||||
|
windowLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const ProposalFieldsFragmentDoc = gql`
|
export const ProposalFieldsFragmentDoc = gql`
|
||||||
fragment ProposalFields on Proposal {
|
fragment ProposalFields on Proposal {
|
||||||
id
|
id
|
||||||
@@ -147,20 +191,24 @@ export const ProposalFieldsFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const ProposalsDocument = gql`
|
export const ProposalsDocument = gql`
|
||||||
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!) {
|
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!, $includeUpdateReferralPrograms: Boolean!, $includeUpdateVolumeDiscountPrograms: Boolean!) {
|
||||||
proposalsConnection {
|
proposalsConnection {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...ProposalFields
|
...ProposalFields
|
||||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||||
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
||||||
|
...UpdateReferralPrograms @include(if: $includeUpdateReferralPrograms)
|
||||||
|
...UpdateVolumeDiscountPrograms @include(if: $includeUpdateVolumeDiscountPrograms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${ProposalFieldsFragmentDoc}
|
${ProposalFieldsFragmentDoc}
|
||||||
${NewMarketProductFieldsFragmentDoc}
|
${NewMarketProductFieldsFragmentDoc}
|
||||||
${UpdateMarketStatesFragmentDoc}`;
|
${UpdateMarketStatesFragmentDoc}
|
||||||
|
${UpdateReferralProgramsFragmentDoc}
|
||||||
|
${UpdateVolumeDiscountProgramsFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useProposalsQuery__
|
* __useProposalsQuery__
|
||||||
@@ -176,6 +224,8 @@ ${UpdateMarketStatesFragmentDoc}`;
|
|||||||
* variables: {
|
* variables: {
|
||||||
* includeNewMarketProductFields: // value for 'includeNewMarketProductFields'
|
* includeNewMarketProductFields: // value for 'includeNewMarketProductFields'
|
||||||
* includeUpdateMarketStates: // value for 'includeUpdateMarketStates'
|
* includeUpdateMarketStates: // value for 'includeUpdateMarketStates'
|
||||||
|
* includeUpdateReferralPrograms: // value for 'includeUpdateReferralPrograms'
|
||||||
|
* includeUpdateVolumeDiscountPrograms: // value for 'includeUpdateVolumeDiscountPrograms'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const ProposalsContainer = () => {
|
|||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
|
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export const RejectedProposalsContainer = () => {
|
|||||||
variables: {
|
variables: {
|
||||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||||
|
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||||
|
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe('Market trading page', () => {
|
|||||||
// 6002-MDET-003
|
// 6002-MDET-003
|
||||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||||
cy.getByTestId(marketPrice).within(() => {
|
cy.getByTestId(marketPrice).within(() => {
|
||||||
cy.getByTestId(itemHeader).should('have.text', 'Price');
|
cy.getByTestId(itemHeader).should('have.text', 'Mark Price');
|
||||||
cy.getByTestId(itemValue).should('not.be.empty');
|
cy.getByTestId(itemValue).should('not.be.empty');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { MarketState } from '@vegaprotocol/types';
|
|
||||||
|
|
||||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
|
||||||
const oracleBannerStatus = 'oracle-banner-status';
|
|
||||||
const oracleFullProfile = 'oracle-full-profile';
|
|
||||||
|
|
||||||
describe('oracle information', { tags: '@smoke' }, () => {
|
|
||||||
before(() => {
|
|
||||||
cy.setOnBoardingViewed();
|
|
||||||
cy.mockTradingPage(
|
|
||||||
MarketState.STATE_ACTIVE,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
'COMPROMISED'
|
|
||||||
);
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.wait('@Markets');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('show oracle banner', () => {
|
|
||||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
|
||||||
cy.getByTestId(oracleBannerDialogTrigger)
|
|
||||||
.should('contain.text', 'Show more')
|
|
||||||
.click();
|
|
||||||
cy.getByTestId(oracleFullProfile).should('exist');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import { checkSorting } from '@vegaprotocol/cypress';
|
|
||||||
|
|
||||||
const dialogClose = 'dialog-close';
|
|
||||||
|
|
||||||
describe('accounts', { tags: '@smoke' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open usage breakdown dialog when clicked on used', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
// 7001-COLL-009
|
|
||||||
cy.get('[col-id="used"]').contains('1.01').click();
|
|
||||||
const headers = ['Market', 'Account type', 'Balance', 'Margin health'];
|
|
||||||
cy.getByTestId('usage-breakdown').within(($headers) => {
|
|
||||||
cy.wrap($headers)
|
|
||||||
.get('.ag-header-cell-text')
|
|
||||||
.each(($header, i) => {
|
|
||||||
cy.wrap($header).should('have.text', headers[i]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cy.getByTestId(dialogClose).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sorting by ag-grid columns should work well', () => {
|
|
||||||
before(() => {
|
|
||||||
const dialogs = Cypress.$('[data-testid="dialog-close"]:visible');
|
|
||||||
if (dialogs.length > 0) {
|
|
||||||
dialogs.each((btn) => {
|
|
||||||
cy.wrap(btn).click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cy.contains('Loading...').should('not.exist');
|
|
||||||
});
|
|
||||||
// 7001-COLL-010
|
|
||||||
it('sorting by asset', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
const marketsSortedDefault = ['tBTC', 'tEURO', 'tDAI', 'tBTC'];
|
|
||||||
const marketsSortedAsc = ['tBTC', 'tBTC', 'tDAI', 'tEURO'];
|
|
||||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
|
||||||
checkSorting(
|
|
||||||
'asset.symbol',
|
|
||||||
marketsSortedDefault,
|
|
||||||
marketsSortedAsc,
|
|
||||||
marketsSortedDesc
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorting by total', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
const marketsSortedDefault = [
|
|
||||||
'1,000.00002',
|
|
||||||
'1,000.01',
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00001',
|
|
||||||
];
|
|
||||||
const marketsSortedAsc = [
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00001',
|
|
||||||
'1,000.00002',
|
|
||||||
'1,000.01',
|
|
||||||
];
|
|
||||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
|
||||||
|
|
||||||
checkSorting(
|
|
||||||
'total',
|
|
||||||
marketsSortedDefault,
|
|
||||||
marketsSortedAsc,
|
|
||||||
marketsSortedDesc
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorting by used', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
// concat actual value with percentage value
|
|
||||||
// as cypress will pick up the entire cell contes
|
|
||||||
// textContent
|
|
||||||
const marketsSortedDefault = [
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
'0.01' + '0.00%',
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
];
|
|
||||||
const marketsSortedAsc = [
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
'0.00' + '0.00%',
|
|
||||||
'0.01' + '0.00%',
|
|
||||||
];
|
|
||||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
|
||||||
checkSorting(
|
|
||||||
'used',
|
|
||||||
marketsSortedDefault,
|
|
||||||
marketsSortedAsc,
|
|
||||||
marketsSortedDesc
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorting by available', () => {
|
|
||||||
cy.getByTestId('Collateral').click();
|
|
||||||
const marketsSortedDefault = [
|
|
||||||
'1,000.00002',
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00001',
|
|
||||||
];
|
|
||||||
const marketsSortedAsc = [
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00',
|
|
||||||
'1,000.00001',
|
|
||||||
'1,000.00002',
|
|
||||||
];
|
|
||||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
|
||||||
|
|
||||||
checkSorting(
|
|
||||||
'available',
|
|
||||||
marketsSortedDefault,
|
|
||||||
marketsSortedAsc,
|
|
||||||
marketsSortedDesc
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
import {
|
|
||||||
TIFlist,
|
|
||||||
orderTIFDropDown,
|
|
||||||
toggleLimit,
|
|
||||||
toggleMarket,
|
|
||||||
} from '../support/deal-ticket';
|
|
||||||
|
|
||||||
const tooltipContent = 'tooltip-content';
|
|
||||||
const reduceOnly = 'reduce-only';
|
|
||||||
const postOnly = 'post-only';
|
|
||||||
|
|
||||||
describe('time in force validation', { tags: '@smoke' }, () => {
|
|
||||||
before(() => {
|
|
||||||
cy.setVegaWallet();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.clearAllLocalStorage();
|
|
||||||
cy.setOnBoardingViewed();
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.wait('@Markets');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must have market order set up to IOC by default', function () {
|
|
||||||
// 7002-SORD-030
|
|
||||||
cy.getByTestId(toggleMarket).click();
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
TIFlist.filter((item) => item.code === 'IOC')[0].text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must have time in force set to GTC for limit order', function () {
|
|
||||||
// 7002-SORD-031
|
|
||||||
cy.getByTestId(toggleLimit).click();
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
TIFlist.filter((item) => item.code === 'GTC')[0].text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('selections should be remembered', () => {
|
|
||||||
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
|
|
||||||
cy.getByTestId(toggleMarket).click();
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
TIFlist.filter((item) => item.code === 'IOC')[0].text
|
|
||||||
);
|
|
||||||
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_FOK');
|
|
||||||
cy.getByTestId(toggleLimit).click();
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
TIFlist.filter((item) => item.code === 'GTT')[0].text
|
|
||||||
);
|
|
||||||
cy.getByTestId(toggleMarket).click();
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
TIFlist.filter((item) => item.code === 'FOK')[0].text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('limit order', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.getByTestId(toggleLimit).click();
|
|
||||||
});
|
|
||||||
const validTIF = TIFlist;
|
|
||||||
validTIF.forEach((tif) => {
|
|
||||||
// 7002-SORD-023
|
|
||||||
// 7002-SORD-024
|
|
||||||
// 7002-SORD-025
|
|
||||||
// 7002-SORD-026
|
|
||||||
// 7002-SORD-027
|
|
||||||
// 7002-SORD-028
|
|
||||||
|
|
||||||
it(`must be able to select ${tif.code}`, function () {
|
|
||||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
tif.text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('market order', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.getByTestId(toggleMarket).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
|
|
||||||
const invalidTIF = TIFlist.filter(
|
|
||||||
(tif) => !['FOK', 'IOC'].includes(tif.code)
|
|
||||||
);
|
|
||||||
|
|
||||||
validTIF.forEach((tif) => {
|
|
||||||
// 7002-SORD-025
|
|
||||||
// 7002-SORD-026
|
|
||||||
|
|
||||||
it(`must be able to select ${tif.code}`, function () {
|
|
||||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
tif.text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
invalidTIF.forEach((tif) => {
|
|
||||||
// 7002-SORD-023
|
|
||||||
// 7002-SORD-024
|
|
||||||
// 7002-SORD-027
|
|
||||||
// 7002-SORD-028
|
|
||||||
it(`must not be able to select ${tif.code}`, function () {
|
|
||||||
cy.getByTestId(orderTIFDropDown).should('not.contain', tif.text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('post and reduce - market order', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.getByTestId(toggleMarket).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
const validTIF = TIFlist.filter((tif) => ['FOK', 'IOC'].includes(tif.code));
|
|
||||||
|
|
||||||
validTIF.forEach((tif) => {
|
|
||||||
// 7002-SORD-025
|
|
||||||
// 7002-SORD-026
|
|
||||||
|
|
||||||
it(`post and reduce order market for ${tif.code}`, function () {
|
|
||||||
// 7003-SORD-054
|
|
||||||
// 7003-SORD-055
|
|
||||||
// 7003-SORD-056
|
|
||||||
// 7003-SORD-057
|
|
||||||
|
|
||||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
tif.text
|
|
||||||
);
|
|
||||||
cy.getByTestId(postOnly).should('be.disabled');
|
|
||||||
cy.getByTestId(reduceOnly).should('be.enabled');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('post and reduce - limit order', () => {
|
|
||||||
before(() => {
|
|
||||||
cy.getByTestId(toggleLimit).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
const validTIFLimit = TIFlist.filter((tif) =>
|
|
||||||
['GFA', 'GFN', 'GTC', 'GTT'].includes(tif.code)
|
|
||||||
);
|
|
||||||
|
|
||||||
validTIFLimit.forEach((tif) => {
|
|
||||||
it(`post and reduce order for limit ${tif.code}`, function () {
|
|
||||||
// 7003-SORD-054
|
|
||||||
// 7003-SORD-055
|
|
||||||
// 7003-SORD-056
|
|
||||||
// 7003-SORD-057
|
|
||||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
|
||||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
|
||||||
'have.text',
|
|
||||||
tif.text
|
|
||||||
);
|
|
||||||
cy.getByTestId(postOnly).should('be.enabled');
|
|
||||||
cy.getByTestId(reduceOnly).should('be.disabled');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it(`can see explanation of what post only and reduce only is/does`, function () {
|
|
||||||
// 7003-SORD-058
|
|
||||||
cy.get('[for="post-only"]').should('have.text', 'Post only').realHover();
|
|
||||||
cy.getByTestId(tooltipContent).should(
|
|
||||||
'contain.text',
|
|
||||||
`"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.`
|
|
||||||
);
|
|
||||||
cy.get('[for="reduce-only"]')
|
|
||||||
.should('have.text', 'Reduce only')
|
|
||||||
.realHover();
|
|
||||||
cy.getByTestId(tooltipContent).should(
|
|
||||||
'contain.text',
|
|
||||||
`"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
|
||||||
import { partyAssetsQuery } from '@vegaprotocol/mock';
|
|
||||||
|
|
||||||
describe('Portfolio page', { tags: '@smoke' }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockGQL((req) => {
|
|
||||||
aliasGQLQuery(req, 'PartyAssets', partyAssetsQuery());
|
|
||||||
});
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
});
|
|
||||||
describe('Ledger entries', () => {
|
|
||||||
it('Download form should be properly rendered', () => {
|
|
||||||
// 7007-LEEN-001
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.getByTestId('"Ledger entries"').click();
|
|
||||||
cy.getByTestId('tab-ledger-entries').within(($headers) => {
|
|
||||||
cy.wrap($headers)
|
|
||||||
.getByTestId('ledger-download-button')
|
|
||||||
.should('be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
const colHeader = '.ag-header-cell-text';
|
|
||||||
const colIdPrice = '[col-id=price]';
|
|
||||||
const colIdSize = '[col-id=size]';
|
|
||||||
const colIdCreatedAt = '[col-id=createdAt]';
|
|
||||||
const tradesTab = 'Trades';
|
|
||||||
const tradesTable = 'tab-trades';
|
|
||||||
|
|
||||||
describe('trades', { tags: '@smoke' }, () => {
|
|
||||||
before(() => {
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setOnBoardingViewed();
|
|
||||||
cy.intercept('POST', '/graphql', (req) => {
|
|
||||||
if (req.body.operationName === 'Trades') {
|
|
||||||
req.alias = '@Trades';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cy.visit('/#/markets/market-0');
|
|
||||||
cy.getByTestId(tradesTab).click();
|
|
||||||
cy.wait('@Trades');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('show trades', () => {
|
|
||||||
// 6005-THIS-001
|
|
||||||
// 6005-THIS-002
|
|
||||||
cy.getByTestId(tradesTab).should('be.visible');
|
|
||||||
cy.getByTestId(tradesTable).should('be.visible');
|
|
||||||
cy.getByTestId(tradesTable).should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('show trades prices', () => {
|
|
||||||
// 6005-THIS-003
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.get(`${colIdPrice} ${colHeader}`)
|
|
||||||
.first()
|
|
||||||
.should('have.text', 'Price');
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.get(colIdPrice)
|
|
||||||
.each(($tradePrice) => {
|
|
||||||
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('show trades sizes', () => {
|
|
||||||
// 6005-THIS-004
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.get(`${colIdSize} ${colHeader}`)
|
|
||||||
.first()
|
|
||||||
.should('have.text', 'Size');
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.get(colIdSize)
|
|
||||||
.each(($tradeSize) => {
|
|
||||||
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('show trades date and time', () => {
|
|
||||||
// 6005-THIS-005
|
|
||||||
cy.getByTestId(tradesTable) // order table shares identical col id
|
|
||||||
.find(`${colIdCreatedAt} ${colHeader}`)
|
|
||||||
.should('have.text', 'Created at');
|
|
||||||
const dateTimeRegex = /(\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.get(`.ag-center-cols-container ${colIdCreatedAt}`)
|
|
||||||
.each(($tradeDateTime) => {
|
|
||||||
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('trades are sorted descending by datetime', () => {
|
|
||||||
// 6005-THIS-006
|
|
||||||
const dateTimes: Date[] = [];
|
|
||||||
cy.getByTestId(tradesTable)
|
|
||||||
.find(colIdCreatedAt)
|
|
||||||
.each(($tradeDateTime, index) => {
|
|
||||||
if (index != 0) {
|
|
||||||
//ignore header
|
|
||||||
dateTimes.push(new Date($tradeDateTime.text()));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(dateTimes).to.deep.equal(
|
|
||||||
dateTimes.sort((a, b) => b.getTime() - a.getTime())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('copy price to deal ticket form', () => {
|
|
||||||
// 6005-THIS-007
|
|
||||||
cy.getByTestId('order-type-Limit').click();
|
|
||||||
cy.get(colIdPrice).last().should('be.visible').click();
|
|
||||||
cy.getByTestId('order-price').should('have.value', '171.16898');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { selectAsset } from '../support/helpers';
|
|
||||||
|
|
||||||
const amountField = 'input[name="amount"]';
|
|
||||||
const amountShortName = 'input[name="amount"] + div + span.text-xs';
|
|
||||||
const assetSelection = 'select-asset';
|
|
||||||
const assetBalance = 'asset-balance';
|
|
||||||
const assetOption = 'rich-select-option';
|
|
||||||
const openTransferButton = 'open-transfer';
|
|
||||||
const submitTransferBtn = '[type="submit"]';
|
|
||||||
const toAddressField = '[name="toAddress"]';
|
|
||||||
const transferForm = 'transfer-form';
|
|
||||||
const ASSET_SEPOLIA_TBTC = 2;
|
|
||||||
const collateralTab = 'Collateral';
|
|
||||||
const toastCloseBtn = 'toast-close';
|
|
||||||
const toastContent = 'toast-content';
|
|
||||||
|
|
||||||
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.getByTestId(collateralTab).click();
|
|
||||||
cy.getByTestId(openTransferButton).click();
|
|
||||||
|
|
||||||
cy.wait('@Accounts');
|
|
||||||
cy.wait('@Assets');
|
|
||||||
cy.mockVegaWalletTransaction();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('key to key transfers by select key', () => {
|
|
||||||
// 1003-TRAN-001
|
|
||||||
// 1003-TRAN-006
|
|
||||||
// 1003-TRAN-007
|
|
||||||
// 1003-TRAN-008
|
|
||||||
// 1003-TRAN-009
|
|
||||||
// 1003-TRAN-010
|
|
||||||
// 1003-TRAN-023
|
|
||||||
cy.getByTestId(transferForm).should('be.visible');
|
|
||||||
cy.getByTestId(transferForm).find(toAddressField).select(1);
|
|
||||||
|
|
||||||
cy.getByTestId(assetSelection).click();
|
|
||||||
cy.getByTestId(assetOption);
|
|
||||||
cy.getByTestId(assetBalance).should('not.be.empty');
|
|
||||||
cy.getByTestId(assetOption).should('have.length.gt', 4);
|
|
||||||
|
|
||||||
let optionText: string;
|
|
||||||
cy.getByTestId(assetOption)
|
|
||||||
.eq(2)
|
|
||||||
.invoke('text')
|
|
||||||
.then((text: string) => {
|
|
||||||
optionText = text;
|
|
||||||
cy.getByTestId(assetOption).eq(2).click();
|
|
||||||
cy.getByTestId(assetSelection).should('have.text', optionText);
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(amountField)
|
|
||||||
.type('1', { delay: 100, force: true });
|
|
||||||
|
|
||||||
cy.getByTestId(transferForm).find(amountShortName).should('not.be.empty');
|
|
||||||
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(toastContent).should(
|
|
||||||
'contain.text',
|
|
||||||
'Awaiting confirmation'
|
|
||||||
);
|
|
||||||
cy.getByTestId(toastCloseBtn).click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('key to key transfers by enter manual key', () => {
|
|
||||||
//1003-TRAN-005
|
|
||||||
cy.getByTestId(transferForm).should('be.visible');
|
|
||||||
cy.contains('Enter manually').click();
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(toAddressField)
|
|
||||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
|
||||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(amountField)
|
|
||||||
.type('1', { delay: 100, force: true });
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(toastContent).should(
|
|
||||||
'contain.text',
|
|
||||||
'Awaiting confirmation'
|
|
||||||
);
|
|
||||||
cy.getByTestId(toastCloseBtn).click();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { DocsLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import {
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
fromNanoSeconds,
|
fromNanoSeconds,
|
||||||
getExpiryDate,
|
getExpiryDate,
|
||||||
getMarketExpiryDate,
|
getMarketExpiryDate,
|
||||||
@@ -14,9 +15,12 @@ import {
|
|||||||
Last24hVolume,
|
Last24hVolume,
|
||||||
getAsset,
|
getAsset,
|
||||||
getDataSourceSpecForSettlementSchedule,
|
getDataSourceSpecForSettlementSchedule,
|
||||||
|
isMarketInAuction,
|
||||||
marketInfoProvider,
|
marketInfoProvider,
|
||||||
useFundingPeriodsQuery,
|
useFundingPeriodsQuery,
|
||||||
useFundingRate,
|
useFundingRate,
|
||||||
|
useMarketTradingMode,
|
||||||
|
useExternalTwap,
|
||||||
} from '@vegaprotocol/markets';
|
} from '@vegaprotocol/markets';
|
||||||
import { MarketState as State } from '@vegaprotocol/types';
|
import { MarketState as State } from '@vegaprotocol/types';
|
||||||
import { HeaderStat } from '../../components/header';
|
import { HeaderStat } from '../../components/header';
|
||||||
@@ -26,6 +30,7 @@ import { MarketState } from '../../components/market-state';
|
|||||||
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
|
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import { PriceCell } from '@vegaprotocol/datagrid';
|
||||||
|
|
||||||
interface MarketHeaderStatsProps {
|
interface MarketHeaderStatsProps {
|
||||||
market: Market;
|
market: Market;
|
||||||
@@ -38,34 +43,8 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
|||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex gap-8">
|
||||||
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
<HeaderStat heading={t('Mark Price')} testId="market-price">
|
||||||
<HeaderStat
|
|
||||||
heading={t('Expiry')}
|
|
||||||
description={
|
|
||||||
<ExpiryTooltipContent
|
|
||||||
market={market}
|
|
||||||
explorerUrl={VEGA_EXPLORER_URL}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
testId="market-expiry"
|
|
||||||
>
|
|
||||||
<ExpiryLabel market={market} />
|
|
||||||
</HeaderStat>
|
|
||||||
)}
|
|
||||||
{market.tradableInstrument.instrument.product.__typename ===
|
|
||||||
'Perpetual' && (
|
|
||||||
<HeaderStat
|
|
||||||
heading={`${t('Funding')} / ${t('Countdown')}`}
|
|
||||||
testId="market-funding"
|
|
||||||
>
|
|
||||||
<div className="flex justify-between gap-2">
|
|
||||||
<FundingRate marketId={market.id} />
|
|
||||||
<FundingCountdown marketId={market.id} />
|
|
||||||
</div>
|
|
||||||
</HeaderStat>
|
|
||||||
)}
|
|
||||||
<HeaderStat heading={t('Price')} testId="market-price">
|
|
||||||
<MarketMarkPrice
|
<MarketMarkPrice
|
||||||
marketId={market.id}
|
marketId={market.id}
|
||||||
decimalPlaces={market.decimalPlaces}
|
decimalPlaces={market.decimalPlaces}
|
||||||
@@ -108,8 +87,64 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
|||||||
marketId={market.id}
|
marketId={market.id}
|
||||||
assetDecimals={asset?.decimals || 0}
|
assetDecimals={asset?.decimals || 0}
|
||||||
/>
|
/>
|
||||||
|
{market.tradableInstrument.instrument.product.__typename === 'Future' && (
|
||||||
|
<HeaderStat
|
||||||
|
heading={t('Expiry')}
|
||||||
|
description={
|
||||||
|
<ExpiryTooltipContent
|
||||||
|
market={market}
|
||||||
|
explorerUrl={VEGA_EXPLORER_URL}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
testId="market-expiry"
|
||||||
|
>
|
||||||
|
<ExpiryLabel market={market} />
|
||||||
|
</HeaderStat>
|
||||||
|
)}
|
||||||
|
{market.tradableInstrument.instrument.product.__typename ===
|
||||||
|
'Perpetual' && (
|
||||||
|
<HeaderStat
|
||||||
|
heading={`${t('Funding Rate')} / ${t('Countdown')}`}
|
||||||
|
testId="market-funding"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between gap-2">
|
||||||
|
<FundingRate marketId={market.id} />
|
||||||
|
<FundingCountdown marketId={market.id} />
|
||||||
|
</div>
|
||||||
|
</HeaderStat>
|
||||||
|
)}
|
||||||
|
{market.tradableInstrument.instrument.product.__typename ===
|
||||||
|
'Perpetual' && (
|
||||||
|
<HeaderStat
|
||||||
|
heading={`${t('Index Price')}`}
|
||||||
|
description={
|
||||||
|
<div className="p1">
|
||||||
|
{t(
|
||||||
|
'The external time weighted average price (TWAP) received from the data source defined in the data sourcing specification.'
|
||||||
|
)}
|
||||||
|
{DocsLinks && (
|
||||||
|
<ExternalLink
|
||||||
|
href={DocsLinks.ETH_DATA_SOURCES}
|
||||||
|
className="mt-2"
|
||||||
|
>
|
||||||
|
{t('Find out more')}
|
||||||
|
</ExternalLink>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
testId="index-price"
|
||||||
|
>
|
||||||
|
<IndexPrice
|
||||||
|
marketId={market.id}
|
||||||
|
decimalPlaces={
|
||||||
|
market.tradableInstrument.instrument.product.settlementAsset
|
||||||
|
.decimals
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</HeaderStat>
|
||||||
|
)}
|
||||||
<MarketProposalNotification marketId={market.id} />
|
<MarketProposalNotification marketId={market.id} />
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,6 +161,24 @@ export const FundingRate = ({ marketId }: { marketId: string }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const IndexPrice = ({
|
||||||
|
marketId,
|
||||||
|
decimalPlaces,
|
||||||
|
}: {
|
||||||
|
marketId: string;
|
||||||
|
decimalPlaces?: number;
|
||||||
|
}) => {
|
||||||
|
const { data: externalTwap } = useExternalTwap(marketId);
|
||||||
|
return externalTwap && decimalPlaces ? (
|
||||||
|
<PriceCell
|
||||||
|
value={Number(externalTwap)}
|
||||||
|
valueFormatted={addDecimalsFormatNumber(externalTwap, decimalPlaces)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const useNow = () => {
|
const useNow = () => {
|
||||||
const [now, setNow] = useState(Date.now());
|
const [now, setNow] = useState(Date.now());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -136,9 +189,11 @@ const useNow = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useEvery = (marketId: string) => {
|
const useEvery = (marketId: string) => {
|
||||||
|
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||||
const { data: marketInfo } = useDataProvider({
|
const { data: marketInfo } = useDataProvider({
|
||||||
dataProvider: marketInfoProvider,
|
dataProvider: marketInfoProvider,
|
||||||
variables: { marketId },
|
variables: { marketId },
|
||||||
|
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||||
});
|
});
|
||||||
let every: number | undefined = undefined;
|
let every: number | undefined = undefined;
|
||||||
const sourceType =
|
const sourceType =
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
import { ExternalLink, Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { getAsset, marketDataProvider, useMarket } from '@vegaprotocol/markets';
|
import { getAsset, marketDataProvider, useMarket } from '@vegaprotocol/markets';
|
||||||
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||||
import { TradeGrid } from './trade-grid';
|
import { TradeGrid } from './trade-grid';
|
||||||
@@ -68,7 +68,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
const update = useGlobalStore((store) => store.update);
|
const update = useGlobalStore((store) => store.update);
|
||||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||||
|
|
||||||
const { data, error, loading } = useMarket(marketId);
|
const { data, loading } = useMarket(marketId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -110,7 +110,15 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
}
|
}
|
||||||
}, [largeScreen, data, pinnedAsset]);
|
}, [largeScreen, data, pinnedAsset]);
|
||||||
|
|
||||||
if (!data && marketId) {
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<Loader />
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
<span className="flex flex-col items-center gap-2">
|
<span className="flex flex-col items-center gap-2">
|
||||||
@@ -120,7 +128,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
<p className="justify-center text-sm">
|
<p className="justify-center text-sm">
|
||||||
{t(`Please choose another market from the`)}{' '}
|
{t(`Please choose another market from the`)}{' '}
|
||||||
<ExternalLink onClick={() => navigate(Links.MARKETS())}>
|
<ExternalLink onClick={() => navigate(Links.MARKETS())}>
|
||||||
market list
|
{t('market list')}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
@@ -129,18 +137,13 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<>
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
data={data || undefined}
|
|
||||||
noDataCondition={(data) => false}
|
|
||||||
>
|
|
||||||
<TitleUpdater
|
<TitleUpdater
|
||||||
marketId={data?.id}
|
marketId={data?.id}
|
||||||
marketName={data?.tradableInstrument.instrument.name}
|
marketName={data?.tradableInstrument.instrument.name}
|
||||||
decimalPlaces={data?.decimalPlaces}
|
decimalPlaces={data?.decimalPlaces}
|
||||||
/>
|
/>
|
||||||
{tradeView}
|
{tradeView}
|
||||||
</AsyncRenderer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { TradingViews } from './trade-views';
|
|||||||
import {
|
import {
|
||||||
MarketSuccessorBanner,
|
MarketSuccessorBanner,
|
||||||
MarketSuccessorProposalBanner,
|
MarketSuccessorProposalBanner,
|
||||||
|
MarketTerminationBanner,
|
||||||
} from '../../components/market-banner';
|
} from '../../components/market-banner';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
@@ -138,6 +139,9 @@ const MainGrid = memo(
|
|||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<TradingViews.fills.component />
|
<TradingViews.fills.component />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||||
|
<TradingViews.fundingPayments.component />
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="accounts"
|
id="accounts"
|
||||||
name={t('Collateral')}
|
name={t('Collateral')}
|
||||||
@@ -169,6 +173,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
|||||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<MarketTerminationBanner market={market} />
|
||||||
<OracleBanner marketId={market?.id || ''} />
|
<OracleBanner marketId={market?.id || ''} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 p-0.5">
|
<div className="min-h-0 p-0.5">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import classNames from 'classnames';
|
|||||||
import {
|
import {
|
||||||
MarketSuccessorBanner,
|
MarketSuccessorBanner,
|
||||||
MarketSuccessorProposalBanner,
|
MarketSuccessorProposalBanner,
|
||||||
|
MarketTerminationBanner,
|
||||||
} from '../../components/market-banner';
|
} from '../../components/market-banner';
|
||||||
import { FLAGS } from '@vegaprotocol/environment';
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|||||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<MarketTerminationBanner market={market} />
|
||||||
<OracleBanner marketId={market?.id || ''} />
|
<OracleBanner marketId={market?.id || ''} />
|
||||||
</div>
|
</div>
|
||||||
<div>{renderMenu()}</div>
|
<div>{renderMenu()}</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { PositionsContainer } from '../../components/positions-container';
|
|||||||
import { AccountsContainer } from '../../components/accounts-container';
|
import { AccountsContainer } from '../../components/accounts-container';
|
||||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||||
import { FundingContainer } from '../../components/funding-container';
|
import { FundingContainer } from '../../components/funding-container';
|
||||||
|
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||||
import type { OrderContainerProps } from '../../components/orders-container';
|
import type { OrderContainerProps } from '../../components/orders-container';
|
||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||||
@@ -55,6 +56,10 @@ export const TradingViews = {
|
|||||||
label: 'Funding',
|
label: 'Funding',
|
||||||
component: requiresMarket(FundingContainer),
|
component: requiresMarket(FundingContainer),
|
||||||
},
|
},
|
||||||
|
fundingPayments: {
|
||||||
|
label: 'Funding Payments',
|
||||||
|
component: FundingPaymentsContainer,
|
||||||
|
},
|
||||||
orderbook: {
|
orderbook: {
|
||||||
label: 'Orderbook',
|
label: 'Orderbook',
|
||||||
component: requiresMarket(OrderbookContainer),
|
component: requiresMarket(OrderbookContainer),
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import { usePageTitleStore } from '../../stores';
|
|||||||
import { AccountsContainer } from '../../components/accounts-container';
|
import { AccountsContainer } from '../../components/accounts-container';
|
||||||
import { DepositsContainer } from '../../components/deposits-container';
|
import { DepositsContainer } from '../../components/deposits-container';
|
||||||
import { FillsContainer } from '../../components/fills-container';
|
import { FillsContainer } from '../../components/fills-container';
|
||||||
|
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||||
import { PositionsContainer } from '../../components/positions-container';
|
import { PositionsContainer } from '../../components/positions-container';
|
||||||
|
import { PositionsMenu } from '../../components/positions-menu';
|
||||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
import { LedgerContainer } from '../../components/ledger-container';
|
import { LedgerContainer } from '../../components/ledger-container';
|
||||||
@@ -68,7 +70,11 @@ export const Portfolio = () => {
|
|||||||
<Tab id="account-history" name={t('Account history')}>
|
<Tab id="account-history" name={t('Account history')}>
|
||||||
<AccountHistoryContainer />
|
<AccountHistoryContainer />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="positions" name={t('Positions')}>
|
<Tab
|
||||||
|
id="positions"
|
||||||
|
name={t('Positions')}
|
||||||
|
menu={<PositionsMenu />}
|
||||||
|
>
|
||||||
<PositionsContainer allKeys />
|
<PositionsContainer allKeys />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="orders" name={t('Orders')}>
|
<Tab id="orders" name={t('Orders')}>
|
||||||
@@ -77,6 +83,9 @@ export const Portfolio = () => {
|
|||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<FillsContainer />
|
<FillsContainer />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||||
|
<FundingPaymentsContainer />
|
||||||
|
</Tab>
|
||||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||||
<LedgerContainer />
|
<LedgerContainer />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { FundingPaymentsManager } from '@vegaprotocol/funding-payments';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
|
|
||||||
|
export const FundingPaymentsContainer = () => {
|
||||||
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
|
const gridStore = useFundingPaymentsStore((store) => store.gridStore);
|
||||||
|
const updateGridStore = useFundingPaymentsStore(
|
||||||
|
(store) => store.updateGridStore
|
||||||
|
);
|
||||||
|
|
||||||
|
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||||
|
updateGridStore(colState);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pubKey) {
|
||||||
|
return (
|
||||||
|
<Splash>
|
||||||
|
<p>{t('Please connect Vega wallet')}</p>
|
||||||
|
</Splash>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FundingPaymentsManager
|
||||||
|
partyId={pubKey}
|
||||||
|
onMarketClick={onMarketClick}
|
||||||
|
gridProps={gridStoreCallbacks}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFundingPaymentsStore = create<DataGridSlice>()(
|
||||||
|
persist(createDataGridSlice, {
|
||||||
|
name: 'vega_funding_payments_store',
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './funding-payments-container';
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './market-successor-banner';
|
export * from './market-successor-banner';
|
||||||
export * from './market-successor-proposal-banner';
|
export * from './market-successor-proposal-banner';
|
||||||
|
export * from './market-termination-banner';
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import type { SingleExecutionResult } from '@apollo/client';
|
|||||||
import type { MockedResponse } from '@apollo/react-testing';
|
import type { MockedResponse } from '@apollo/react-testing';
|
||||||
import { MockedProvider } from '@apollo/react-testing';
|
import { MockedProvider } from '@apollo/react-testing';
|
||||||
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
|
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
|
||||||
import type { SuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
|
||||||
import { SuccessorProposalsListDocument } from '@vegaprotocol/proposals';
|
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
const marketProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
request: {
|
request: {
|
||||||
query: SuccessorProposalsListDocument,
|
query: MarketViewProposalsDocument,
|
||||||
|
variables: {
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
@@ -18,8 +23,11 @@ const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
|||||||
node: {
|
node: {
|
||||||
__typename: 'Proposal',
|
__typename: 'Proposal',
|
||||||
id: 'proposal-1',
|
id: 'proposal-1',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
terms: {
|
terms: {
|
||||||
__typename: 'ProposalTerms',
|
__typename: 'ProposalTerms',
|
||||||
|
closingDatetime: '2023-09-27',
|
||||||
|
enactmentDatetime: '2023-09-28',
|
||||||
change: {
|
change: {
|
||||||
__typename: 'NewMarket',
|
__typename: 'NewMarket',
|
||||||
instrument: {
|
instrument: {
|
||||||
@@ -66,12 +74,13 @@ describe('MarketSuccessorProposalBanner', () => {
|
|||||||
proposalsConnection: {
|
proposalsConnection: {
|
||||||
edges: [
|
edges: [
|
||||||
...((
|
...((
|
||||||
marketProposalMock?.result as SingleExecutionResult<SuccessorProposalsListQuery>
|
marketProposalMock?.result as SingleExecutionResult<MarketViewProposalsQuery>
|
||||||
)?.data?.proposalsConnection?.edges ?? []),
|
)?.data?.proposalsConnection?.edges ?? []),
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
__typename: 'Proposal',
|
__typename: 'Proposal',
|
||||||
id: 'proposal-2',
|
id: 'proposal-2',
|
||||||
|
state: Types.ProposalState.STATE_OPEN,
|
||||||
terms: {
|
terms: {
|
||||||
__typename: 'ProposalTerms',
|
__typename: 'ProposalTerms',
|
||||||
change: {
|
change: {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import type {
|
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
SuccessorProposalListFieldsFragment,
|
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
||||||
NewMarketSuccessorFieldsFragment,
|
|
||||||
} from '@vegaprotocol/proposals';
|
|
||||||
import { useSuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
|
||||||
import {
|
import {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Intent,
|
Intent,
|
||||||
@@ -11,23 +8,33 @@ import {
|
|||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
export const MarketSuccessorProposalBanner = ({
|
export const MarketSuccessorProposalBanner = ({
|
||||||
marketId,
|
marketId,
|
||||||
}: {
|
}: {
|
||||||
marketId?: string;
|
marketId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: proposals } = useSuccessorProposalsListQuery({
|
const proposals = useMarketViewProposals({
|
||||||
skip: !marketId,
|
skip: !marketId,
|
||||||
|
inState: Types.ProposalState.STATE_OPEN,
|
||||||
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
typename: 'NewMarket',
|
||||||
});
|
});
|
||||||
|
|
||||||
const successors =
|
const successors =
|
||||||
proposals?.proposalsConnection?.edges
|
proposals?.filter((item) => {
|
||||||
?.map((item) => item?.node as SuccessorProposalListFieldsFragment)
|
if (item.terms.change.__typename === 'NewMarket') {
|
||||||
.filter(
|
const newMarket = item.terms.change;
|
||||||
(item: SuccessorProposalListFieldsFragment) =>
|
if (
|
||||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
newMarket.successorConfiguration?.parentMarketId === marketId &&
|
||||||
?.successorConfiguration?.parentMarketId === marketId
|
item.state === Types.ProposalState.STATE_OPEN
|
||||||
) ?? [];
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}) ?? [];
|
||||||
const [visible, setVisible] = useState(true);
|
const [visible, setVisible] = useState(true);
|
||||||
const tokenLink = useLinks(DApp.Governance);
|
const tokenLink = useLinks(DApp.Governance);
|
||||||
if (visible && successors.length) {
|
if (visible && successors.length) {
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
|
||||||
|
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
|
||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
|
import { MarketTerminationBanner } from './market-termination-banner';
|
||||||
|
|
||||||
|
const marketMock = {
|
||||||
|
id: 'market-1',
|
||||||
|
decimalPlaces: 3,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
quoteName: 'tDAI',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Market;
|
||||||
|
|
||||||
|
const proposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||||
|
request: {
|
||||||
|
query: MarketViewProposalsDocument,
|
||||||
|
variables: { inState: Types.ProposalState.STATE_PASSED },
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
proposalsConnection: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'first-id',
|
||||||
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-09-30T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-1',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market one name',
|
||||||
|
code: 'Market one',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
id: 'second-id',
|
||||||
|
state: Types.ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: '2023-09-27T11:48:18Z',
|
||||||
|
enactmentDatetime: '2023-10-01T11:48:18',
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarketState',
|
||||||
|
updateType:
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
|
||||||
|
price: '',
|
||||||
|
market: {
|
||||||
|
id: 'market-2',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'Market two name',
|
||||||
|
code: 'Market two',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mocks: MockedResponse[] = [proposalMock];
|
||||||
|
|
||||||
|
describe('MarketTerminationBanner', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers().setSystemTime(new Date('2023-09-28T10:10:10.000Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be properly rendered', async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<MarketTerminationBanner market={marketMock} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container).not.toBeEmptyDOMElement();
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('termination-warning-banner-market-1')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { format, formatDuration, intervalToDuration } from 'date-fns';
|
||||||
|
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
|
import { getQuoteName } from '@vegaprotocol/markets';
|
||||||
|
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||||
|
|
||||||
|
export const MarketTerminationBanner = ({
|
||||||
|
market,
|
||||||
|
}: {
|
||||||
|
market: Market | null;
|
||||||
|
}) => {
|
||||||
|
const [visible, setVisible] = useState(true);
|
||||||
|
const skip = !market || !visible;
|
||||||
|
const proposalsData = useMarketViewProposals({
|
||||||
|
skip,
|
||||||
|
inState: Types.ProposalState.STATE_PASSED,
|
||||||
|
typename: 'UpdateMarketState',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!market) return null;
|
||||||
|
const marketFound = (proposalsData || []).find(
|
||||||
|
(item) =>
|
||||||
|
item.terms.change.__typename === 'UpdateMarketState' &&
|
||||||
|
item.terms.change.market.id === market.id &&
|
||||||
|
item.terms.change.updateType ===
|
||||||
|
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE &&
|
||||||
|
item.state === Types.ProposalState.STATE_PASSED // subscription doesn't have state parameter
|
||||||
|
);
|
||||||
|
|
||||||
|
const enactmentDatetime = new Date(marketFound?.terms.enactmentDatetime);
|
||||||
|
const name =
|
||||||
|
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
||||||
|
? marketFound.terms.change.market.tradableInstrument.instrument.code
|
||||||
|
: '';
|
||||||
|
|
||||||
|
if (name && enactmentDatetime.getTime() > Date.now()) {
|
||||||
|
const dayMonthDate = format(enactmentDatetime, 'dd MMMM');
|
||||||
|
const duration = intervalToDuration({
|
||||||
|
start: new Date(),
|
||||||
|
end: enactmentDatetime,
|
||||||
|
});
|
||||||
|
const formattedDuration = formatDuration(duration, {
|
||||||
|
format: ['days', 'hours'],
|
||||||
|
});
|
||||||
|
const price =
|
||||||
|
marketFound?.terms.change.__typename === 'UpdateMarketState'
|
||||||
|
? marketFound.terms.change.price
|
||||||
|
: '';
|
||||||
|
const assetSymbol = getQuoteName(market);
|
||||||
|
return (
|
||||||
|
<NotificationBanner
|
||||||
|
intent={Intent.Warning}
|
||||||
|
onClose={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
data-testid={`termination-warning-banner-${market.id}`}
|
||||||
|
>
|
||||||
|
<div className="uppercase mb-1">
|
||||||
|
{t('Trading on Market %s will stop on %s', [name, dayMonthDate])}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
'You will no longer be able to hold a position on this market when it closes in %s.',
|
||||||
|
[formattedDuration]
|
||||||
|
)}{' '}
|
||||||
|
{price &&
|
||||||
|
assetSymbol &&
|
||||||
|
t('The final price will be %s %s.', [
|
||||||
|
addDecimalsFormatNumber(price, market.decimalPlaces),
|
||||||
|
assetSymbol,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
</NotificationBanner>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import type { AccountFields } from './accounts-data-provider';
|
import type { AccountFields } from './accounts-data-provider';
|
||||||
import { getAccountData } from './accounts-data-provider';
|
import { getAccountData } from './accounts-data-provider';
|
||||||
import { AccountTable } from './accounts-table';
|
import { AccountTable } from './accounts-table';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
const singleRow = {
|
const singleRow = {
|
||||||
__typename: 'AccountBalance',
|
__typename: 'AccountBalance',
|
||||||
@@ -24,6 +25,26 @@ const singleRow = {
|
|||||||
} as AccountFields;
|
} as AccountFields;
|
||||||
const singleRowData = [singleRow];
|
const singleRowData = [singleRow];
|
||||||
|
|
||||||
|
const secondRow = {
|
||||||
|
__typename: 'AccountBalance',
|
||||||
|
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||||
|
balance: '125600002',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||||
|
symbol: 'aBTC',
|
||||||
|
decimals: 5,
|
||||||
|
},
|
||||||
|
available: '125600001',
|
||||||
|
used: '125600001',
|
||||||
|
total: '251200002',
|
||||||
|
} as AccountFields;
|
||||||
|
const multiRowData = [singleRow, secondRow];
|
||||||
|
|
||||||
describe('AccountsTable', () => {
|
describe('AccountsTable', () => {
|
||||||
it('should render correct columns', async () => {
|
it('should render correct columns', async () => {
|
||||||
// 7001-COLL-001
|
// 7001-COLL-001
|
||||||
@@ -68,6 +89,25 @@ describe('AccountsTable', () => {
|
|||||||
expect(rows?.childElementCount).toBe(1);
|
expect(rows?.childElementCount).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should sort assets', async () => {
|
||||||
|
// 7001-COLL-010
|
||||||
|
const { container } = render(
|
||||||
|
<AccountTable
|
||||||
|
rowData={multiRowData}
|
||||||
|
onClickAsset={() => null}
|
||||||
|
isReadOnly={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const headerCell = screen.getByText('Asset');
|
||||||
|
await userEvent.click(headerCell);
|
||||||
|
const rows = container.querySelectorAll(
|
||||||
|
'.ag-center-cols-container .ag-row'
|
||||||
|
);
|
||||||
|
expect(rows[0].textContent).toContain('aBTC');
|
||||||
|
expect(rows[1].textContent).toContain('tBTC');
|
||||||
|
});
|
||||||
|
|
||||||
it('should apply correct formatting in view as user mode', async () => {
|
it('should apply correct formatting in view as user mode', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<AccountTable
|
<AccountTable
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import BigNumber from 'bignumber.js';
|
|||||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||||
import { AccountType } from '@vegaprotocol/types';
|
import { AccountType } from '@vegaprotocol/types';
|
||||||
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
describe('TransferForm', () => {
|
describe('TransferForm', () => {
|
||||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||||
@@ -33,6 +34,59 @@ describe('TransferForm', () => {
|
|||||||
submitTransfer: jest.fn(),
|
submitTransfer: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
it('form tooltips correctly displayed', async () => {
|
||||||
|
// 1003-TRAN-015
|
||||||
|
// 1003-TRAN-016
|
||||||
|
// 1003-TRAN-017
|
||||||
|
// 1003-TRAN-018
|
||||||
|
// 1003-TRAN-019
|
||||||
|
render(<TransferForm {...props} />);
|
||||||
|
// 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 } }
|
||||||
|
);
|
||||||
|
// set valid amount
|
||||||
|
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||||
|
target: { value: amount },
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.hover(screen.getByText('Include transfer fee'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.hover(screen.getByText('Transfer fee'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.hover(screen.getByText('Amount to be transferred'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
userEvent.hover(screen.getByText('Total amount (with fee)'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('validates a manually entered address', async () => {
|
it('validates a manually entered address', async () => {
|
||||||
render(<TransferForm {...props} />);
|
render(<TransferForm {...props} />);
|
||||||
submit();
|
submit();
|
||||||
@@ -174,6 +228,7 @@ describe('TransferForm', () => {
|
|||||||
|
|
||||||
const amountInput = screen.getByLabelText('Amount');
|
const amountInput = screen.getByLabelText('Amount');
|
||||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||||
|
// 1003-TRAN-022
|
||||||
expect(checkbox).not.toBeChecked();
|
expect(checkbox).not.toBeChecked();
|
||||||
act(() => {
|
act(() => {
|
||||||
/* fire events that update state */
|
/* fire events that update state */
|
||||||
@@ -190,6 +245,7 @@ describe('TransferForm', () => {
|
|||||||
.times(props.feeFactor)
|
.times(props.feeFactor)
|
||||||
.toFixed();
|
.toFixed();
|
||||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||||
|
// 1003-TRAN-020
|
||||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||||
expectedAmount
|
expectedAmount
|
||||||
@@ -201,6 +257,8 @@ describe('TransferForm', () => {
|
|||||||
submit();
|
submit();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
// 1003-TRAN-023
|
||||||
|
|
||||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
@@ -263,6 +321,7 @@ describe('TransferForm', () => {
|
|||||||
.times(props.feeFactor)
|
.times(props.feeFactor)
|
||||||
.toFixed();
|
.toFixed();
|
||||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||||
|
// 1003-TRAN-021
|
||||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export * from '../deposits/src/lib/deposit.mock';
|
|||||||
export * from '../environment/src/utils/node.mock';
|
export * from '../environment/src/utils/node.mock';
|
||||||
export * from '../environment/src/components/node-guard/node-guard.mock';
|
export * from '../environment/src/components/node-guard/node-guard.mock';
|
||||||
export * from '../fills/src/lib/fills.mock';
|
export * from '../fills/src/lib/fills.mock';
|
||||||
|
export * from '../funding-payments/src/lib/funding-payments.mock';
|
||||||
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
export * from '../proposals/src/lib/proposals-data-provider/proposals.mock';
|
||||||
export * from '../market-depth/src/lib/market-depth.mock';
|
export * from '../market-depth/src/lib/market-depth.mock';
|
||||||
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { addImportNodeWallets } from './lib/commands/import-node-wallets';
|
|||||||
import { addVegaWalletTopUpRewardsPool } from './lib/commands/vega-wallet-top-up-rewards-pool';
|
import { addVegaWalletTopUpRewardsPool } from './lib/commands/vega-wallet-top-up-rewards-pool';
|
||||||
import { addAssociateTokensToVegaWallet } from './lib/commands/associate-tokens-to-vega-wallet';
|
import { addAssociateTokensToVegaWallet } from './lib/commands/associate-tokens-to-vega-wallet';
|
||||||
import { addMockChainId } from './lib/commands/mock-chain-id';
|
import { addMockChainId } from './lib/commands/mock-chain-id';
|
||||||
|
import { addVegaWalletTopUpNetworkAccount } from './lib/commands/vega-wallet-fund-network-account';
|
||||||
|
|
||||||
addGetTestIdcommand();
|
addGetTestIdcommand();
|
||||||
addMockGQLCommand();
|
addMockGQLCommand();
|
||||||
@@ -52,6 +53,7 @@ addImportNodeWallets();
|
|||||||
addVegaWalletTopUpRewardsPool();
|
addVegaWalletTopUpRewardsPool();
|
||||||
addAssociateTokensToVegaWallet();
|
addAssociateTokensToVegaWallet();
|
||||||
addMockChainId();
|
addMockChainId();
|
||||||
|
addVegaWalletTopUpNetworkAccount();
|
||||||
|
|
||||||
export {
|
export {
|
||||||
mockConnectWallet,
|
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) => {
|
.then((epochText) => {
|
||||||
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
||||||
transferEndEpoch = transferStartEpoch + 100;
|
transferEndEpoch = transferStartEpoch + 100;
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.log(transferStartEpoch);
|
|
||||||
console.log(transferEndEpoch);
|
|
||||||
/* eslint-enable */
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
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 type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet } 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 type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||||
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
import {
|
||||||
|
formatNumberPercentage,
|
||||||
|
formatRange,
|
||||||
|
formatValue,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
@@ -22,15 +26,23 @@ import {
|
|||||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
import { useEstimateFees } from '../../hooks';
|
import {
|
||||||
|
sumFees,
|
||||||
|
sumFeesDiscounts,
|
||||||
|
useEstimateFees,
|
||||||
|
} from '../../hooks/use-estimate-fees';
|
||||||
import { KeyValue } from './key-value';
|
import { KeyValue } from './key-value';
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionChevron,
|
AccordionChevron,
|
||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
|
Intent,
|
||||||
|
Pill,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { FeesBreakdown } from '../fees-breakdown';
|
||||||
|
|
||||||
const emptyValue = '-';
|
const emptyValue = '-';
|
||||||
|
|
||||||
@@ -50,7 +62,18 @@ export const DealTicketFeeDetails = ({
|
|||||||
const feeEstimate = useEstimateFees(order, isMarketInAuction);
|
const feeEstimate = useEstimateFees(order, isMarketInAuction);
|
||||||
const asset = getAsset(market);
|
const asset = getAsset(market);
|
||||||
const { decimals: assetDecimals, quantum } = asset;
|
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 (
|
return (
|
||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Fees')}
|
label={t('Fees')}
|
||||||
@@ -59,8 +82,19 @@ export const DealTicketFeeDetails = ({
|
|||||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||||
}
|
}
|
||||||
formattedValue={
|
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={
|
labelDescription={
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ describe('DealTicket', () => {
|
|||||||
'checked'
|
'checked'
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
|
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
|
||||||
|
// 7002-SORD-031
|
||||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
|
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
|
||||||
);
|
);
|
||||||
@@ -235,7 +236,7 @@ describe('DealTicket', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set values for a non-persistent reduce only order and disable post only checkbox', () => {
|
it('should set values for a non-persistent reduce only order and disable post only checkbox', async () => {
|
||||||
const expectedOrder = {
|
const expectedOrder = {
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
type: Schema.OrderType.TYPE_LIMIT,
|
type: Schema.OrderType.TYPE_LIMIT,
|
||||||
@@ -273,13 +274,28 @@ describe('DealTicket', () => {
|
|||||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||||
expectedOrder.price
|
expectedOrder.price
|
||||||
);
|
);
|
||||||
|
// 7003-SORD-054
|
||||||
|
// 7003-SORD-055
|
||||||
|
// 7003-SORD-057
|
||||||
expect(screen.getByTestId('post-only')).toBeDisabled();
|
expect(screen.getByTestId('post-only')).toBeDisabled();
|
||||||
expect(screen.getByTestId('reduce-only')).toBeEnabled();
|
expect(screen.getByTestId('reduce-only')).toBeEnabled();
|
||||||
expect(screen.getByTestId('reduce-only')).toBeChecked();
|
expect(screen.getByTestId('reduce-only')).toBeChecked();
|
||||||
expect(screen.getByTestId('post-only')).not.toBeChecked();
|
expect(screen.getByTestId('post-only')).not.toBeChecked();
|
||||||
|
userEvent.hover(screen.getByText('Reduce only'));
|
||||||
|
// 7003-SORD-056
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set values for a persistent post only order and disable reduce only checkbox', () => {
|
userEvent.hover(screen.getByText('Post only'));
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set values for a persistent post only order and disable reduce only checkbox', async () => {
|
||||||
const expectedOrder = {
|
const expectedOrder = {
|
||||||
marketId: market.id,
|
marketId: market.id,
|
||||||
type: Schema.OrderType.TYPE_LIMIT,
|
type: Schema.OrderType.TYPE_LIMIT,
|
||||||
@@ -318,10 +334,49 @@ describe('DealTicket', () => {
|
|||||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||||
expectedOrder.price
|
expectedOrder.price
|
||||||
);
|
);
|
||||||
|
// 7003-SORD-054
|
||||||
|
// 7003-SORD-055
|
||||||
|
// 7003-SORD-057
|
||||||
expect(screen.getByTestId('post-only')).toBeEnabled();
|
expect(screen.getByTestId('post-only')).toBeEnabled();
|
||||||
expect(screen.getByTestId('reduce-only')).toBeDisabled();
|
expect(screen.getByTestId('reduce-only')).toBeDisabled();
|
||||||
expect(screen.getByTestId('post-only')).toBeChecked();
|
expect(screen.getByTestId('post-only')).toBeChecked();
|
||||||
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
|
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
|
||||||
|
|
||||||
|
userEvent.hover(screen.getByText('Reduce only'));
|
||||||
|
// 7003-SORD-056
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
userEvent.hover(screen.getByText('Post only'));
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should see an explanation of post only', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
userEvent.hover(screen.getByText('Post only'));
|
||||||
|
// 7003-SORD-058
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toHaveTextContent(
|
||||||
|
`"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should see an explanation of reduce only', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
userEvent.hover(screen.getByText('Reduce only'));
|
||||||
|
// 7003-SORD-058
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltips = screen.getAllByTestId('tooltip-content');
|
||||||
|
expect(tooltips[0]).toHaveTextContent(
|
||||||
|
`"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set values for a persistent post only iceberg order and disable reduce only checkbox', () => {
|
it('should set values for a persistent post only iceberg order and disable reduce only checkbox', () => {
|
||||||
@@ -430,6 +485,12 @@ describe('DealTicket', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Only FOK and IOC should be present for type market order
|
// Only FOK and IOC should be present for type market order
|
||||||
|
// 7002-SORD-023
|
||||||
|
// 7002-SORD-024
|
||||||
|
// 7002-SORD-025
|
||||||
|
// 7002-SORD-026
|
||||||
|
// 7002-SORD-027
|
||||||
|
// 7002-SORD-028
|
||||||
expect(
|
expect(
|
||||||
Array.from(screen.getByTestId('order-tif').children).map(
|
Array.from(screen.getByTestId('order-tif').children).map(
|
||||||
(o) => o.textContent
|
(o) => o.textContent
|
||||||
@@ -437,6 +498,7 @@ describe('DealTicket', () => {
|
|||||||
).toEqual(['Fill or Kill (FOK)', 'Immediate or Cancel (IOC)']);
|
).toEqual(['Fill or Kill (FOK)', 'Immediate or Cancel (IOC)']);
|
||||||
|
|
||||||
// IOC should be default
|
// IOC should be default
|
||||||
|
// 7002-SORD-030
|
||||||
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
|
expect(screen.getByTestId('order-tif')).toHaveDisplayValue(
|
||||||
'Immediate or Cancel (IOC)'
|
'Immediate or Cancel (IOC)'
|
||||||
);
|
);
|
||||||
@@ -451,6 +513,12 @@ describe('DealTicket', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Switch to type limit order -> all TIF options should be shown
|
// Switch to type limit order -> all TIF options should be shown
|
||||||
|
// 7002-SORD-023
|
||||||
|
// 7002-SORD-024
|
||||||
|
// 7002-SORD-025
|
||||||
|
// 7002-SORD-026
|
||||||
|
// 7002-SORD-027
|
||||||
|
// 7002-SORD-028
|
||||||
await userEvent.click(screen.getByTestId('order-type-Limit'));
|
await userEvent.click(screen.getByTestId('order-type-Limit'));
|
||||||
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
expect(screen.getByTestId('order-tif').children).toHaveLength(
|
||||||
Object.keys(Schema.OrderTimeInForce).length
|
Object.keys(Schema.OrderTimeInForce).length
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface KeyValuePros {
|
|||||||
symbol: string;
|
symbol: string;
|
||||||
indent?: boolean | undefined;
|
indent?: boolean | undefined;
|
||||||
labelDescription?: ReactNode;
|
labelDescription?: ReactNode;
|
||||||
formattedValue?: string;
|
formattedValue?: ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,11 @@ export const KeyValue = ({
|
|||||||
onClick,
|
onClick,
|
||||||
formattedValue,
|
formattedValue,
|
||||||
}: KeyValuePros) => {
|
}: KeyValuePros) => {
|
||||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
const displayValue = (
|
||||||
|
<>
|
||||||
|
{formattedValue || '-'} {symbol || ''}
|
||||||
|
</>
|
||||||
|
);
|
||||||
const valueElement = onClick ? (
|
const valueElement = onClick ? (
|
||||||
<button onClick={onClick} className="font-mono ml-auto">
|
<button onClick={onClick} className="font-mono ml-auto">
|
||||||
{displayValue}
|
{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';
|
||||||
export * from './deal-ticket-validation';
|
export * from './deal-ticket-validation';
|
||||||
|
export * from './fees-breakdown';
|
||||||
export * from './trading-mode-tooltip';
|
export * from './trading-mode-tooltip';
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ query EstimateFees(
|
|||||||
makerFee
|
makerFee
|
||||||
infrastructureFee
|
infrastructureFee
|
||||||
liquidityFee
|
liquidityFee
|
||||||
|
makerFeeReferralDiscount
|
||||||
|
makerFeeVolumeDiscount
|
||||||
|
infrastructureFeeReferralDiscount
|
||||||
|
infrastructureFeeVolumeDiscount
|
||||||
|
liquidityFeeReferralDiscount
|
||||||
|
liquidityFeeVolumeDiscount
|
||||||
}
|
}
|
||||||
totalFeeAmount
|
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`
|
export const EstimateFeesDocument = gql`
|
||||||
@@ -34,6 +34,12 @@ export const EstimateFeesDocument = gql`
|
|||||||
makerFee
|
makerFee
|
||||||
infrastructureFee
|
infrastructureFee
|
||||||
liquidityFee
|
liquidityFee
|
||||||
|
makerFeeReferralDiscount
|
||||||
|
makerFeeVolumeDiscount
|
||||||
|
infrastructureFeeReferralDiscount
|
||||||
|
infrastructureFeeVolumeDiscount
|
||||||
|
liquidityFeeReferralDiscount
|
||||||
|
liquidityFeeVolumeDiscount
|
||||||
}
|
}
|
||||||
totalFeeAmount
|
totalFeeAmount
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,17 @@ import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
|
|||||||
|
|
||||||
const data: EstimateFeesQuery = {
|
const data: EstimateFeesQuery = {
|
||||||
estimateFees: {
|
estimateFees: {
|
||||||
totalFeeAmount: '12',
|
totalFeeAmount: '120',
|
||||||
fees: {
|
fees: {
|
||||||
infrastructureFee: '2',
|
infrastructureFee: '20',
|
||||||
liquidityFee: '4',
|
infrastructureFeeReferralDiscount: '2',
|
||||||
makerFee: '6',
|
infrastructureFeeVolumeDiscount: '4',
|
||||||
|
liquidityFee: '40',
|
||||||
|
liquidityFeeReferralDiscount: '6',
|
||||||
|
liquidityFeeVolumeDiscount: '8',
|
||||||
|
makerFee: '60',
|
||||||
|
makerFeeReferralDiscount: '10',
|
||||||
|
makerFeeVolumeDiscount: '12',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -67,11 +73,17 @@ describe('useEstimateFees', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
expect(result.current).toEqual({
|
expect(result.current).toEqual({
|
||||||
totalFeeAmount: '6',
|
totalFeeAmount: '60',
|
||||||
fees: {
|
fees: {
|
||||||
infrastructureFee: '1',
|
infrastructureFee: '10',
|
||||||
liquidityFee: '2',
|
infrastructureFeeReferralDiscount: '1',
|
||||||
makerFee: '3',
|
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';
|
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
|
||||||
|
|
||||||
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
|
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 = (
|
export const useEstimateFees = (
|
||||||
order?: OrderSubmissionBody['orderSubmission'],
|
order?: OrderSubmissionBody['orderSubmission'],
|
||||||
@@ -43,6 +69,26 @@ export const useEstimateFees = (
|
|||||||
),
|
),
|
||||||
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
|
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
|
||||||
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
|
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;
|
: data?.estimateFees;
|
||||||
|
|||||||
@@ -429,6 +429,12 @@ function compileFeatureFlags(): FeatureFlags {
|
|||||||
process.env['NX_GOVERNANCE_TRANSFERS']
|
process.env['NX_GOVERNANCE_TRANSFERS']
|
||||||
) as string
|
) as string
|
||||||
),
|
),
|
||||||
|
VOLUME_DISCOUNTS: TRUTHY.includes(
|
||||||
|
windowOrDefault(
|
||||||
|
'NX_VOLUME_DISCOUNTS',
|
||||||
|
process.env['NX_VOLUME_DISCOUNTS']
|
||||||
|
) as string
|
||||||
|
),
|
||||||
};
|
};
|
||||||
const EXPLORER_FLAGS = {
|
const EXPLORER_FLAGS = {
|
||||||
EXPLORER_ASSETS: TRUTHY.includes(
|
EXPLORER_ASSETS: TRUTHY.includes(
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
|||||||
WITHDRAWAL_LIMITS: `${VEGA_DOCS_URL}/concepts/assets/deposits-withdrawals#withdrawal-limits`,
|
WITHDRAWAL_LIMITS: `${VEGA_DOCS_URL}/concepts/assets/deposits-withdrawals#withdrawal-limits`,
|
||||||
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
|
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
|
||||||
MARKET_LIFECYCLE: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-lifecycle`,
|
MARKET_LIFECYCLE: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-lifecycle`,
|
||||||
|
ETH_DATA_SOURCES: `${VEGA_DOCS_URL}/concepts/trading-on-vega/data-sources#ethereum-data-sources`,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type CosmicElevatorFlags = Pick<
|
|||||||
| 'REFERRALS'
|
| 'REFERRALS'
|
||||||
| 'UPDATE_MARKET_STATE'
|
| 'UPDATE_MARKET_STATE'
|
||||||
| 'GOVERNANCE_TRANSFERS'
|
| 'GOVERNANCE_TRANSFERS'
|
||||||
|
| 'VOLUME_DISCOUNTS'
|
||||||
>;
|
>;
|
||||||
export type Configuration = z.infer<typeof tomlConfigSchema>;
|
export type Configuration = z.infer<typeof tomlConfigSchema>;
|
||||||
export const CUSTOM_NODE_KEY = 'custom' as const;
|
export const CUSTOM_NODE_KEY = 'custom' as const;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ const COSMIC_ELEVATOR_FLAGS = {
|
|||||||
REFERRALS: z.optional(z.boolean()),
|
REFERRALS: z.optional(z.boolean()),
|
||||||
UPDATE_MARKET_STATE: z.optional(z.boolean()),
|
UPDATE_MARKET_STATE: z.optional(z.boolean()),
|
||||||
GOVERNANCE_TRANSFERS: z.optional(z.boolean()),
|
GOVERNANCE_TRANSFERS: z.optional(z.boolean()),
|
||||||
|
VOLUME_DISCOUNTS: z.optional(z.boolean()),
|
||||||
};
|
};
|
||||||
|
|
||||||
const EXPLORER_FLAGS = {
|
const EXPLORER_FLAGS = {
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
fragment TradeFeeFields on TradeFee {
|
||||||
|
makerFee
|
||||||
|
infrastructureFee
|
||||||
|
liquidityFee
|
||||||
|
makerFeeReferralDiscount
|
||||||
|
makerFeeVolumeDiscount
|
||||||
|
infrastructureFeeReferralDiscount
|
||||||
|
infrastructureFeeVolumeDiscount
|
||||||
|
liquidityFeeReferralDiscount
|
||||||
|
liquidityFeeVolumeDiscount
|
||||||
|
}
|
||||||
|
|
||||||
fragment FillFields on Trade {
|
fragment FillFields on Trade {
|
||||||
id
|
id
|
||||||
market {
|
market {
|
||||||
@@ -16,14 +28,10 @@ fragment FillFields on Trade {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
buyerFee {
|
buyerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
sellerFee {
|
sellerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,14 +69,10 @@ fragment FillUpdateFields on TradeUpdate {
|
|||||||
createdAt
|
createdAt
|
||||||
type
|
type
|
||||||
buyerFee {
|
buyerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
sellerFee {
|
sellerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-19
@@ -3,9 +3,11 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
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<{
|
export type FillsQueryVariables = Types.Exact<{
|
||||||
filter?: Types.InputMaybe<Types.TradesFilter>;
|
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<{
|
export type FillsEventSubscriptionVariables = Types.Exact<{
|
||||||
filter: Types.TradesSubscriptionFilter;
|
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`
|
export const FillFieldsFragmentDoc = gql`
|
||||||
fragment FillFields on Trade {
|
fragment FillFields on Trade {
|
||||||
id
|
id
|
||||||
@@ -43,17 +58,13 @@ export const FillFieldsFragmentDoc = gql`
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
buyerFee {
|
buyerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
sellerFee {
|
sellerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${TradeFeeFieldsFragmentDoc}`;
|
||||||
export const FillEdgeFragmentDoc = gql`
|
export const FillEdgeFragmentDoc = gql`
|
||||||
fragment FillEdge on TradeEdge {
|
fragment FillEdge on TradeEdge {
|
||||||
node {
|
node {
|
||||||
@@ -76,17 +87,13 @@ export const FillUpdateFieldsFragmentDoc = gql`
|
|||||||
createdAt
|
createdAt
|
||||||
type
|
type
|
||||||
buyerFee {
|
buyerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
sellerFee {
|
sellerFee {
|
||||||
makerFee
|
...TradeFeeFields
|
||||||
infrastructureFee
|
|
||||||
liquidityFee
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${TradeFeeFieldsFragmentDoc}`;
|
||||||
export const FillsDocument = gql`
|
export const FillsDocument = gql`
|
||||||
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||||
trades(filter: $filter, pagination: $pagination) {
|
trades(filter: $filter, pagination: $pagination) {
|
||||||
|
|||||||
@@ -4,14 +4,17 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
|
|||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
import type { Trade } from './fills-data-provider';
|
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 { generateFill } from './test-helpers';
|
||||||
|
import type { TradeFeeFieldsFragment } from './__generated__/Fills';
|
||||||
|
|
||||||
describe('FillsTable', () => {
|
const partyId = 'party-id';
|
||||||
let defaultFill: PartialDeep<Trade>;
|
const defaultFill: PartialDeep<Trade> = {
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
defaultFill = {
|
|
||||||
price: '100',
|
price: '100',
|
||||||
size: '300000',
|
size: '300000',
|
||||||
market: {
|
market: {
|
||||||
@@ -32,8 +35,7 @@ describe('FillsTable', () => {
|
|||||||
},
|
},
|
||||||
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 () => {
|
it('correct columns are rendered', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
|
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
|
||||||
@@ -47,6 +49,7 @@ describe('FillsTable', () => {
|
|||||||
'Notional',
|
'Notional',
|
||||||
'Role',
|
'Role',
|
||||||
'Fee',
|
'Fee',
|
||||||
|
'Fee Discount',
|
||||||
'Date',
|
'Date',
|
||||||
'', // action column
|
'', // action column
|
||||||
];
|
];
|
||||||
@@ -55,7 +58,6 @@ describe('FillsTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('formats cells correctly for buyer fill', async () => {
|
it('formats cells correctly for buyer fill', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const buyerFill = generateFill({
|
const buyerFill = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
buyer: {
|
buyer: {
|
||||||
@@ -68,7 +70,9 @@ describe('FillsTable', () => {
|
|||||||
liquidityFee: '2',
|
liquidityFee: '2',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
|
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
|
||||||
|
});
|
||||||
const cells = screen.getAllByRole('gridcell');
|
const cells = screen.getAllByRole('gridcell');
|
||||||
const expectedValues = [
|
const expectedValues = [
|
||||||
buyerFill.market?.tradableInstrument.instrument.code || '',
|
buyerFill.market?.tradableInstrument.instrument.code || '',
|
||||||
@@ -77,6 +81,7 @@ describe('FillsTable', () => {
|
|||||||
'3.00 BTC',
|
'3.00 BTC',
|
||||||
'Maker',
|
'Maker',
|
||||||
'2.00 BTC',
|
'2.00 BTC',
|
||||||
|
'0.27 BTC',
|
||||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||||
'', // action column
|
'', // action column
|
||||||
];
|
];
|
||||||
@@ -89,7 +94,6 @@ describe('FillsTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should format cells correctly for seller fill', async () => {
|
it('should format cells correctly for seller fill', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const buyerFill = generateFill({
|
const buyerFill = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
seller: {
|
seller: {
|
||||||
@@ -102,7 +106,9 @@ describe('FillsTable', () => {
|
|||||||
liquidityFee: '1',
|
liquidityFee: '1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||||
|
});
|
||||||
|
|
||||||
const cells = screen.getAllByRole('gridcell');
|
const cells = screen.getAllByRole('gridcell');
|
||||||
const expectedValues = [
|
const expectedValues = [
|
||||||
@@ -112,6 +118,7 @@ describe('FillsTable', () => {
|
|||||||
'3.00 BTC',
|
'3.00 BTC',
|
||||||
'Taker',
|
'Taker',
|
||||||
'0.03 BTC',
|
'0.03 BTC',
|
||||||
|
'0.27 BTC',
|
||||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||||
'', // action column
|
'', // action column
|
||||||
];
|
];
|
||||||
@@ -124,7 +131,6 @@ describe('FillsTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should format cells correctly for side unspecified', async () => {
|
it('should format cells correctly for side unspecified', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const buyerFill = generateFill({
|
const buyerFill = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
seller: {
|
seller: {
|
||||||
@@ -137,7 +143,9 @@ describe('FillsTable', () => {
|
|||||||
liquidityFee: '1',
|
liquidityFee: '1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||||
|
});
|
||||||
|
|
||||||
const cells = screen.getAllByRole('gridcell');
|
const cells = screen.getAllByRole('gridcell');
|
||||||
const expectedValues = [
|
const expectedValues = [
|
||||||
@@ -147,6 +155,7 @@ describe('FillsTable', () => {
|
|||||||
'3.00 BTC',
|
'3.00 BTC',
|
||||||
'-',
|
'-',
|
||||||
'0.03 BTC',
|
'0.03 BTC',
|
||||||
|
'0.27 BTC',
|
||||||
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
getDateTimeFormat().format(new Date(buyerFill.createdAt)),
|
||||||
'', // action column
|
'', // action column
|
||||||
];
|
];
|
||||||
@@ -158,30 +167,31 @@ describe('FillsTable', () => {
|
|||||||
expect(amountCell).toHaveClass('text-market-red');
|
expect(amountCell).toHaveClass('text-market-red');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render correct maker or taker role', async () => {
|
it('should render correct taker role', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const takerFill = generateFill({
|
const takerFill = generateFill({
|
||||||
seller: {
|
seller: {
|
||||||
id: partyId,
|
id: partyId,
|
||||||
},
|
},
|
||||||
aggressor: Schema.Side.SIDE_SELL,
|
aggressor: Schema.Side.SIDE_SELL,
|
||||||
});
|
});
|
||||||
const { rerender } = render(
|
await act(async () => {
|
||||||
<FillsTable partyId={partyId} rowData={[takerFill]} />
|
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||||
);
|
});
|
||||||
expect(
|
expect(
|
||||||
screen
|
screen
|
||||||
.getAllByRole('gridcell')
|
.getAllByRole('gridcell')
|
||||||
.find((c) => c.getAttribute('col-id') === 'aggressor')
|
.find((c) => c.getAttribute('col-id') === 'aggressor')
|
||||||
).toHaveTextContent('Taker');
|
).toHaveTextContent('Taker');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct maker role', async () => {
|
||||||
const makerFill = generateFill({
|
const makerFill = generateFill({
|
||||||
seller: {
|
seller: {
|
||||||
id: partyId,
|
id: partyId,
|
||||||
},
|
},
|
||||||
aggressor: Schema.Side.SIDE_BUY,
|
aggressor: Schema.Side.SIDE_BUY,
|
||||||
});
|
});
|
||||||
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
|
render(<FillsTable partyId={partyId} rowData={[makerFill]} />);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen
|
screen
|
||||||
@@ -191,22 +201,19 @@ describe('FillsTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render tooltip over fees', async () => {
|
it('should render tooltip over fees', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const takerFill = generateFill({
|
const takerFill = generateFill({
|
||||||
seller: {
|
seller: {
|
||||||
id: partyId,
|
id: partyId,
|
||||||
},
|
},
|
||||||
aggressor: Schema.Side.SIDE_SELL,
|
aggressor: Schema.Side.SIDE_SELL,
|
||||||
});
|
});
|
||||||
|
await act(async () => {
|
||||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||||
|
});
|
||||||
|
|
||||||
const feeCell = screen
|
const feeCell = screen
|
||||||
.getAllByRole('gridcell')
|
.getAllByRole('gridcell')
|
||||||
.find(
|
.find((c) => c.getAttribute('col-id') === 'fee');
|
||||||
(c) =>
|
|
||||||
c.getAttribute('col-id') ===
|
|
||||||
'market.tradableInstrument.instrument.product'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(feeCell).toBeInTheDocument();
|
expect(feeCell).toBeInTheDocument();
|
||||||
await userEvent.hover(feeCell as HTMLElement);
|
await userEvent.hover(feeCell as HTMLElement);
|
||||||
@@ -215,8 +222,29 @@ describe('FillsTable', () => {
|
|||||||
).toBeInTheDocument();
|
).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 () => {
|
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||||
const partyId = 'party-id';
|
|
||||||
const negativeDecimalPositionFill = generateFill({
|
const negativeDecimalPositionFill = generateFill({
|
||||||
...defaultFill,
|
...defaultFill,
|
||||||
market: {
|
market: {
|
||||||
@@ -235,6 +263,38 @@ describe('FillsTable', () => {
|
|||||||
.find((c) => c.getAttribute('col-id') === 'size');
|
.find((c) => c.getAttribute('col-id') === 'size');
|
||||||
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('FeesDiscountBreakdownTooltip', () => {
|
||||||
|
it('shows all discounts', () => {
|
||||||
|
const data = generateFill({
|
||||||
|
...defaultFill,
|
||||||
|
buyer: {
|
||||||
|
id: partyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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', () => {
|
describe('getFeesBreakdown', () => {
|
||||||
it('should return correct fees breakdown for a taker', () => {
|
it('should return correct fees breakdown for a taker', () => {
|
||||||
@@ -267,4 +327,19 @@ describe('FillsTable', () => {
|
|||||||
expect(getFeesBreakdown('Maker', fees)).toEqual(expectedBreakdown);
|
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 { forwardRef } from 'react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import type { Trade } from './fills-data-provider';
|
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 { FillActionsDropdown } from './fill-actions-dropdown';
|
||||||
import { getAsset } from '@vegaprotocol/markets';
|
import { getAsset } from '@vegaprotocol/markets';
|
||||||
|
|
||||||
@@ -87,13 +90,34 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t('Fee'),
|
headerName: t('Fee'),
|
||||||
field: 'market.tradableInstrument.instrument.product',
|
colId: 'fee',
|
||||||
|
field: 'market',
|
||||||
valueFormatter: formatFee(partyId),
|
valueFormatter: formatFee(partyId),
|
||||||
type: 'rightAligned',
|
type: 'rightAligned',
|
||||||
tooltipField: 'market.tradableInstrument.instrument.product',
|
tooltipField: 'market',
|
||||||
tooltipComponent: FeesBreakdownTooltip,
|
tooltipComponent: FeesBreakdownTooltip,
|
||||||
tooltipComponentParams: { partyId },
|
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'),
|
headerName: t('Date'),
|
||||||
field: 'createdAt',
|
field: 'createdAt',
|
||||||
@@ -214,14 +238,11 @@ const formatRole = (partyId: string) => {
|
|||||||
|
|
||||||
const formatFee = (partyId: string) => {
|
const formatFee = (partyId: string) => {
|
||||||
return ({
|
return ({
|
||||||
value,
|
value: market,
|
||||||
data,
|
data,
|
||||||
}: VegaValueFormatterParams<
|
}: VegaValueFormatterParams<Trade, 'market'>) => {
|
||||||
Trade,
|
if (!market || !data) return '-';
|
||||||
'market.tradableInstrument.instrument.product'
|
const asset = getAsset(market);
|
||||||
>) => {
|
|
||||||
if (!value || !data || !data?.market) return '-';
|
|
||||||
const asset = getAsset(data.market);
|
|
||||||
const { fees: feesObj, role } = getRoleAndFees({ data, partyId });
|
const { fees: feesObj, role } = getRoleAndFees({ data, partyId });
|
||||||
if (!feesObj) return '-';
|
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) => {
|
export const isEmptyFeeObj = (feeObj: Schema.TradeFee) => {
|
||||||
if (!feeObj) return true;
|
if (!feeObj) return true;
|
||||||
return (
|
return (
|
||||||
@@ -251,50 +287,50 @@ export const getRoleAndFees = ({
|
|||||||
partyId?: string;
|
partyId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let role: Role;
|
let role: Role;
|
||||||
let feesObj;
|
let fees;
|
||||||
if (data?.buyer.id === partyId) {
|
if (data?.buyer.id === partyId) {
|
||||||
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||||
role = TAKER;
|
role = TAKER;
|
||||||
feesObj = data?.buyerFee;
|
fees = data?.buyerFee;
|
||||||
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
|
} else if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||||
role = MAKER;
|
role = MAKER;
|
||||||
feesObj = data?.sellerFee;
|
fees = data?.sellerFee;
|
||||||
} else {
|
} else {
|
||||||
role = '-';
|
role = '-';
|
||||||
feesObj = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
fees = !isEmptyFeeObj(data?.buyerFee) ? data.buyerFee : data.sellerFee;
|
||||||
}
|
}
|
||||||
} else if (data?.seller.id === partyId) {
|
} else if (data?.seller.id === partyId) {
|
||||||
if (data.aggressor === Schema.Side.SIDE_SELL) {
|
if (data.aggressor === Schema.Side.SIDE_SELL) {
|
||||||
role = TAKER;
|
role = TAKER;
|
||||||
feesObj = data?.sellerFee;
|
fees = data?.sellerFee;
|
||||||
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
|
} else if (data.aggressor === Schema.Side.SIDE_BUY) {
|
||||||
role = MAKER;
|
role = MAKER;
|
||||||
feesObj = data?.buyerFee;
|
fees = data?.buyerFee;
|
||||||
} else {
|
} else {
|
||||||
role = '-';
|
role = '-';
|
||||||
feesObj = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
fees = !isEmptyFeeObj(data.sellerFee) ? data.sellerFee : data.buyerFee;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return { role: '-', feesObj: '-' };
|
return { role: '-', fees: undefined };
|
||||||
}
|
}
|
||||||
return { role, fees: feesObj };
|
return { role, fees };
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeesBreakdownTooltip = ({
|
const FeesBreakdownTooltip = ({
|
||||||
data,
|
data,
|
||||||
value,
|
value: market,
|
||||||
partyId,
|
partyId,
|
||||||
}: ITooltipParams & { partyId?: string }) => {
|
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
|
||||||
if (!value?.settlementAsset || !data) {
|
if (!market || !data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const asset = value.settlementAsset;
|
const asset = getAsset(market);
|
||||||
|
|
||||||
const { role, fees: feesObj } = getRoleAndFees({ data, partyId }) ?? {};
|
const { role, fees } = getRoleAndFees({ data, partyId }) ?? {};
|
||||||
if (!feesObj) return null;
|
if (!fees) return null;
|
||||||
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
|
const { infrastructureFee, liquidityFee, makerFee, totalFee } =
|
||||||
getFeesBreakdown(role, feesObj);
|
getFeesBreakdown(role, fees);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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 = (
|
export const getFeesBreakdown = (
|
||||||
role: Role,
|
role: Role,
|
||||||
feesObj: {
|
feesObj: TradeFeeFieldsFragment
|
||||||
__typename?: 'TradeFee' | undefined;
|
|
||||||
makerFee: string;
|
|
||||||
infrastructureFee: string;
|
|
||||||
liquidityFee: string;
|
|
||||||
}
|
|
||||||
) => {
|
) => {
|
||||||
const makerFee =
|
const makerFee =
|
||||||
role === MAKER
|
role === MAKER
|
||||||
|
|||||||
@@ -28,12 +28,24 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
|
|||||||
makerFee: '100',
|
makerFee: '100',
|
||||||
infrastructureFee: '100',
|
infrastructureFee: '100',
|
||||||
liquidityFee: '100',
|
liquidityFee: '100',
|
||||||
|
liquidityFeeReferralDiscount: '1',
|
||||||
|
liquidityFeeVolumeDiscount: '2',
|
||||||
|
makerFeeReferralDiscount: '3',
|
||||||
|
makerFeeVolumeDiscount: '4',
|
||||||
|
infrastructureFeeReferralDiscount: '5',
|
||||||
|
infrastructureFeeVolumeDiscount: '6',
|
||||||
},
|
},
|
||||||
sellerFee: {
|
sellerFee: {
|
||||||
__typename: 'TradeFee',
|
__typename: 'TradeFee',
|
||||||
makerFee: '200',
|
makerFee: '200',
|
||||||
infrastructureFee: '200',
|
infrastructureFee: '200',
|
||||||
liquidityFee: '200',
|
liquidityFee: '200',
|
||||||
|
liquidityFeeReferralDiscount: '2',
|
||||||
|
liquidityFeeVolumeDiscount: '3',
|
||||||
|
makerFeeReferralDiscount: '4',
|
||||||
|
makerFeeVolumeDiscount: '5',
|
||||||
|
infrastructureFeeReferralDiscount: '6',
|
||||||
|
infrastructureFeeVolumeDiscount: '7',
|
||||||
},
|
},
|
||||||
market: {
|
market: {
|
||||||
__typename: 'Market',
|
__typename: 'Market',
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"sourceType": "unambiguous",
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nx/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*", "__generated__"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# funding-payments
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test funding-payments` to execute the unit tests via [Jest](https://jestjs.io).
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
function ReactMarkdown({ children }) {
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReactMarkdown;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'funding-payments',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
globals: {},
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/libs/funding-payments',
|
||||||
|
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
const { join } = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {
|
||||||
|
config: join(__dirname, 'tailwind.config.js'),
|
||||||
|
},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "funding-payments",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/funding-payments/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nx/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/funding-payments/**/*.{ts,tsx,js,jsx}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nx/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/libs/funding-payments"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/funding-payments/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"ci": true,
|
||||||
|
"codeCoverage": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"build-spec": {
|
||||||
|
"executor": "nx:run-commands",
|
||||||
|
"outputs": [],
|
||||||
|
"options": {
|
||||||
|
"command": "yarn tsc --project ./libs/funding-payments/tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './lib/funding-payments-manager';
|
||||||
|
export * from './lib/funding-payments-data-provider';
|
||||||
|
export * from './lib/__generated__/FundingPayments';
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
fragment FundingPaymentFields on FundingPayment {
|
||||||
|
marketId
|
||||||
|
partyId
|
||||||
|
fundingPeriodSeq
|
||||||
|
amount
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
query FundingPayments($partyId: ID!, $pagination: Pagination) {
|
||||||
|
fundingPayments(partyId: $partyId, pagination: $pagination) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...FundingPaymentFields
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type FundingPaymentFieldsFragment = { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any };
|
||||||
|
|
||||||
|
export type FundingPaymentsQueryVariables = Types.Exact<{
|
||||||
|
partyId: Types.Scalars['ID'];
|
||||||
|
pagination?: Types.InputMaybe<Types.Pagination>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type FundingPaymentsQuery = { __typename?: 'Query', fundingPayments: { __typename?: 'FundingPaymentConnection', edges: Array<{ __typename?: 'FundingPaymentEdge', cursor: string, node: { __typename?: 'FundingPayment', marketId: string, partyId: string, fundingPeriodSeq: number, amount?: string | null, timestamp: any } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
|
||||||
|
|
||||||
|
export const FundingPaymentFieldsFragmentDoc = gql`
|
||||||
|
fragment FundingPaymentFields on FundingPayment {
|
||||||
|
marketId
|
||||||
|
partyId
|
||||||
|
fundingPeriodSeq
|
||||||
|
amount
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const FundingPaymentsDocument = gql`
|
||||||
|
query FundingPayments($partyId: ID!, $pagination: Pagination) {
|
||||||
|
fundingPayments(partyId: $partyId, pagination: $pagination) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...FundingPaymentFields
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${FundingPaymentFieldsFragmentDoc}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFundingPaymentsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFundingPaymentsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFundingPaymentsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useFundingPaymentsQuery({
|
||||||
|
* variables: {
|
||||||
|
* partyId: // value for 'partyId'
|
||||||
|
* pagination: // value for 'pagination'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFundingPaymentsQuery(baseOptions: Apollo.QueryHookOptions<FundingPaymentsQuery, FundingPaymentsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FundingPaymentsQuery, FundingPaymentsQueryVariables>(FundingPaymentsDocument, options);
|
||||||
|
}
|
||||||
|
export function useFundingPaymentsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FundingPaymentsQuery, FundingPaymentsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FundingPaymentsQuery, FundingPaymentsQueryVariables>(FundingPaymentsDocument, options);
|
||||||
|
}
|
||||||
|
export type FundingPaymentsQueryHookResult = ReturnType<typeof useFundingPaymentsQuery>;
|
||||||
|
export type FundingPaymentsLazyQueryHookResult = ReturnType<typeof useFundingPaymentsLazyQuery>;
|
||||||
|
export type FundingPaymentsQueryResult = Apollo.QueryResult<FundingPaymentsQuery, FundingPaymentsQueryVariables>;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
||||||
|
import {
|
||||||
|
makeDataProvider,
|
||||||
|
makeDerivedDataProvider,
|
||||||
|
defaultAppend as append,
|
||||||
|
} from '@vegaprotocol/data-provider';
|
||||||
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
|
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||||
|
import { FundingPaymentsDocument } from './__generated__/FundingPayments';
|
||||||
|
import type {
|
||||||
|
FundingPaymentsQuery,
|
||||||
|
FundingPaymentsQueryVariables,
|
||||||
|
FundingPaymentFieldsFragment,
|
||||||
|
} from './__generated__/FundingPayments';
|
||||||
|
|
||||||
|
export type FundingPayment = Omit<FundingPaymentFieldsFragment, 'market'> & {
|
||||||
|
market?: Market;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = (
|
||||||
|
responseData: FundingPaymentsQuery | null
|
||||||
|
): (FundingPaymentFieldsFragment & Cursor)[] =>
|
||||||
|
responseData?.fundingPayments?.edges.map<
|
||||||
|
FundingPaymentFieldsFragment & Cursor
|
||||||
|
>((edge) => ({
|
||||||
|
...edge.node,
|
||||||
|
cursor: edge.cursor,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const getPageInfo = (
|
||||||
|
responseData: FundingPaymentsQuery | null
|
||||||
|
): PageInfo | null => responseData?.fundingPayments?.pageInfo || null;
|
||||||
|
|
||||||
|
export const fundingPaymentsProvider = makeDataProvider<
|
||||||
|
Parameters<typeof getData>['0'],
|
||||||
|
ReturnType<typeof getData>,
|
||||||
|
never,
|
||||||
|
never,
|
||||||
|
FundingPaymentsQueryVariables
|
||||||
|
>({
|
||||||
|
query: FundingPaymentsDocument,
|
||||||
|
getData,
|
||||||
|
pagination: {
|
||||||
|
getPageInfo,
|
||||||
|
append,
|
||||||
|
first: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fundingPaymentsWithMarketProvider = makeDerivedDataProvider<
|
||||||
|
FundingPayment[],
|
||||||
|
never,
|
||||||
|
FundingPaymentsQueryVariables
|
||||||
|
>(
|
||||||
|
[
|
||||||
|
fundingPaymentsProvider,
|
||||||
|
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||||
|
],
|
||||||
|
(partsData): FundingPayment[] | null => {
|
||||||
|
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||||
|
(fundingPayment) => ({
|
||||||
|
...fundingPayment,
|
||||||
|
market: (partsData[1] as Record<string, Market>)[
|
||||||
|
fundingPayment.marketId
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { FundingPaymentsTable } from './funding-payments-table';
|
||||||
|
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||||
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider';
|
||||||
|
|
||||||
|
interface FundingPaymentsManagerProps {
|
||||||
|
partyId: string;
|
||||||
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FundingPaymentsManager = ({
|
||||||
|
partyId,
|
||||||
|
onMarketClick,
|
||||||
|
gridProps,
|
||||||
|
}: FundingPaymentsManagerProps) => {
|
||||||
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
|
const { data, error } = useDataProvider({
|
||||||
|
dataProvider: fundingPaymentsWithMarketProvider,
|
||||||
|
update: ({ data }) => {
|
||||||
|
if (data?.length && gridRef.current?.api) {
|
||||||
|
gridRef.current?.api.setRowData(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
variables: { partyId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FundingPaymentsTable
|
||||||
|
ref={gridRef}
|
||||||
|
rowData={data}
|
||||||
|
onMarketClick={onMarketClick}
|
||||||
|
overlayNoRowsTemplate={error ? error.message : t('No funding payments')}
|
||||||
|
{...gridProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import { act, render, screen } from '@testing-library/react';
|
||||||
|
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import type { FundingPayment } from './funding-payments-data-provider';
|
||||||
|
import { FundingPaymentsTable } from './funding-payments-table';
|
||||||
|
import { generateFundingPayment } from './test-helpers';
|
||||||
|
|
||||||
|
describe('FundingPaymentsTable', () => {
|
||||||
|
let defaultFundingPayment: PartialDeep<FundingPayment>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
defaultFundingPayment = {
|
||||||
|
marketId:
|
||||||
|
'69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c',
|
||||||
|
partyId:
|
||||||
|
'02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||||
|
fundingPeriodSeq: 84,
|
||||||
|
amount: '100',
|
||||||
|
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||||
|
market: {
|
||||||
|
decimalPlaces: 2,
|
||||||
|
positionDecimalPlaces: 5,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
code: 'test market',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
settlementAsset: {
|
||||||
|
decimals: 2,
|
||||||
|
symbol: 'BTC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correct columns are rendered', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(<FundingPaymentsTable rowData={[generateFundingPayment()]} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = screen.getAllByRole('columnheader');
|
||||||
|
const expectedHeaders = ['Market', 'Amount', 'Date'];
|
||||||
|
expect(headers).toHaveLength(expectedHeaders.length);
|
||||||
|
expect(headers.map((h) => h.textContent?.trim())).toEqual(expectedHeaders);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats positive cells', async () => {
|
||||||
|
const fundingPayment = generateFundingPayment({
|
||||||
|
...defaultFundingPayment,
|
||||||
|
});
|
||||||
|
render(<FundingPaymentsTable rowData={[fundingPayment]} />);
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues = [
|
||||||
|
fundingPayment.market?.tradableInstrument.instrument.code || '',
|
||||||
|
'1.00 BTC',
|
||||||
|
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount');
|
||||||
|
expect(
|
||||||
|
amountCell?.querySelector('.ag-cell-value')?.firstElementChild
|
||||||
|
).toHaveClass('text-market-green-600');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats negative cells', async () => {
|
||||||
|
const fundingPayment = generateFundingPayment({
|
||||||
|
...defaultFundingPayment,
|
||||||
|
amount: `-${defaultFundingPayment.amount}`,
|
||||||
|
});
|
||||||
|
render(<FundingPaymentsTable rowData={[fundingPayment]} />);
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues = [
|
||||||
|
fundingPayment.market?.tradableInstrument.instrument.code || '',
|
||||||
|
'-1.00 BTC',
|
||||||
|
getDateTimeFormat().format(new Date(fundingPayment.timestamp)),
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'amount');
|
||||||
|
expect(
|
||||||
|
amountCell?.querySelector('.ag-cell-value')?.firstElementChild
|
||||||
|
).toHaveClass('text-market-red dark:text-market-red');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import type {
|
||||||
|
AgGridReact,
|
||||||
|
AgGridReactProps,
|
||||||
|
AgReactUiProps,
|
||||||
|
} from 'ag-grid-react';
|
||||||
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
getDateTimeFormat,
|
||||||
|
isNumeric,
|
||||||
|
toBigNum,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AgGrid,
|
||||||
|
DateRangeFilter,
|
||||||
|
MarketNameCell,
|
||||||
|
negativeClassNames,
|
||||||
|
positiveClassNames,
|
||||||
|
} from '@vegaprotocol/datagrid';
|
||||||
|
import type {
|
||||||
|
VegaValueFormatterParams,
|
||||||
|
VegaValueGetterParams,
|
||||||
|
} from '@vegaprotocol/datagrid';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import type { FundingPayment } from './funding-payments-data-provider';
|
||||||
|
|
||||||
|
import { getAsset } from '@vegaprotocol/markets';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const defaultColDef = {
|
||||||
|
resizable: true,
|
||||||
|
sortable: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||||
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatAmount = ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<FundingPayment, 'amount'>) => {
|
||||||
|
if (!data?.market || !isNumeric(value)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const { symbol: assetSymbol, decimals: assetDecimals } = getAsset(
|
||||||
|
data.market
|
||||||
|
);
|
||||||
|
const valueFormatted = addDecimalsFormatNumber(value, assetDecimals);
|
||||||
|
return `${valueFormatted} ${assetSymbol}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FundingPaymentsTable = forwardRef<AgGridReact, Props>(
|
||||||
|
({ onMarketClick, ...props }, ref) => {
|
||||||
|
const columnDefs = useMemo<ColDef[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
headerName: t('Market'),
|
||||||
|
field: 'market.tradableInstrument.instrument.code',
|
||||||
|
cellRenderer: 'MarketNameCell',
|
||||||
|
filter: true,
|
||||||
|
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Amount'),
|
||||||
|
field: 'amount',
|
||||||
|
valueFormatter: formatAmount,
|
||||||
|
type: 'rightAligned',
|
||||||
|
filter: 'agNumberColumnFilter',
|
||||||
|
valueGetter: ({ data }: VegaValueGetterParams<FundingPayment>) =>
|
||||||
|
data?.amount && data?.market
|
||||||
|
? toBigNum(data.amount, getAsset(data.market).decimals).toNumber()
|
||||||
|
: 0,
|
||||||
|
cellRenderer: ({ data }: { data: FundingPayment }) => {
|
||||||
|
if (!data?.market || !isNumeric(data.amount)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const { symbol: assetSymbol, decimals: assetDecimals } = getAsset(
|
||||||
|
data.market
|
||||||
|
);
|
||||||
|
const valueFormatted = addDecimalsFormatNumber(
|
||||||
|
data.amount,
|
||||||
|
assetDecimals
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={classNames({
|
||||||
|
[positiveClassNames]: !data?.amount?.startsWith('-'),
|
||||||
|
[negativeClassNames]: !!data?.amount?.startsWith('-'),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{valueFormatted}
|
||||||
|
</span>
|
||||||
|
{` ${assetSymbol}`}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Date'),
|
||||||
|
field: 'timestamp',
|
||||||
|
type: 'rightAligned',
|
||||||
|
filter: DateRangeFilter,
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<FundingPayment, 'timestamp'>) => {
|
||||||
|
return value ? getDateTimeFormat().format(new Date(value)) : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[onMarketClick]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<AgGrid
|
||||||
|
ref={ref}
|
||||||
|
defaultColDef={defaultColDef}
|
||||||
|
columnDefs={columnDefs}
|
||||||
|
overlayNoRowsTemplate={t('No funding payments')}
|
||||||
|
getRowId={({ data }: { data?: FundingPayment }) =>
|
||||||
|
`${data?.marketId}-${data?.fundingPeriodSeq}`
|
||||||
|
}
|
||||||
|
components={{ MarketNameCell }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import type {
|
||||||
|
FundingPaymentsQuery,
|
||||||
|
FundingPaymentFieldsFragment,
|
||||||
|
} from './__generated__/FundingPayments';
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
|
||||||
|
export const fundingPaymentsQuery = (
|
||||||
|
override?: PartialDeep<FundingPaymentsQuery>,
|
||||||
|
vegaPublicKey?: string
|
||||||
|
): FundingPaymentsQuery => {
|
||||||
|
const defaultResult: FundingPaymentsQuery = {
|
||||||
|
fundingPayments: {
|
||||||
|
__typename: 'FundingPaymentConnection',
|
||||||
|
edges: fundingPayments(vegaPublicKey).map((node) => ({
|
||||||
|
__typename: 'FundingPaymentEdge',
|
||||||
|
cursor: '3',
|
||||||
|
node,
|
||||||
|
})),
|
||||||
|
pageInfo: {
|
||||||
|
__typename: 'PageInfo',
|
||||||
|
startCursor: '1',
|
||||||
|
endCursor: '2',
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultResult, override);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateFundingPayment = (
|
||||||
|
override?: PartialDeep<FundingPaymentFieldsFragment>
|
||||||
|
) => {
|
||||||
|
const defaultFundingPayment: FundingPaymentFieldsFragment = {
|
||||||
|
marketId: 'market-0',
|
||||||
|
partyId: 'partyId',
|
||||||
|
fundingPeriodSeq: 84,
|
||||||
|
amount: '126973',
|
||||||
|
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultFundingPayment, override);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fundingPayments = (
|
||||||
|
partyId = 'partyId'
|
||||||
|
): FundingPaymentFieldsFragment[] => [
|
||||||
|
generateFundingPayment({
|
||||||
|
partyId,
|
||||||
|
fundingPeriodSeq: 78,
|
||||||
|
amount: '92503',
|
||||||
|
timestamp: '2023-10-06T04:06:43.652759Z',
|
||||||
|
}),
|
||||||
|
generateFundingPayment({
|
||||||
|
partyId,
|
||||||
|
fundingPeriodSeq: 77,
|
||||||
|
amount: '-37841',
|
||||||
|
timestamp: '2023-10-06T03:36:43.437139Z',
|
||||||
|
}),
|
||||||
|
generateFundingPayment({
|
||||||
|
partyId,
|
||||||
|
fundingPeriodSeq: 76,
|
||||||
|
amount: '32838',
|
||||||
|
timestamp: '2023-10-06T03:06:43.430384Z',
|
||||||
|
}),
|
||||||
|
generateFundingPayment({
|
||||||
|
partyId,
|
||||||
|
fundingPeriodSeq: 75,
|
||||||
|
amount: '-298259',
|
||||||
|
timestamp: '2023-10-06T02:36:43.51153Z',
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { FundingPayment } from './funding-payments-data-provider';
|
||||||
|
|
||||||
|
const { MarketState, MarketTradingMode } = Schema;
|
||||||
|
|
||||||
|
export const generateFundingPayment = (
|
||||||
|
override?: PartialDeep<FundingPayment>
|
||||||
|
) => {
|
||||||
|
const defaultFundingPayment: FundingPayment = {
|
||||||
|
marketId:
|
||||||
|
'69abf5c456c20f4d189cea79a11dfd6b0958ead58ab34bd66f73eea48aee600c',
|
||||||
|
partyId: '02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65',
|
||||||
|
fundingPeriodSeq: 84,
|
||||||
|
amount: '126973',
|
||||||
|
timestamp: '2023-10-06T07:06:43.020994Z',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: 'market-id',
|
||||||
|
positionDecimalPlaces: 0,
|
||||||
|
decimalPlaces: 5,
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||||
|
liquidityMonitoringParameters: {
|
||||||
|
triggeringRatio: '1',
|
||||||
|
},
|
||||||
|
fees: {
|
||||||
|
__typename: 'Fees',
|
||||||
|
factors: {
|
||||||
|
__typename: 'FeeFactors',
|
||||||
|
infrastructureFee: '0.1',
|
||||||
|
liquidityFee: '0.1',
|
||||||
|
makerFee: '0.1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marketTimestamps: {
|
||||||
|
__typename: 'MarketTimestamps',
|
||||||
|
open: '2005-04-02T19:37:00.000Z',
|
||||||
|
close: '2005-04-02T19:37:00.000Z',
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
instrument: {
|
||||||
|
__typename: 'Instrument',
|
||||||
|
id: 'instrument-id',
|
||||||
|
code: 'instrument-code',
|
||||||
|
name: 'UNIDAI Monthly (30 Jun 2022)',
|
||||||
|
metadata: {
|
||||||
|
__typename: 'InstrumentMetadata',
|
||||||
|
tags: ['tag-a'],
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
settlementAsset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: 'asset-id',
|
||||||
|
name: 'asset-id',
|
||||||
|
symbol: 'SYM',
|
||||||
|
decimals: 18,
|
||||||
|
quantum: '1',
|
||||||
|
},
|
||||||
|
quoteName: '',
|
||||||
|
dataSourceSpecForTradingTermination: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
data: {
|
||||||
|
__typename: 'DataSourceDefinition',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: 'oracleId',
|
||||||
|
data: {
|
||||||
|
__typename: 'DataSourceDefinition',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
tradingTerminationProperty: 'trading-termination-property',
|
||||||
|
settlementDataProperty: 'settlement-data-property',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultFundingPayment, override);
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
|
||||||
|
global.ResizeObserver = ResizeObserver;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user