Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
503e68cd61 | ||
|
|
9dc9588a14 | ||
|
|
a0844d41bf | ||
|
|
3d1aa74128 | ||
|
|
2376bfe58f | ||
|
|
72ccf96523 | ||
|
|
f416fa4a25 | ||
|
|
a24e3646cc | ||
|
|
55b092b216 | ||
|
|
ef4cf6a87e | ||
|
|
c9860a3412 | ||
|
|
127bd3faf3 | ||
|
|
1791b3ca6a | ||
|
|
6044b86ff4 | ||
|
|
341d78e761 | ||
|
|
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'
|
||||
uses: ./.github/workflows/console-test-run.yml
|
||||
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:
|
||||
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
|
||||
+1
-2
@@ -1,3 +1,2 @@
|
||||
* @vegaprotocol/frontend
|
||||
* @vegaprotocol/frontend-qa
|
||||
* @vegaprotocol/frontend @vegaprotocol/frontend-qa
|
||||
*.graphql @vegaprotocol/core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
AppFailure,
|
||||
NetworkLoader,
|
||||
NodeFailure,
|
||||
NodeGuard,
|
||||
NodeSwitcherDialog,
|
||||
useEnvironment,
|
||||
@@ -31,7 +31,7 @@ function App() {
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Suspense fallback={splashLoading}>
|
||||
<RouterProvider router={router} fallbackElement={splashLoading} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import {
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
LiquiditySLAParametersInfoPanel,
|
||||
MarginScalingFactorsPanel,
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
getDataSourceSpecForSettlementData,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
OracleInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||
@@ -69,8 +69,8 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
<MetadataInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Margin scaling factors')}</h2>
|
||||
<MarginScalingFactorsPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel market={market} />
|
||||
{(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_SUCCESSOR_MARKETS=true
|
||||
NX_PRODUCT_PERPETUALS=true
|
||||
NX_REFERRALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_GOVERNANCE_TRANSFERS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
|
||||
@@ -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_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=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';
|
||||
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
|
||||
import {
|
||||
depositAsset,
|
||||
switchVegaWalletPubKey,
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
} from '../../support/wallet-functions';
|
||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||
import {
|
||||
createGovernanceTransferProposalTxBody,
|
||||
createSuccessorMarketProposalTxBody,
|
||||
} from '../../support/proposal.functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const participationNotMet = 'token-participation-not-met';
|
||||
@@ -51,6 +55,7 @@ const openProposals = 'open-proposals';
|
||||
const viewProposalButton = 'view-proposal-btn';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
const governanceTransferToggle = 'proposal-transfer-details';
|
||||
const marketProposalType = 'proposal-type';
|
||||
|
||||
describe(
|
||||
@@ -520,5 +525,78 @@ describe(
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to see governance transfer proposal', function () {
|
||||
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
|
||||
depositAsset(vegaAssetAddress, '1000', 18);
|
||||
cy.getByTestId('currency-title', Cypress.env('txTimeout')).should(
|
||||
'contain.text',
|
||||
'Collateral'
|
||||
);
|
||||
cy.VegaWalletTopUpNetworkAccount('100');
|
||||
cy.VegaWalletSubmitProposal(createGovernanceTransferProposalTxBody());
|
||||
cy.reload();
|
||||
getProposalFromTitle('Governance transfer proposal').within(() => {
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
|
||||
cy.getByTestId(governanceTransferToggle).click();
|
||||
cy.getByTestId('proposal-transfer-details-table').within(() => {
|
||||
getProposalInformationFromTable('Source Type')
|
||||
.invoke('text')
|
||||
.and('eq', 'Network Treasury');
|
||||
getProposalInformationFromTable('Destination')
|
||||
.invoke('text')
|
||||
.and('eq', Cypress.env('vegaWalletPublicKey'));
|
||||
getProposalInformationFromTable('Asset')
|
||||
.invoke('text')
|
||||
.and('eq', 'VEGA');
|
||||
getProposalInformationFromTable('Fraction Of Balance')
|
||||
.invoke('text')
|
||||
.and('eq', '50%');
|
||||
getProposalInformationFromTable('Amount')
|
||||
.invoke('text')
|
||||
.and('eq', '100.00');
|
||||
getProposalInformationFromTable('Transfer Type')
|
||||
.invoke('text')
|
||||
.and('eq', 'All or nothing');
|
||||
getProposalInformationFromTable('Kind')
|
||||
.invoke('text')
|
||||
.and('eq', 'One off');
|
||||
});
|
||||
});
|
||||
|
||||
it(' Able to see cancel transfer proposal - rejected', function () {
|
||||
const proposalPath = 'src/fixtures/proposals/cancel-transfer-raw.json';
|
||||
const enactmentTimestamp =
|
||||
createTenDigitUnixTimeStampForSpecifiedDays(11);
|
||||
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(10);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
closingTimestamp: closingTimestamp,
|
||||
submit: false,
|
||||
});
|
||||
cy.getByTestId('proposal-submit').should('be.visible').click();
|
||||
cy.getByTestId('dialog-title').should('have.text', 'Proposal rejected');
|
||||
cy.getByTestId('icon-cross').last().click();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get('[href="/proposals/rejected"]').click();
|
||||
getProposalFromTitle('Governance cancel transfer proposal').within(() => {
|
||||
cy.getByTestId(marketProposalType).should(
|
||||
'have.text',
|
||||
'CancelTransfer'
|
||||
);
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
cy.getByTestId(marketProposalType).should('have.text', 'CancelTransfer');
|
||||
getProposalInformationFromTable('Error details')
|
||||
.invoke('text')
|
||||
.and('eq', 'Governance transfer invalid transfer id not found');
|
||||
getProposalInformationFromTable('transferId')
|
||||
.invoke('text')
|
||||
.and('eq', 'invalid transfer id');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
import { addDays, addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { upgradeProposalsData } from '../fixtures/mocks/network-upgrade';
|
||||
import { proposalsData } from '../fixtures/mocks/proposals';
|
||||
import { nodeData } from '../fixtures/mocks/nodes';
|
||||
import { AccountType, GovernanceTransferType } from '@vegaprotocol/types';
|
||||
|
||||
export function createUpdateNetworkProposalTxBody(): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
@@ -358,6 +359,46 @@ export function createSuccessorMarketProposalTxBody(
|
||||
};
|
||||
}
|
||||
|
||||
export function createGovernanceTransferProposalTxBody(): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 5;
|
||||
const MIN_ENACT_SEC = 7;
|
||||
|
||||
const closingDate = addDays(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addDays(closingDate, MIN_ENACT_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
const destination = Cypress.env('vegaWalletPublicKey');
|
||||
return {
|
||||
proposalSubmission: {
|
||||
rationale: {
|
||||
title: 'Governance transfer proposal',
|
||||
description: 'E2E test for transfer proposal test',
|
||||
},
|
||||
terms: {
|
||||
newTransfer: {
|
||||
changes: {
|
||||
fractionOfBalance: '0.5',
|
||||
amount: '100' + '0'.repeat(18),
|
||||
sourceType: AccountType.ACCOUNT_TYPE_NETWORK_TREASURY,
|
||||
source: '',
|
||||
transferType:
|
||||
GovernanceTransferType.GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING,
|
||||
destinationType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
destination,
|
||||
asset:
|
||||
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b',
|
||||
oneOff: {
|
||||
deliverOn: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mockNetworkUpgradeProposal() {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||
|
||||
@@ -41,6 +41,9 @@ export async function depositAsset(
|
||||
) {
|
||||
// Approve asset
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
// Wait needed to allow Eth chain to catch up
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(4000);
|
||||
cy.wrap(
|
||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||
transactionTimeout
|
||||
|
||||
@@ -35,4 +35,6 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_GOVERNANCE_TRANSFERS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -35,3 +35,5 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=true
|
||||
NX_UPDATE_MARKET_STATE=true
|
||||
NX_REFERRALS=true
|
||||
NX_GOVERNANCE_TRANSFERS=true
|
||||
NX_VOLUME_DISCOUNTS=true
|
||||
|
||||
@@ -28,3 +28,5 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_PRODUCT_PERPETUALS=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_PRODUCT_PERPETUALS=false
|
||||
NX_UPDATE_MARKET_STATE=false
|
||||
NX_REFERRALS=false
|
||||
NX_VOLUME_DISCOUNTS=false
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable */
|
||||
process.env.TZ = 'GMT';
|
||||
export default {
|
||||
displayName: 'governance',
|
||||
preset: '../../jest.preset.js',
|
||||
|
||||
@@ -38,10 +38,10 @@ import {
|
||||
NetworkLoader,
|
||||
useInitializeEnv,
|
||||
NodeGuard,
|
||||
AppFailure,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
NodeFailure,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
@@ -184,7 +184,7 @@ const Web3Container = ({
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 border-t border-neutral-700 break-all">
|
||||
<footer className="p-4 break-all border-t border-neutral-700">
|
||||
<NetworkInfo />
|
||||
</footer>
|
||||
</AppLayout>
|
||||
@@ -312,7 +312,7 @@ const AppContainer = () => {
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
<AppFailure title={t('NodeUnsuitable', { url: VEGA_URL })} />
|
||||
<NodeFailure title={t('NodeUnsuitable', { url: VEGA_URL })} />
|
||||
}
|
||||
>
|
||||
<AsyncRenderer<EthereumConfig | null>
|
||||
|
||||
@@ -709,6 +709,8 @@
|
||||
"NewMarketProposal": "New market proposal",
|
||||
"UpdateMarketProposal": "Update market proposal",
|
||||
"UpdateMarketStateProposal": "Update market state proposal",
|
||||
"UpdateReferralProgramProposal": "Update referral program proposal",
|
||||
"UpdateVolumeDiscountProgramProposal": "Update volume discount program proposal",
|
||||
"MarketChange": "Market change",
|
||||
"MarketStateChange": "Market state change",
|
||||
"MarketDetails": "Market details",
|
||||
@@ -716,6 +718,8 @@
|
||||
"UpdateAssetProposal": "Update asset proposal",
|
||||
"NewFreeformProposal": "New freeform proposal",
|
||||
"NewRawProposal": "New proposal",
|
||||
"NewTransferProposal": "New transfer proposal",
|
||||
"CancelTransferProposal": "Cancel transfer proposal",
|
||||
"MARKET_STATE_UPDATE_TYPE_RESUME": "Resume market",
|
||||
"MARKET_STATE_UPDATE_TYPE_SUSPEND": "Suspend market",
|
||||
"MARKET_STATE_UPDATE_TYPE_TERMINATE": "Terminate market",
|
||||
@@ -732,8 +736,12 @@
|
||||
"NewMarketSpotProduct": "New market - spot",
|
||||
"UpdateMarket": "Update market",
|
||||
"UpdateMarketState": "Update market state",
|
||||
"UpdateReferralProgram": "Update referral program",
|
||||
"UpdateVolumeDiscountProgram": "Update volume discount program",
|
||||
"NewAsset": "New asset",
|
||||
"UpdateAsset": "Update asset",
|
||||
"NewTransfer": "New transfer",
|
||||
"CancelTransfer": "Cancel transfer",
|
||||
"AssetID": "Asset ID",
|
||||
"Freeform": "Freeform",
|
||||
"RawProposal": "Let me choose (raw proposal)",
|
||||
@@ -890,5 +898,51 @@
|
||||
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
|
||||
"HowToProposeRawStep3": "3. Submit on-chain below",
|
||||
"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",
|
||||
"ACCOUNT_TYPE_INSURANCE": "Insurance account",
|
||||
"ACCOUNT_TYPE_GLOBAL_INSURANCE": "Global insurance account",
|
||||
"ACCOUNT_TYPE_SETTLEMENT": "Settlement account",
|
||||
"ACCOUNT_TYPE_MARGIN": "Margin account ",
|
||||
"ACCOUNT_TYPE_GENERAL": "General account",
|
||||
"ACCOUNT_TYPE_FEES_INFRASTRUCTURE": "Infrastructure fees account",
|
||||
"ACCOUNT_TYPE_FEES_LIQUIDITY": "Liquidity fees account",
|
||||
"ACCOUNT_TYPE_FEES_MAKER": "Maker fees account",
|
||||
"ACCOUNT_TYPE_BOND": "Bond account",
|
||||
"ACCOUNT_TYPE_EXTERNAL": "External account",
|
||||
"ACCOUNT_TYPE_GLOBAL_REWARD": "Global reward account",
|
||||
"ACCOUNT_TYPE_PENDING_TRANSFERS": "Pending transfers account",
|
||||
"ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES": "Maker paid fees reward account",
|
||||
"ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES": "Maker received fees reward account",
|
||||
"ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES": "Liquidity provider received fees reward account",
|
||||
"ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS": "Market proposers reward account",
|
||||
"ACCOUNT_TYPE_HOLDING": "Holding account",
|
||||
"ACCOUNT_TYPE_LP_LIQUIDITY_FEES": "Liquidity provider fees account",
|
||||
"ACCOUNT_TYPE_NETWORK_TREASURY": "Network treasury account",
|
||||
"ACCOUNT_TYPE_VESTING_REWARDS": "Vesting rewards account",
|
||||
"ACCOUNT_TYPE_VESTED_REWARDS": "Vested rewards account",
|
||||
"ACCOUNT_TYPE_REWARD_AVERAGE_POSITION": "Average position reward account",
|
||||
"ACCOUNT_TYPE_REWARD_RELATIVE_RETURN": "Relative return reward account",
|
||||
"ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY": "Return volatility reward account",
|
||||
"ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING": "Validator ranking reward account",
|
||||
"ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD": "Pending fee referral reward account"
|
||||
}
|
||||
|
||||
@@ -188,6 +188,8 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+14
-1
@@ -98,6 +98,16 @@ export const ProposalHeader = ({
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'UpdateReferralProgram': {
|
||||
proposalType = 'UpdateReferralProgram';
|
||||
fallbackTitle = t('UpdateReferralProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'UpdateVolumeDiscountProgram': {
|
||||
proposalType = 'UpdateVolumeDiscountProgram';
|
||||
fallbackTitle = t('UpdateVolumeDiscountProgramProposal');
|
||||
break;
|
||||
}
|
||||
case 'NewAsset': {
|
||||
proposalType = 'NewAsset';
|
||||
fallbackTitle = t('NewAssetProposal');
|
||||
@@ -253,7 +263,10 @@ const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => {
|
||||
return (
|
||||
<span>
|
||||
{GovernanceTransferKindMapping[details.kind.__typename]}{' '}
|
||||
{t('transfer from')} <Lozenge>{truncateMiddle(details.source)}</Lozenge>{' '}
|
||||
{t('transfer from')}{' '}
|
||||
<Lozenge>
|
||||
{truncateMiddle(details.source) || t(details.sourceType)}
|
||||
</Lozenge>{' '}
|
||||
{t('to')} <Lozenge>{truncateMiddle(details.destination)}</Lozenge>
|
||||
</span>
|
||||
);
|
||||
|
||||
+5
-3
@@ -12,12 +12,12 @@ import {
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
getDataSourceSpecForSettlementSchedule,
|
||||
getDataSourceSpecForSettlementData,
|
||||
getDataSourceSpecForTradingTermination,
|
||||
getSigners,
|
||||
MarginScalingFactorsPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Button,
|
||||
@@ -219,8 +219,10 @@ export const ProposalMarketData = ({
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Margin scaling factors')}
|
||||
</h2>
|
||||
<MarginScalingFactorsPanel
|
||||
market={marketData}
|
||||
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 { ProposalJson } from '../proposal-json';
|
||||
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 { ListAsset } from '../list-asset';
|
||||
import Routes from '../../../routes';
|
||||
@@ -113,6 +115,15 @@ export const Proposal = ({
|
||||
// TODO: check minVoterBalance for 'CancelTransfer'
|
||||
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>
|
||||
)}
|
||||
|
||||
{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}
|
||||
|
||||
<div className="mb-10">
|
||||
|
||||
@@ -19,6 +19,8 @@ export const useProposalNetworkParams = ({
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_market_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_updateAsset_requiredParticipation,
|
||||
NetworkParams.governance_proposal_asset_requiredMajority,
|
||||
NetworkParams.governance_proposal_asset_requiredParticipation,
|
||||
@@ -26,6 +28,10 @@ export const useProposalNetworkParams = ({
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredParticipation,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredParticipation,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredParticipation,
|
||||
NetworkParams.governance_proposal_transfer_requiredMajority,
|
||||
]);
|
||||
|
||||
const fallback = {
|
||||
@@ -91,6 +97,30 @@ export const useProposalNetworkParams = ({
|
||||
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
|
||||
),
|
||||
};
|
||||
case 'NewTransfer':
|
||||
case 'CancelTransfer':
|
||||
return {
|
||||
requiredMajority: params.governance_proposal_transfer_requiredMajority,
|
||||
requiredParticipation: new BigNumber(
|
||||
params.governance_proposal_transfer_requiredParticipation
|
||||
),
|
||||
};
|
||||
default:
|
||||
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(
|
||||
$proposalId: ID!
|
||||
$includeNewMarketProductField: Boolean!
|
||||
$includeUpdateMarketState: Boolean!
|
||||
$includeUpdateReferralProgram: Boolean!
|
||||
$includeUpdateVolumeDiscountProgram: Boolean!
|
||||
) {
|
||||
proposal(id: $proposalId) {
|
||||
id
|
||||
@@ -64,6 +102,9 @@ query Proposal(
|
||||
errorDetails
|
||||
...NewMarketProductField @include(if: $includeNewMarketProductField)
|
||||
...UpdateMarketState @include(if: $includeUpdateMarketState)
|
||||
...UpdateReferralProgram @include(if: $includeUpdateReferralProgram)
|
||||
...UpdateVolumeDiscountProgram
|
||||
@include(if: $includeUpdateVolumeDiscountProgram)
|
||||
terms {
|
||||
closingDatetime
|
||||
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_updateNetParam_minVoterBalance,
|
||||
NetworkParams.governance_proposal_freeform_minVoterBalance,
|
||||
NetworkParams.governance_proposal_referralProgram_minVoterBalance,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_minVoterBalance,
|
||||
NetworkParams.spam_protection_voting_min_tokens,
|
||||
NetworkParams.governance_proposal_market_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateMarket_requiredMajority,
|
||||
@@ -44,6 +46,8 @@ export const ProposalContainer = () => {
|
||||
NetworkParams.governance_proposal_updateAsset_requiredMajority,
|
||||
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
|
||||
NetworkParams.governance_proposal_freeform_requiredMajority,
|
||||
NetworkParams.governance_proposal_referralProgram_requiredMajority,
|
||||
NetworkParams.governance_proposal_VolumeDiscountProgram_requiredMajority,
|
||||
]);
|
||||
|
||||
const {
|
||||
@@ -57,6 +61,8 @@ export const ProposalContainer = () => {
|
||||
proposalId: params.proposalId || '',
|
||||
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralProgram: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountProgram: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
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 {
|
||||
id
|
||||
rationale {
|
||||
@@ -127,6 +163,8 @@ fragment ProposalFields on Proposal {
|
||||
query Proposals(
|
||||
$includeNewMarketProductFields: Boolean!
|
||||
$includeUpdateMarketStates: Boolean!
|
||||
$includeUpdateReferralPrograms: Boolean!
|
||||
$includeUpdateVolumeDiscountPrograms: Boolean!
|
||||
) {
|
||||
proposalsConnection {
|
||||
edges {
|
||||
@@ -134,6 +172,9 @@ query Proposals(
|
||||
...ProposalFields
|
||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||
...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 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 ProposalsQueryVariables = Types.Exact<{
|
||||
includeNewMarketProductFields: 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`
|
||||
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`
|
||||
fragment ProposalFields on Proposal {
|
||||
id
|
||||
@@ -147,20 +191,24 @@ export const ProposalFieldsFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const ProposalsDocument = gql`
|
||||
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!) {
|
||||
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!, $includeUpdateReferralPrograms: Boolean!, $includeUpdateVolumeDiscountPrograms: Boolean!) {
|
||||
proposalsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ProposalFields
|
||||
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
|
||||
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
|
||||
...UpdateReferralPrograms @include(if: $includeUpdateReferralPrograms)
|
||||
...UpdateVolumeDiscountPrograms @include(if: $includeUpdateVolumeDiscountPrograms)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ProposalFieldsFragmentDoc}
|
||||
${NewMarketProductFieldsFragmentDoc}
|
||||
${UpdateMarketStatesFragmentDoc}`;
|
||||
${UpdateMarketStatesFragmentDoc}
|
||||
${UpdateReferralProgramsFragmentDoc}
|
||||
${UpdateVolumeDiscountProgramsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useProposalsQuery__
|
||||
@@ -176,6 +224,8 @@ ${UpdateMarketStatesFragmentDoc}`;
|
||||
* variables: {
|
||||
* includeNewMarketProductFields: // value for 'includeNewMarketProductFields'
|
||||
* includeUpdateMarketStates: // value for 'includeUpdateMarketStates'
|
||||
* includeUpdateReferralPrograms: // value for 'includeUpdateReferralPrograms'
|
||||
* includeUpdateVolumeDiscountPrograms: // value for 'includeUpdateVolumeDiscountPrograms'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,8 @@ export const ProposalsContainer = () => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ export const RejectedProposalsContainer = () => {
|
||||
variables: {
|
||||
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
|
||||
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
|
||||
includeUpdateReferralPrograms: !!FLAGS.REFERRALS,
|
||||
includeUpdateVolumeDiscountPrograms: !!FLAGS.VOLUME_DISCOUNTS,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #000;
|
||||
}
|
||||
.pre-loader .pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader .pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(0) {
|
||||
animation-delay: 0ms;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -0.1s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 1s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.9s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
animation-delay: -1.4s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
animation-delay: 1.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -2.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
animation-delay: 1.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -1.95s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 2.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(17) {
|
||||
animation-delay: -0.85s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(18) {
|
||||
animation-delay: 1.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(19) {
|
||||
animation-delay: -1.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(20) {
|
||||
animation-delay: 5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(21) {
|
||||
animation-delay: -5.25s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(22) {
|
||||
animation-delay: 4.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(23) {
|
||||
animation-delay: -5.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(24) {
|
||||
animation-delay: 4.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(25) {
|
||||
animation-delay: -5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
html.dark .pre-loader .loader-item {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: black;
|
||||
}
|
||||
.pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@for $i from 0 through 25 {
|
||||
.loader-item:nth-child(#{$i}) {
|
||||
@if $i % 2 == 0 {
|
||||
animation-delay: #{$i * 50 * random(5)}ms;
|
||||
animation-direction: reverse;
|
||||
} @else {
|
||||
animation-delay: #{$i * -50 * random(5)}ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
.loader-item {
|
||||
animation: flickering 0.4s linear alternate infinite;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
html.dark {
|
||||
.pre-loader {
|
||||
.loader-item {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import { removeDecimal } from '@vegaprotocol/cypress';
|
||||
import { ethers } from 'ethers';
|
||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const assetSelectField = 'select[name="asset"]';
|
||||
const toAddressField = 'input[name="to"]';
|
||||
const amountField = 'input[name="amount"]';
|
||||
const formFieldError = 'input-error-text';
|
||||
|
||||
const ASSET_EURO = 1;
|
||||
|
||||
describe('deposit form validation', { tags: '@smoke' }, () => {
|
||||
function openDepositForm() {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage();
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('MetaMask');
|
||||
cy.wait('@Assets');
|
||||
}
|
||||
|
||||
before(() => {
|
||||
openDepositForm();
|
||||
});
|
||||
|
||||
it('handles empty fields', () => {
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
// once Ethereum wallet is connected and key selected the only field that will
|
||||
// error is the asset select
|
||||
cy.getByTestId(formFieldError).should('have.length', 1);
|
||||
cy.get('[data-testid="input-error-text"][aria-describedby="asset"]').should(
|
||||
'have.length',
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
it('unable to select assets not enabled', () => {
|
||||
// Assets not enabled in mocks
|
||||
cy.get(assetSelectField + ' option:contains(Asset 2)').should('not.exist');
|
||||
cy.get(assetSelectField + ' option:contains(Asset 3)').should('not.exist');
|
||||
cy.get(assetSelectField + ' option:contains(Asset 4)').should('not.exist');
|
||||
});
|
||||
|
||||
it('invalid public key when entering address manually', () => {
|
||||
cy.getByTestId('enter-pubkey-manually').click();
|
||||
cy.get(toAddressField).clear().type('INVALID_DEPOSIT_TO_ADDRESS');
|
||||
cy.get(`[data-testid="${formFieldError}"][aria-describedby="to"]`).should(
|
||||
'have.text',
|
||||
'Invalid Vega key'
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid amount', () => {
|
||||
mockWeb3DepositCalls({
|
||||
allowance: '1000',
|
||||
depositLifetimeLimit: '1000',
|
||||
balance: '800',
|
||||
deposited: '0',
|
||||
dps: 5,
|
||||
});
|
||||
// Deposit amount smaller than minimum viable for selected asset
|
||||
// Select an amount so that we have a known decimal places value to work with
|
||||
selectAsset(ASSET_EURO);
|
||||
|
||||
cy.get(amountField)
|
||||
.clear()
|
||||
.type('0.00000000000000000000000000000000001')
|
||||
.next(`[data-testid="${formFieldError}"]`)
|
||||
.should('have.text', 'Value is below minimum');
|
||||
});
|
||||
|
||||
it('insufficient funds', () => {
|
||||
// 1001-DEPO-004
|
||||
mockWeb3DepositCalls({
|
||||
allowance: '1000',
|
||||
depositLifetimeLimit: '1000',
|
||||
balance: '800',
|
||||
deposited: '0',
|
||||
dps: 5,
|
||||
});
|
||||
cy.get(amountField)
|
||||
.clear()
|
||||
.type('850')
|
||||
.next(`[data-testid="${formFieldError}"]`)
|
||||
.should(
|
||||
'have.text',
|
||||
"You can't deposit more than you have in your Ethereum wallet, 800 tEURO"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deposit actions', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-1');
|
||||
});
|
||||
|
||||
it.skip('Deposit to trade is visible', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit').click();
|
||||
cy.getByTestId('deposit-submit').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
function mockWeb3DepositCalls({
|
||||
allowance,
|
||||
depositLifetimeLimit,
|
||||
balance,
|
||||
deposited,
|
||||
dps,
|
||||
}: {
|
||||
allowance: string;
|
||||
depositLifetimeLimit: string;
|
||||
balance: string;
|
||||
deposited: string;
|
||||
dps: number;
|
||||
}) {
|
||||
const assetContractAddress = '0x0158031158bb4df2ad02eaa31e8963e84ea978a4';
|
||||
const collateralBridgeAddress = '0x7fe27d970bc8afc3b11cc8d9737bfb66b1efd799';
|
||||
const toResult = (value: string, dps: number) => {
|
||||
const rawValue = removeDecimal(value, dps);
|
||||
return ethers.utils.hexZeroPad(
|
||||
ethers.utils.hexlify(parseInt(rawValue)),
|
||||
32
|
||||
);
|
||||
};
|
||||
cy.intercept('POST', 'http://localhost:8545', (req) => {
|
||||
// Mock chainId call
|
||||
if (req.body.method === 'eth_chainId') {
|
||||
req.alias = 'eth_chainId';
|
||||
req.reply({
|
||||
id: req.body.id,
|
||||
jsonrpc: req.body.jsonrpc,
|
||||
result: '0xaa36a7', // 11155111 for sepolia chain id
|
||||
});
|
||||
}
|
||||
|
||||
// Mock deposited amount
|
||||
if (req.body.method === 'eth_getStorageAt') {
|
||||
req.alias = 'eth_getStorageAt';
|
||||
req.reply({
|
||||
id: req.body.id,
|
||||
jsonrpc: req.body.jsonrpc,
|
||||
result: toResult(deposited, dps),
|
||||
});
|
||||
}
|
||||
|
||||
if (req.body.method === 'eth_call') {
|
||||
// Mock approved amount for asset on collateral bridge
|
||||
if (
|
||||
req.body.params[0].to === assetContractAddress &&
|
||||
req.body.params[0].data ===
|
||||
'0xdd62ed3e000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f0000000000000000000000007fe27d970bc8afc3b11cc8d9737bfb66b1efd799'
|
||||
) {
|
||||
req.alias = 'eth_call_allowance';
|
||||
req.reply({
|
||||
id: req.body.id,
|
||||
jsonrpc: req.body.jsonrpc,
|
||||
result: toResult(allowance, dps),
|
||||
});
|
||||
}
|
||||
// Mock balance of asset in Ethereum wallet
|
||||
else if (
|
||||
req.body.params[0].to === assetContractAddress &&
|
||||
req.body.params[0].data ===
|
||||
'0x70a08231000000000000000000000000ee7d375bcb50c26d52e1a4a472d8822a2a22d94f'
|
||||
) {
|
||||
req.alias = 'eth_call_balanceOf';
|
||||
req.reply({
|
||||
id: req.body.id,
|
||||
jsonrpc: req.body.jsonrpc,
|
||||
result: toResult(balance, dps),
|
||||
});
|
||||
}
|
||||
// Mock deposit lifetime limit
|
||||
else if (
|
||||
req.body.params[0].to === collateralBridgeAddress &&
|
||||
req.body.params[0].data ===
|
||||
'0x354a897a0000000000000000000000000158031158bb4df2ad02eaa31e8963e84ea978a4'
|
||||
) {
|
||||
req.alias = 'eth_call_get_deposit_maximum'; // deposit lifetime limit
|
||||
req.reply({
|
||||
id: req.body.id,
|
||||
jsonrpc: req.body.jsonrpc,
|
||||
result: toResult(depositLifetimeLimit, dps),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -58,7 +58,7 @@ describe('Market trading page', () => {
|
||||
// 6002-MDET-003
|
||||
cy.getByTestId(marketSummaryBlock).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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 transferText = 'transfer-intro-text';
|
||||
const errorText = 'input-error-text';
|
||||
const formFieldError = 'input-error-text';
|
||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const transferForm = 'transfer-form';
|
||||
const walletTransfer = 'wallet-transfer';
|
||||
|
||||
const ASSET_EURO = 1;
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
describe(
|
||||
'transfer form validation',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('transfer Text', () => {
|
||||
// 1003-TRAN-003
|
||||
cy.getByTestId(transferText)
|
||||
.should('exist')
|
||||
.get(keyID)
|
||||
.invoke('text')
|
||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||
});
|
||||
|
||||
it('invalid vega key validation', () => {
|
||||
//1003-TRAN-013
|
||||
//1003-TRAN-004
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||
cy.contains('label', 'Vega key').should('be.visible');
|
||||
cy.contains('label', 'Asset').should('be.visible');
|
||||
cy.contains('label', 'Amount').should('be.visible');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
// 1003-TRAN-012
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||
});
|
||||
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-014
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('max amount', () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-002
|
||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'You cannot transfer more than your available collateral'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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,5 +0,0 @@
|
||||
import MarketPage from '../market';
|
||||
|
||||
export const ClosedMarketPage = () => {
|
||||
return <MarketPage closed />;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { ClosedMarketPage as default } from './closed-market';
|
||||
@@ -0,0 +1,48 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
SidebarButton,
|
||||
SidebarDivider,
|
||||
ViewType,
|
||||
} from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const LiquiditySidebar = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarDivider />
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
icon={VegaIconNames.TICKET}
|
||||
tooltip={t('Order')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { DocsLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, ExternalLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
fromNanoSeconds,
|
||||
getExpiryDate,
|
||||
getMarketExpiryDate,
|
||||
@@ -14,9 +15,12 @@ import {
|
||||
Last24hVolume,
|
||||
getAsset,
|
||||
getDataSourceSpecForSettlementSchedule,
|
||||
isMarketInAuction,
|
||||
marketInfoProvider,
|
||||
useFundingPeriodsQuery,
|
||||
useFundingRate,
|
||||
useMarketTradingMode,
|
||||
useExternalTwap,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketState as State } from '@vegaprotocol/types';
|
||||
import { HeaderStat } from '../../components/header';
|
||||
@@ -26,6 +30,7 @@ import { MarketState } from '../../components/market-state';
|
||||
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { PriceCell } from '@vegaprotocol/datagrid';
|
||||
|
||||
interface MarketHeaderStatsProps {
|
||||
market: Market;
|
||||
@@ -39,33 +44,7 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{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')} / ${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">
|
||||
<HeaderStat heading={t('Mark Price')} testId="market-price">
|
||||
<MarketMarkPrice
|
||||
marketId={market.id}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
@@ -108,6 +87,62 @@ export const MarketHeaderStats = ({ market }: MarketHeaderStatsProps) => {
|
||||
marketId={market.id}
|
||||
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} />
|
||||
</>
|
||||
);
|
||||
@@ -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 [now, setNow] = useState(Date.now());
|
||||
useEffect(() => {
|
||||
@@ -136,9 +189,11 @@ const useNow = () => {
|
||||
};
|
||||
|
||||
const useEvery = (marketId: string) => {
|
||||
const { data: marketTradingMode } = useMarketTradingMode(marketId);
|
||||
const { data: marketInfo } = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
variables: { marketId },
|
||||
skip: !marketTradingMode || isMarketInAuction(marketTradingMode),
|
||||
});
|
||||
let every: number | undefined = undefined;
|
||||
const sourceType =
|
||||
|
||||
@@ -3,16 +3,15 @@ import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
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 { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||
import { TradeGrid } from './trade-grid';
|
||||
import { TradePanels } from './trade-panels';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../lib/links';
|
||||
import { Links } from '../../lib/links';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||
return markPrice && decimalPlaces
|
||||
@@ -57,7 +56,7 @@ const TitleUpdater = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
export const MarketPage = () => {
|
||||
const { marketId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
@@ -68,27 +67,13 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const { data, error, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
data?.state &&
|
||||
[
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
].includes(data.state) &&
|
||||
currentRouteId !== Routes.CLOSED_MARKETS &&
|
||||
marketId
|
||||
) {
|
||||
navigate(Links.CLOSED_MARKETS(marketId));
|
||||
}
|
||||
}, [data?.state, currentRouteId, navigate, marketId]);
|
||||
const { data, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.id && data.id !== lastMarketId && !closed) {
|
||||
update({ marketId: data.id });
|
||||
}
|
||||
}, [update, lastMarketId, data?.id, closed]);
|
||||
}, [update, lastMarketId, data?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (largeScreen && view === undefined) {
|
||||
@@ -97,7 +82,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
currentRouteId
|
||||
);
|
||||
}
|
||||
}, [setViews, view, currentRouteId, largeScreen, closed]);
|
||||
}, [setViews, view, currentRouteId, largeScreen]);
|
||||
|
||||
const pinnedAsset = data && getAsset(data);
|
||||
|
||||
@@ -110,7 +95,15 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
}
|
||||
}, [largeScreen, data, pinnedAsset]);
|
||||
|
||||
if (!data && marketId) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Splash>
|
||||
<Loader />
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Splash>
|
||||
<span className="flex flex-col items-center gap-2">
|
||||
@@ -120,7 +113,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
<p className="justify-center text-sm">
|
||||
{t(`Please choose another market from the`)}{' '}
|
||||
<ExternalLink onClick={() => navigate(Links.MARKETS())}>
|
||||
market list
|
||||
{t('market list')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
</span>
|
||||
@@ -129,18 +122,13 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data || undefined}
|
||||
noDataCondition={(data) => false}
|
||||
>
|
||||
<>
|
||||
<TitleUpdater
|
||||
marketId={data?.id}
|
||||
marketName={data?.tradableInstrument.instrument.name}
|
||||
decimalPlaces={data?.decimalPlaces}
|
||||
/>
|
||||
{tradeView}
|
||||
</AsyncRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TradingViews } from './trade-views';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
@@ -72,10 +73,19 @@ const MainGrid = memo(
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding" name={t('Funding')}>
|
||||
<Tab id="funding-history" name={t('Funding history')}>
|
||||
<TradingViews.funding.component marketId={marketId} />
|
||||
</Tab>
|
||||
) : null}
|
||||
{market &&
|
||||
market.tradableInstrument.instrument.product.__typename ===
|
||||
'Perpetual' ? (
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<TradingViews.fundingPayments.component
|
||||
marketId={marketId}
|
||||
/>
|
||||
</Tab>
|
||||
) : null}
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
</ResizableGridPanel>
|
||||
@@ -169,6 +179,7 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div className="min-h-0 p-0.5">
|
||||
|
||||
@@ -11,6 +11,7 @@ import classNames from 'classnames';
|
||||
import {
|
||||
MarketSuccessorBanner,
|
||||
MarketSuccessorProposalBanner,
|
||||
MarketTerminationBanner,
|
||||
} from '../../components/market-banner';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
@@ -59,6 +60,7 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
<MarketSuccessorProposalBanner marketId={market?.id} />
|
||||
</>
|
||||
)}
|
||||
<MarketTerminationBanner market={market} />
|
||||
<OracleBanner marketId={market?.id || ''} />
|
||||
</div>
|
||||
<div>{renderMenu()}</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { PositionsContainer } from '../../components/positions-container';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import { FundingContainer } from '../../components/funding-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
@@ -55,6 +56,10 @@ export const TradingViews = {
|
||||
label: 'Funding',
|
||||
component: requiresMarket(FundingContainer),
|
||||
},
|
||||
fundingPayments: {
|
||||
label: 'Funding Payments',
|
||||
component: FundingPaymentsContainer,
|
||||
},
|
||||
orderbook: {
|
||||
label: 'Orderbook',
|
||||
component: requiresMarket(OrderbookContainer),
|
||||
|
||||
@@ -363,12 +363,12 @@ describe('Closed', () => {
|
||||
const container = within(
|
||||
document.querySelector('.ag-center-cols-container') as HTMLElement
|
||||
);
|
||||
const cell = container.getAllByRole('gridcell', {
|
||||
name: (_name, element) => element.getAttribute('col-id') === 'code',
|
||||
})[0];
|
||||
|
||||
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent(
|
||||
'PRNT'
|
||||
);
|
||||
const cells = await container.findAllByRole('gridcell');
|
||||
const cell = cells.find((el) => el.getAttribute('col-id') === 'code');
|
||||
|
||||
expect(
|
||||
within(cell as HTMLElement).getByTestId('stack-cell-secondary')
|
||||
).toHaveTextContent('PRNT');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import compact from 'lodash/compact';
|
||||
import { isAfter } from 'date-fns';
|
||||
import type {
|
||||
@@ -5,6 +6,7 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useMemo } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Asset } from '@vegaprotocol/types';
|
||||
@@ -17,13 +19,11 @@ import {
|
||||
import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets';
|
||||
import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { MarketCodeCell } from './market-code-cell';
|
||||
import { MarketActionsDropdown } from './market-table-actions';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { useClosedMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
type SettlementAsset = Pick<
|
||||
Asset,
|
||||
@@ -127,7 +127,7 @@ const ClosedMarketsDataGrid = ({
|
||||
rowData: Row[];
|
||||
error: Error | undefined;
|
||||
}) => {
|
||||
const handleOnSelect = useClosedMarketClickHandler();
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
@@ -302,8 +302,11 @@ const ClosedMarketsDataGrid = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore metaKey exists
|
||||
handleOnSelect(data.id, event ? event.metaKey : false);
|
||||
handleOnSelect(
|
||||
data.id,
|
||||
// @ts-ignore metaKey exists
|
||||
event ? event.metaKey : false
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Route, Routes, useParams } from 'react-router-dom';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
SidebarButton,
|
||||
SidebarDivider,
|
||||
ViewType,
|
||||
} from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const MarketsSidebar = () => {
|
||||
const { marketId } = useParams();
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const { data } = useMarket(marketId);
|
||||
const active =
|
||||
data &&
|
||||
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<Routes>
|
||||
<Route
|
||||
path=":marketId"
|
||||
element={
|
||||
<>
|
||||
<SidebarDivider />
|
||||
{active && (
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
icon={VegaIconNames.TICKET}
|
||||
tooltip={t('Order')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
)}
|
||||
<SidebarButton
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { SidebarButton, ViewType } from '../../components/sidebar';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const PortfolioSidebar = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -9,7 +9,9 @@ import { usePageTitleStore } from '../../stores';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { DepositsContainer } from '../../components/deposits-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { FundingPaymentsContainer } from '../../components/funding-payments-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { PositionsMenu } from '../../components/positions-menu';
|
||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
@@ -68,7 +70,11 @@ export const Portfolio = () => {
|
||||
<Tab id="account-history" name={t('Account history')}>
|
||||
<AccountHistoryContainer />
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<Tab
|
||||
id="positions"
|
||||
name={t('Positions')}
|
||||
menu={<PositionsMenu />}
|
||||
>
|
||||
<PositionsContainer allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
@@ -77,6 +83,9 @@ export const Portfolio = () => {
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<FillsContainer />
|
||||
</Tab>
|
||||
<Tab id="funding-payments" name={t('Funding payments')}>
|
||||
<FundingPaymentsContainer />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<LedgerContainer />
|
||||
</Tab>
|
||||
|
||||
+13
-17
@@ -1,38 +1,29 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
AppLoader,
|
||||
DocsLinks,
|
||||
NetworkLoader,
|
||||
NodeFailure,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MaintenancePage } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Web3Provider } from './web3-provider';
|
||||
|
||||
export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
loading: () => <>Loading...</>,
|
||||
});
|
||||
|
||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
|
||||
const {
|
||||
error,
|
||||
VEGA_URL,
|
||||
VEGA_ENV,
|
||||
VEGA_WALLET_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
MAINTENANCE_PAGE,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
if (MAINTENANCE_PAGE) {
|
||||
return <MaintenancePage />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
@@ -41,22 +32,27 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!DocsLinks
|
||||
) {
|
||||
return null;
|
||||
return <AppLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
skeleton={<DynamicLoader />}
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t('Could not initialize app')} error={error} />
|
||||
}
|
||||
>
|
||||
<NodeGuard
|
||||
skeleton={<DynamicLoader />}
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
skeleton={<AppLoader />}
|
||||
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Web3Provider>
|
||||
<Web3Provider
|
||||
skeleton={<AppLoader />}
|
||||
failure={
|
||||
<AppFailure title={t(`Could not configure web3 provider`)} />
|
||||
}
|
||||
>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
export * from './app-loader';
|
||||
export * from './bootstrapper';
|
||||
export * from './web3-provider';
|
||||
+22
-19
@@ -6,14 +6,20 @@ import {
|
||||
useWeb3ConnectStore,
|
||||
createDefaultProvider,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
export const Web3Provider = ({
|
||||
children,
|
||||
skeleton,
|
||||
failure,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
skeleton: ReactNode;
|
||||
failure: ReactNode;
|
||||
}) => {
|
||||
const { config, loading, error } = useEthereumConfig();
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
@@ -49,23 +55,20 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
|
||||
ETH_WALLET_MNEMONIC,
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
return <>{failure}</>;
|
||||
}
|
||||
|
||||
if (loading || !connectors.length) {
|
||||
return <>{skeleton}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={connectors}
|
||||
noDataCondition={(d) => {
|
||||
if (!d) return true;
|
||||
return d.length < 1;
|
||||
}}
|
||||
noDataMessage={t('Could not fetch Ethereum configuration')}
|
||||
<Web3ProviderInternal
|
||||
connectors={connectors}
|
||||
defaultProvider={defaultProvider}
|
||||
>
|
||||
<Web3ProviderInternal
|
||||
connectors={connectors}
|
||||
defaultProvider={defaultProvider}
|
||||
>
|
||||
<>{children}</>
|
||||
</Web3ProviderInternal>
|
||||
</AsyncRenderer>
|
||||
<>{children}</>
|
||||
</Web3ProviderInternal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
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 = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId?: string;
|
||||
}) => {
|
||||
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}
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const useFundingPaymentsStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_funding_payments_store',
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export * from './funding-payments-container';
|
||||
@@ -17,11 +17,11 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
||||
);
|
||||
return (
|
||||
<header className={headerClasses}>
|
||||
<div className="hidden lg:flex flex-col justify-center items-start pl-3 lg:pl-4 pt-2 xl:pb-2 pb-0">
|
||||
<div className="flex-col items-start justify-center hidden pt-2 pb-0 pl-3 lg:flex lg:pl-4 xl:pb-2">
|
||||
{title}
|
||||
</div>
|
||||
<div data-testid="header-summary" className="min-w-0">
|
||||
<div className="px-3 lg:px-4 py-2 flex flex-nowrap gap-4 items-center text-xs overflow-x-auto">
|
||||
<div className="flex items-center px-3 py-2 overflow-x-auto text-xs lg:px-4 flex-nowrap gap-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@ export const HeaderTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<h1
|
||||
data-testid="header-title"
|
||||
className="flex gap-4 items-center text-lg whitespace-nowrap xl:pr-4 xl:border-r border-default"
|
||||
className="flex items-center text-lg gap-4 whitespace-nowrap xl:pr-4 xl:border-r border-default"
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Outlet, Routes, Route } from 'react-router-dom';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
|
||||
import classNames from 'classnames';
|
||||
import { MarketHeader } from '../market-header';
|
||||
import { LiquidityHeader } from '../liquidity-header';
|
||||
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
|
||||
|
||||
export const LayoutWithSidebar = () => {
|
||||
export const LayoutWithSidebar = ({
|
||||
header,
|
||||
sidebar,
|
||||
}: {
|
||||
header?: ReactNode;
|
||||
sidebar?: ReactNode;
|
||||
}) => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const views = useSidebar((store) => store.views);
|
||||
const sidebarView = views[currentRouteId] || null;
|
||||
@@ -20,16 +25,7 @@ export const LayoutWithSidebar = () => {
|
||||
|
||||
return (
|
||||
<div className={gridClasses}>
|
||||
<div className="col-span-full">
|
||||
<Routes>
|
||||
<Route path="markets/:marketId" element={<MarketHeader />} />
|
||||
<Route
|
||||
path="markets/all/closed/:marketId"
|
||||
element={<MarketHeader />}
|
||||
/>
|
||||
<Route path="liquidity/:marketId" element={<LiquidityHeader />} />
|
||||
</Routes>
|
||||
</div>
|
||||
<div className="col-span-full">{header}</div>
|
||||
<main
|
||||
className={classNames('col-start-1 col-end-1', {
|
||||
'lg:col-end-3': !sidebarOpen,
|
||||
@@ -40,9 +36,12 @@ export const LayoutWithSidebar = () => {
|
||||
</main>
|
||||
<aside
|
||||
// min-h-0 is needed as this element is part of a grid, we want the content to be scrollable, without it it will push the grid element taller
|
||||
className={classNames('col-start-1 lg:col-start-2 min-h-0', {
|
||||
hidden: !sidebarOpen,
|
||||
})}
|
||||
className={classNames(
|
||||
'col-start-1 lg:col-start-2 min-h-0 lg:border-l border-default lg:bg-vega-clight-800 dark:lg:bg-vega-cdark-800',
|
||||
{
|
||||
hidden: !sidebarOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<SidebarContent />
|
||||
</aside>
|
||||
@@ -54,7 +53,7 @@ export const LayoutWithSidebar = () => {
|
||||
'lg:row-start-2 lg:row-span-full lg:col-start-3'
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
<Sidebar options={sidebar} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './market-successor-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 { MockedProvider } from '@apollo/react-testing';
|
||||
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
|
||||
import type { SuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import { SuccessorProposalsListDocument } from '@vegaprotocol/proposals';
|
||||
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
|
||||
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
||||
const marketProposalMock: MockedResponse<MarketViewProposalsQuery> = {
|
||||
request: {
|
||||
query: SuccessorProposalsListDocument,
|
||||
query: MarketViewProposalsDocument,
|
||||
variables: {
|
||||
inState: Types.ProposalState.STATE_OPEN,
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
@@ -18,8 +23,11 @@ const marketProposalMock: MockedResponse<SuccessorProposalsListQuery> = {
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-1',
|
||||
state: Types.ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
closingDatetime: '2023-09-27',
|
||||
enactmentDatetime: '2023-09-28',
|
||||
change: {
|
||||
__typename: 'NewMarket',
|
||||
instrument: {
|
||||
@@ -66,12 +74,13 @@ describe('MarketSuccessorProposalBanner', () => {
|
||||
proposalsConnection: {
|
||||
edges: [
|
||||
...((
|
||||
marketProposalMock?.result as SingleExecutionResult<SuccessorProposalsListQuery>
|
||||
marketProposalMock?.result as SingleExecutionResult<MarketViewProposalsQuery>
|
||||
)?.data?.proposalsConnection?.edges ?? []),
|
||||
{
|
||||
node: {
|
||||
__typename: 'Proposal',
|
||||
id: 'proposal-2',
|
||||
state: Types.ProposalState.STATE_OPEN,
|
||||
terms: {
|
||||
__typename: 'ProposalTerms',
|
||||
change: {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import type {
|
||||
SuccessorProposalListFieldsFragment,
|
||||
NewMarketSuccessorFieldsFragment,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useSuccessorProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
import type { NewMarketSuccessorFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useMarketViewProposals } from '@vegaprotocol/proposals';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
@@ -11,23 +8,33 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
export const MarketSuccessorProposalBanner = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId?: string;
|
||||
}) => {
|
||||
const { data: proposals } = useSuccessorProposalsListQuery({
|
||||
const proposals = useMarketViewProposals({
|
||||
skip: !marketId,
|
||||
inState: Types.ProposalState.STATE_OPEN,
|
||||
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||
typename: 'NewMarket',
|
||||
});
|
||||
|
||||
const successors =
|
||||
proposals?.proposalsConnection?.edges
|
||||
?.map((item) => item?.node as SuccessorProposalListFieldsFragment)
|
||||
.filter(
|
||||
(item: SuccessorProposalListFieldsFragment) =>
|
||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||
?.successorConfiguration?.parentMarketId === marketId
|
||||
) ?? [];
|
||||
proposals?.filter((item) => {
|
||||
if (item.terms.change.__typename === 'NewMarket') {
|
||||
const newMarket = item.terms.change;
|
||||
if (
|
||||
newMarket.successorConfiguration?.parentMarketId === marketId &&
|
||||
item.state === Types.ProposalState.STATE_OPEN
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}) ?? [];
|
||||
const [visible, setVisible] = useState(true);
|
||||
const tokenLink = useLinks(DApp.Governance);
|
||||
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;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Popover, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { Header, HeaderTitle } from '../header';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Route, Routes, useParams } from 'react-router-dom';
|
||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||
@@ -19,36 +19,44 @@ export const MarketHeader = () => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
<Popover
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={
|
||||
<HeaderTitle>
|
||||
<span>
|
||||
{data.tradableInstrument.instrument.code}
|
||||
<span className="ml-2 text-xs uppercase text-muted">
|
||||
{' '}
|
||||
{data.tradableInstrument.instrument.product.__typename &&
|
||||
ProductTypeShortName[
|
||||
data.tradableInstrument.instrument.product.__typename
|
||||
]}{' '}
|
||||
</span>
|
||||
</span>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||
</HeaderTitle>
|
||||
}
|
||||
alignOffset={-10}
|
||||
>
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<MarketHeaderStats market={data} />
|
||||
</Header>
|
||||
<Routes>
|
||||
<Route
|
||||
path=":marketId"
|
||||
element={
|
||||
<Header
|
||||
title={
|
||||
<Popover
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={
|
||||
<HeaderTitle>
|
||||
<span>
|
||||
{data.tradableInstrument.instrument.code}
|
||||
<span className="ml-1 text-xs uppercase text-muted">
|
||||
{data.tradableInstrument.instrument.product
|
||||
.__typename &&
|
||||
ProductTypeShortName[
|
||||
data.tradableInstrument.instrument.product
|
||||
.__typename
|
||||
]}
|
||||
</span>
|
||||
</span>
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||
</HeaderTitle>
|
||||
}
|
||||
alignOffset={-10}
|
||||
>
|
||||
<MarketSelector
|
||||
currentMarketId={marketId}
|
||||
onSelect={() => setOpen(false)}
|
||||
/>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<MarketHeaderStats market={data} />
|
||||
</Header>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('Navbar', () => {
|
||||
|
||||
const expectedLinks = [
|
||||
['/', ''],
|
||||
['/markets/all', 'Markets'],
|
||||
['/markets', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
['/referrals', 'Referrals'],
|
||||
@@ -80,7 +80,7 @@ describe('Navbar', () => {
|
||||
});
|
||||
|
||||
it('Markets page route should not match empty market page', () => {
|
||||
renderComponent(['/markets/all']);
|
||||
renderComponent(['/markets']);
|
||||
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
|
||||
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
|
||||
'active'
|
||||
@@ -96,7 +96,7 @@ describe('Navbar', () => {
|
||||
const menu = within(menuEl);
|
||||
|
||||
const expectedLinks = [
|
||||
['/markets/all', 'Markets'],
|
||||
['/markets', 'Markets'],
|
||||
[`/markets/${marketId}`, 'Trading'],
|
||||
['/portfolio', 'Portfolio'],
|
||||
['/referrals', 'Referrals'],
|
||||
|
||||
@@ -264,6 +264,7 @@ const NavbarLink = ({
|
||||
<N.Link asChild={true}>
|
||||
<NavLink
|
||||
to={to}
|
||||
end={true}
|
||||
className={classNames(
|
||||
'block lg:flex lg:h-full flex-col justify-center',
|
||||
'px-6 py-2 lg:p-0 text-lg lg:text-sm',
|
||||
|
||||
@@ -3,14 +3,12 @@ import { Filter, OrderListManager } from '@vegaprotocol/orders';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { DataGridStore } from '../../stores/datagrid-store-slice';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { Links } from '../../lib/links';
|
||||
|
||||
export const FilterStatusValue = {
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
@@ -32,8 +30,7 @@ const AUTO_SIZE_COLUMNS = ['instrument-code'];
|
||||
|
||||
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
const navigate = useNavigateWithMeta();
|
||||
const { gridState, updateGridState } = useOrderListGridState(filter);
|
||||
const gridStoreCallbacks = useDataGridEvents(
|
||||
gridState,
|
||||
@@ -51,8 +48,12 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onMarketClick={(marketId, metaKey) => {
|
||||
navigate(Links.MARKET(marketId), metaKey);
|
||||
}}
|
||||
onOrderTypeClick={(marketId, metaKey) =>
|
||||
navigate(Links.LIQUIDITY(marketId), metaKey)
|
||||
}
|
||||
isReadOnly={isReadOnly}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Preloader = () => {
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
body{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
html.dark body{
|
||||
background: #000;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className="pre-loader">
|
||||
<Loader />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preloader;
|
||||
@@ -38,79 +38,68 @@ const walletContext = {
|
||||
} as VegaWalletContextShape;
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
(path) => {
|
||||
render(
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
beforeEach(() => {
|
||||
useSidebar.setState({ views: {} });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('node-health')).toBeInTheDocument();
|
||||
|
||||
// no order or info sidebars
|
||||
expect(screen.queryByTestId(ViewType.Order)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(ViewType.Info)).not.toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it('renders ticket and info on market pages', () => {
|
||||
it('renders options prop', () => {
|
||||
render(
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
<MemoryRouter>
|
||||
<Sidebar options={<div data-testid="options" />} />
|
||||
</MemoryRouter>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('node-health')).toBeInTheDocument();
|
||||
|
||||
// order and info sidebars are shown
|
||||
expect(screen.getByTestId(ViewType.Order)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Info)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('options')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders selected state', async () => {
|
||||
const routeId = '/markets/ABC';
|
||||
render(
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
<MemoryRouter initialEntries={[routeId]}>
|
||||
<Sidebar
|
||||
options={
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip="Deposit"
|
||||
routeId={'/markets/:marketId'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||
const orderButton = screen.getByTestId(ViewType.Order);
|
||||
const depositButton = screen.getByTestId(ViewType.Deposit);
|
||||
|
||||
// select settings first
|
||||
await userEvent.click(settingsButton);
|
||||
expect(settingsButton).toHaveClass('bg-vega-yellow text-black');
|
||||
|
||||
// switch to order
|
||||
await userEvent.click(orderButton);
|
||||
// switch to deposit
|
||||
await userEvent.click(depositButton);
|
||||
|
||||
expect(settingsButton).not.toHaveClass('bg-vega-yellow text-black');
|
||||
expect(orderButton).toHaveClass('bg-vega-yellow text-black');
|
||||
expect(depositButton).toHaveClass('bg-vega-yellow text-black');
|
||||
|
||||
// close order
|
||||
await userEvent.click(orderButton);
|
||||
expect(orderButton).not.toHaveClass('bg-vega-yellow text-black');
|
||||
await userEvent.click(depositButton);
|
||||
expect(depositButton).not.toHaveClass('bg-vega-yellow text-black');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SidebarContent', () => {
|
||||
beforeEach(() => {
|
||||
useSidebar.setState({ views: {} });
|
||||
});
|
||||
|
||||
it('renders the correct content', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Route, Routes, useParams } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { create } from 'zustand';
|
||||
import { TransferContainer } from '@vegaprotocol/accounts';
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
@@ -50,108 +50,14 @@ type SidebarView =
|
||||
type: ViewType.Settings;
|
||||
};
|
||||
|
||||
const MarketSidebarButtons = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.Order}
|
||||
icon={VegaIconNames.TICKET}
|
||||
tooltip={t('Order')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AssetSidebarButtons = () => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
view={ViewType.Deposit}
|
||||
icon={VegaIconNames.DEPOSIT}
|
||||
tooltip={t('Deposit')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Withdraw}
|
||||
icon={VegaIconNames.WITHDRAW}
|
||||
tooltip={t('Withdraw')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
<SidebarButton
|
||||
view={ViewType.Transfer}
|
||||
icon={VegaIconNames.TRANSFER}
|
||||
tooltip={t('Transfer')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sidebar = () => {
|
||||
export const Sidebar = ({ options }: { options?: ReactNode }) => {
|
||||
const currentRouteId = useGetCurrentRouteId();
|
||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||
const { pubKeys } = useVegaWallet();
|
||||
return (
|
||||
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
|
||||
<nav className={navClasses}>
|
||||
<Routes>
|
||||
<Route path="markets/all" element={<AssetSidebarButtons />} />
|
||||
<Route path="portfolio">
|
||||
<Route
|
||||
// Show deposit/withdraw/transfer sidebar options only on portflio dashboard (index)
|
||||
index={true}
|
||||
element={<AssetSidebarButtons />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="markets/:marketId"
|
||||
element={
|
||||
<>
|
||||
<AssetSidebarButtons />
|
||||
<SidebarDivider />
|
||||
<MarketSidebarButtons />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="liquidity/:marketId"
|
||||
element={
|
||||
<>
|
||||
<AssetSidebarButtons />
|
||||
<SidebarDivider />
|
||||
<MarketSidebarButtons />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="markets/all/closed/:marketId"
|
||||
element={
|
||||
<>
|
||||
<AssetSidebarButtons />
|
||||
<SidebarDivider />
|
||||
<SidebarButton
|
||||
view={ViewType.Info}
|
||||
icon={VegaIconNames.BREAKDOWN}
|
||||
tooltip={t('Market specification')}
|
||||
routeId={currentRouteId}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</nav>
|
||||
{options && <nav className={navClasses}>{options}</nav>}
|
||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||
<SidebarButton
|
||||
view={ViewType.ViewAs}
|
||||
@@ -234,7 +140,7 @@ export const SidebarButton = ({
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarDivider = () => {
|
||||
export const SidebarDivider = () => {
|
||||
return (
|
||||
<div
|
||||
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TelemetryApproval } from './telemetry-approval';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useOnboardingStore } from '../welcome-dialog/use-get-onboarding-step';
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_toast_id';
|
||||
|
||||
export const Telemetry = () => {
|
||||
const onboardingDissmissed = useOnboardingStore((store) => store.dismissed);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { Links } from '../../lib/links';
|
||||
|
||||
export const useMarketClickHandler = (replace = false) => {
|
||||
@@ -15,17 +14,10 @@ export const useMarketClickHandler = (replace = false) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useMarketLiquidityClickHandler = () => {
|
||||
return useCallback((selectedId: string, metaKey?: boolean) => {
|
||||
window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self');
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useClosedMarketClickHandler = (replace = false) => {
|
||||
export const useNavigateWithMeta = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links.CLOSED_MARKETS(selectedId);
|
||||
return (link: string, metaKey?: boolean) => {
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else {
|
||||
|
||||
@@ -4,8 +4,7 @@ import trimEnd from 'lodash/trimEnd';
|
||||
// href creation
|
||||
export const Routes = {
|
||||
HOME: '/',
|
||||
MARKETS: '/markets/all',
|
||||
CLOSED_MARKETS: '/markets/all/closed/:marketId',
|
||||
MARKETS: '/markets',
|
||||
MARKET: '/markets/:marketId',
|
||||
LIQUIDITY: '/liquidity/:marketId',
|
||||
PORTFOLIO: '/portfolio',
|
||||
@@ -29,8 +28,6 @@ export const Links: ConsoleLinks = {
|
||||
MARKET: (marketId: string) =>
|
||||
trimEnd(Routes.MARKET.replace(':marketId', marketId)),
|
||||
MARKETS: () => Routes.MARKETS,
|
||||
CLOSED_MARKETS: (marketId: string) =>
|
||||
trimEnd(Routes.CLOSED_MARKETS.replace(':marketId', marketId)),
|
||||
PORTFOLIO: () => Routes.PORTFOLIO,
|
||||
LIQUIDITY: (marketId: string) =>
|
||||
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import Head from 'next/head';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
useEagerConnect as useVegaEagerConnect,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
} from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEagerConnect as useEthereumEagerConnect,
|
||||
useEthTransactionManager,
|
||||
useEthTransactionUpdater,
|
||||
useEthWithdrawApprovalsManager,
|
||||
} from '@vegaprotocol/web3';
|
||||
import {
|
||||
envTriggerMapping,
|
||||
Networks,
|
||||
@@ -25,21 +11,11 @@ import {
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import './styles.css';
|
||||
import { useGlobalStore, usePageTitleStore } from '../stores';
|
||||
import { usePageTitleStore } from '../stores';
|
||||
import DialogsContainer from './dialogs-container';
|
||||
import ToastsManager from './toasts-manager';
|
||||
import {
|
||||
HashRouter,
|
||||
useLocation,
|
||||
Route,
|
||||
Routes,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
import { HashRouter, useLocation, Route, Routes } from 'react-router-dom';
|
||||
import { Bootstrapper } from '../components/bootstrapper';
|
||||
import { AnnouncementBanner } from '../components/banner';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import classNames from 'classnames';
|
||||
@@ -52,6 +28,10 @@ import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { NavHeader } from '../components/navbar/nav-header';
|
||||
import { Telemetry } from '../components/telemetry';
|
||||
import { Routes as AppRoutes } from '../lib/links';
|
||||
import { SSRLoader } from './ssr-loader';
|
||||
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
|
||||
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
|
||||
import { TransactionHandlers } from './transaction-handlers';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -76,15 +56,6 @@ const Title = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
return null;
|
||||
};
|
||||
|
||||
function AppBody({ Component }: AppProps) {
|
||||
const location = useLocation();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
@@ -124,9 +95,9 @@ function AppBody({ Component }: AppProps) {
|
||||
</div>
|
||||
<DialogsContainer />
|
||||
<ToastsManager />
|
||||
<InitializeHandlers />
|
||||
<TransactionHandlers />
|
||||
<MaybeConnectEagerly />
|
||||
<PartyData />
|
||||
<PartyActiveOrdersHandler />
|
||||
<Telemetry />
|
||||
</div>
|
||||
);
|
||||
@@ -139,55 +110,26 @@ function VegaTradingApp(props: AppProps) {
|
||||
store.setDialogOpen,
|
||||
]);
|
||||
|
||||
// Start validation of env vars and determine VEGA_URL
|
||||
useInitializeEnv();
|
||||
|
||||
// Prevent HashRouter from being server side rendered as it
|
||||
// relies on presence of document object
|
||||
//
|
||||
// This is the last point at which we get pregenerated
|
||||
// HTML so render a ssr friendly loader component
|
||||
if (status === 'default') {
|
||||
return <DynamicLoader />;
|
||||
return <SSRLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<AppLoader>
|
||||
<Bootstrapper>
|
||||
<AppBody {...props} />
|
||||
</AppLoader>
|
||||
</Bootstrapper>
|
||||
<NodeSwitcherDialog open={nodeSwitcherOpen} setOpen={setNodeSwitcher} />
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default VegaTradingApp;
|
||||
|
||||
const PartyData = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const variables = { partyId: pubKey || '' };
|
||||
const skip = !pubKey;
|
||||
useDataProvider({
|
||||
dataProvider: activeOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const eagerConnecting = useVegaEagerConnect(Connectors);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||
);
|
||||
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(Connectors['view']);
|
||||
}
|
||||
useEffect(() => {
|
||||
update({ eagerConnecting });
|
||||
}, [update, eagerConnecting]);
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -22,14 +22,17 @@ export default function Document() {
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" content="/favicon.ico" />
|
||||
|
||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||
<link rel="stylesheet" href="/preloader.css" media="all" />
|
||||
|
||||
{/* scripts */}
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
</Head>
|
||||
<Html>
|
||||
<body className="bg-white dark:bg-vega-cdark-900 text-default font-alpha">
|
||||
<body
|
||||
// Nextjs will set body to display none until js runs. Because the entire app is client rendered
|
||||
// and delivered via ipfs we override this to show a server side render loading animation until the
|
||||
// js is downloaded and react takes over rendering
|
||||
style={{ display: 'block' }}
|
||||
className="bg-white dark:bg-vega-cdark-900 text-default font-alpha"
|
||||
>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { Navigate, Outlet, useRoutes } from 'react-router-dom';
|
||||
import { Navigate, useRoutes } from 'react-router-dom';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -13,7 +13,7 @@ import { Assets } from '../client-pages/assets';
|
||||
import { Deposit } from '../client-pages/deposit';
|
||||
import { Withdraw } from '../client-pages/withdraw';
|
||||
import { Transfer } from '../client-pages/transfer';
|
||||
import { Routes } from '../lib/links';
|
||||
import { Routes as AppRoutes } from '../lib/links';
|
||||
import { LayoutWithSky } from '../client-pages/referrals/layout';
|
||||
import { Referrals } from '../client-pages/referrals/referrals';
|
||||
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
|
||||
@@ -22,11 +22,15 @@ import { CreateCodeContainer } from '../client-pages/referrals/create-code-form'
|
||||
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
|
||||
import { compact } from 'lodash';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import { LiquidityHeader } from '../components/liquidity-header';
|
||||
import { MarketHeader } from '../components/market-header';
|
||||
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
|
||||
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
|
||||
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
|
||||
|
||||
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
|
||||
// Using dynamic imports is a workaround for this until pennant is published as ESM
|
||||
const MarketPage = lazy(() => import('../client-pages/market'));
|
||||
const ClosedMarketPage = lazy(() => import('../client-pages/closed-market'));
|
||||
const Portfolio = lazy(() => import('../client-pages/portfolio'));
|
||||
|
||||
const NotFound = () => (
|
||||
@@ -36,18 +40,21 @@ const NotFound = () => (
|
||||
);
|
||||
|
||||
export const routerConfig: RouteObject[] = compact([
|
||||
// Pages that don't use the LayoutWithSidebar must come first
|
||||
// to ensure they are matched before the catch all route '/*'
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
id: AppRoutes.HOME,
|
||||
},
|
||||
{
|
||||
path: 'disclaimer',
|
||||
element: <LayoutCentered />,
|
||||
id: Routes.DISCLAIMER,
|
||||
id: AppRoutes.DISCLAIMER,
|
||||
children: [{ index: true, element: <Disclaimer /> }],
|
||||
},
|
||||
// Referrals routing (the pages should be available if the feature flag is on)
|
||||
FLAGS.REFERRALS
|
||||
? {
|
||||
path: Routes.REFERRALS,
|
||||
path: AppRoutes.REFERRALS,
|
||||
element: <LayoutWithSky />,
|
||||
children: [
|
||||
{
|
||||
@@ -58,11 +65,11 @@ export const routerConfig: RouteObject[] = compact([
|
||||
element: <ReferralStatistics />,
|
||||
},
|
||||
{
|
||||
path: Routes.REFERRALS_CREATE_CODE,
|
||||
path: AppRoutes.REFERRALS_CREATE_CODE,
|
||||
element: <CreateCodeContainer />,
|
||||
},
|
||||
{
|
||||
path: Routes.REFERRALS_APPLY_CODE,
|
||||
path: AppRoutes.REFERRALS_APPLY_CODE,
|
||||
element: <ApplyCodeForm />,
|
||||
},
|
||||
],
|
||||
@@ -74,75 +81,74 @@ export const routerConfig: RouteObject[] = compact([
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
// All other pages will use the sidebar
|
||||
{
|
||||
path: '/*',
|
||||
element: <LayoutWithSidebar />,
|
||||
path: 'markets/*',
|
||||
element: (
|
||||
<LayoutWithSidebar
|
||||
header={<MarketHeader />}
|
||||
sidebar={<MarketsSidebar />}
|
||||
/>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Home />,
|
||||
id: Routes.HOME,
|
||||
element: <MarketsPage />,
|
||||
id: AppRoutes.MARKETS,
|
||||
},
|
||||
{
|
||||
path: 'markets',
|
||||
element: <Outlet />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Navigate to="all" />,
|
||||
},
|
||||
{
|
||||
path: 'all',
|
||||
element: <MarketsPage />,
|
||||
id: Routes.MARKETS,
|
||||
},
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
id: Routes.MARKET,
|
||||
},
|
||||
{
|
||||
path: 'all/closed/:marketId',
|
||||
element: <ClosedMarketPage />,
|
||||
id: Routes.CLOSED_MARKETS,
|
||||
},
|
||||
],
|
||||
path: 'all',
|
||||
element: <Navigate to="/markets" />,
|
||||
},
|
||||
{
|
||||
path: 'portfolio',
|
||||
element: <Outlet />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Portfolio />,
|
||||
id: Routes.PORTFOLIO,
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Assets />,
|
||||
id: Routes.ASSETS,
|
||||
children: [
|
||||
{ index: true, element: <Navigate to="deposit" /> },
|
||||
{ path: 'deposit', element: <Deposit />, id: Routes.DEPOSIT },
|
||||
{ path: 'withdraw', element: <Withdraw />, id: Routes.WITHDRAW },
|
||||
{ path: 'transfer', element: <Transfer />, id: Routes.TRANSFER },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'liquidity/:marketId',
|
||||
element: <Liquidity />,
|
||||
id: Routes.LIQUIDITY,
|
||||
},
|
||||
// NotFound page is here so its caught within parent '/*' route
|
||||
{
|
||||
path: '*',
|
||||
element: <NotFound />,
|
||||
path: ':marketId',
|
||||
element: <MarketPage />,
|
||||
id: AppRoutes.MARKET,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'portfolio/*',
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Portfolio />,
|
||||
id: AppRoutes.PORTFOLIO,
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
element: <Assets />,
|
||||
id: AppRoutes.ASSETS,
|
||||
children: [
|
||||
{ index: true, element: <Navigate to="deposit" /> },
|
||||
{ path: 'deposit', element: <Deposit />, id: AppRoutes.DEPOSIT },
|
||||
{ path: 'withdraw', element: <Withdraw />, id: AppRoutes.WITHDRAW },
|
||||
{ path: 'transfer', element: <Transfer />, id: AppRoutes.TRANSFER },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'liquidity/*',
|
||||
element: (
|
||||
<LayoutWithSidebar
|
||||
header={<LiquidityHeader />}
|
||||
sidebar={<LiquiditySidebar />}
|
||||
/>
|
||||
),
|
||||
id: AppRoutes.LIQUIDITY,
|
||||
children: [
|
||||
{
|
||||
path: ':marketId',
|
||||
element: <Liquidity />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
element: <NotFound />,
|
||||
},
|
||||
]);
|
||||
|
||||
export const ClientRouter = () => {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEagerConnect as useEthereumEagerConnect } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEagerConnect as useVegaEagerConnect,
|
||||
useVegaWallet,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useGlobalStore } from '../stores';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
|
||||
export const MaybeConnectEagerly = () => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const eagerConnecting = useVegaEagerConnect(Connectors);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||
);
|
||||
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [query] = useState(searchParams.get('address'));
|
||||
if (query && !pubKey) {
|
||||
connect(Connectors['view']);
|
||||
}
|
||||
useEffect(() => {
|
||||
update({ eagerConnecting });
|
||||
}, [update, eagerConnecting]);
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
export const PartyActiveOrdersHandler = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const variables = { partyId: pubKey || '' };
|
||||
const skip = !pubKey;
|
||||
useDataProvider({
|
||||
dataProvider: activeOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { pseudoRandom } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const generate = pseudoRandom(1);
|
||||
|
||||
export const SSRLoader = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<style jsx>{`
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<div style={{ width: 50, height: 50, display: 'flex', flexWrap: 'wrap' }}>
|
||||
{new Array(25).fill(null).map((_, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
animation: 'flickering 0.4s linear alternate infinite',
|
||||
animationDelay: `-${generate()}s`,
|
||||
animationDirection: i % 2 === 0 ? 'reverse' : 'alternate',
|
||||
background: 'black',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
useVegaTransactionManager,
|
||||
useVegaTransactionUpdater,
|
||||
} from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthTransactionManager,
|
||||
useEthTransactionUpdater,
|
||||
useEthWithdrawApprovalsManager,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { useLedgerDownloadManager } from '@vegaprotocol/ledger';
|
||||
|
||||
export const TransactionHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
useEthTransactionUpdater();
|
||||
useEthWithdrawApprovalsManager();
|
||||
useLedgerDownloadManager();
|
||||
return null;
|
||||
};
|
||||
@@ -1,116 +0,0 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #000;
|
||||
}
|
||||
.pre-loader .pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader .pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
.pre-loader .loader-item {
|
||||
background: #000;
|
||||
}
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(0) {
|
||||
animation-delay: 0ms;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -50ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
animation-delay: -1.4s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
animation-delay: 1.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -0.55s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
animation-delay: 1.2s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -0.65s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 0.7s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -3.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
html.dark .pre-loader .loader-item {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import { getAccountData } from './accounts-data-provider';
|
||||
import { AccountTable } from './accounts-table';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const singleRow = {
|
||||
__typename: 'AccountBalance',
|
||||
@@ -24,6 +25,48 @@ const singleRow = {
|
||||
} as AccountFields;
|
||||
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];
|
||||
|
||||
const zeroBalanceRow = {
|
||||
__typename: 'AccountBalance',
|
||||
type: Types.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
balance: '0',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
|
||||
},
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
decimals: 5,
|
||||
},
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
} as AccountFields;
|
||||
const zeroBalanceRowData = [zeroBalanceRow];
|
||||
|
||||
const onClickDepositMock = jest.fn();
|
||||
|
||||
describe('AccountsTable', () => {
|
||||
it('should render correct columns', async () => {
|
||||
// 7001-COLL-001
|
||||
@@ -50,6 +93,27 @@ describe('AccountsTable', () => {
|
||||
).toEqual(expectedHeaders);
|
||||
});
|
||||
|
||||
it('should render deposit button', async () => {
|
||||
render(
|
||||
<AccountTable
|
||||
rowData={zeroBalanceRowData}
|
||||
onClickAsset={() => null}
|
||||
isReadOnly={false}
|
||||
onClickDeposit={onClickDepositMock}
|
||||
pinnedAsset={{
|
||||
decimals: 5,
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const depositButton = screen.getByTestId('deposit');
|
||||
expect(depositButton).toBeVisible();
|
||||
await userEvent.click(depositButton);
|
||||
expect(onClickDepositMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should apply correct formatting', async () => {
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
@@ -68,6 +132,25 @@ describe('AccountsTable', () => {
|
||||
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 () => {
|
||||
const { container } = render(
|
||||
<AccountTable
|
||||
|
||||
@@ -9,6 +9,7 @@ import BigNumber from 'bignumber.js';
|
||||
import { AddressField, TransferFee, TransferForm } from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { addDecimal, formatNumber, removeDecimal } from '@vegaprotocol/utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
|
||||
@@ -33,7 +34,63 @@ describe('TransferForm', () => {
|
||||
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 () => {
|
||||
// 1003-TRAN-012
|
||||
// 1003-TRAN-013
|
||||
// 1003-TRAN-004
|
||||
render(<TransferForm {...props} />);
|
||||
submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3);
|
||||
@@ -62,6 +119,11 @@ describe('TransferForm', () => {
|
||||
});
|
||||
|
||||
it('validates fields and submits', async () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-003
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-014
|
||||
render(<TransferForm {...props} />);
|
||||
|
||||
// check current pubkey not shown
|
||||
@@ -174,6 +236,7 @@ describe('TransferForm', () => {
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
// 1003-TRAN-022
|
||||
expect(checkbox).not.toBeChecked();
|
||||
act(() => {
|
||||
/* fire events that update state */
|
||||
@@ -190,6 +253,7 @@ describe('TransferForm', () => {
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||
// 1003-TRAN-020
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
expectedAmount
|
||||
@@ -201,6 +265,8 @@ describe('TransferForm', () => {
|
||||
submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
@@ -263,6 +329,7 @@ describe('TransferForm', () => {
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
// 1003-TRAN-021
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
|
||||
@@ -4,9 +4,10 @@ import {
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AssetsDocument } from './__generated__/Assets';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import type { AssetsQuery } from './__generated__/Assets';
|
||||
import type { Asset } from './asset-data-provider';
|
||||
import { DENY_LIST } from './constants';
|
||||
|
||||
export interface BuiltinAssetSource {
|
||||
__typename: 'BuiltinAsset';
|
||||
@@ -48,6 +49,7 @@ export const assetsMapProvider = makeDerivedDataProvider<
|
||||
}
|
||||
);
|
||||
|
||||
/** Returns a record of assets by id */
|
||||
export const useAssetsMapProvider = () =>
|
||||
useDataProvider({
|
||||
dataProvider: assetsMapProvider,
|
||||
@@ -58,13 +60,32 @@ export const enabledAssetsProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never
|
||||
>([assetsProvider], ([assets]) =>
|
||||
(assets as ReturnType<typeof getData>).filter(
|
||||
(a) => a.status === Schema.AssetStatus.STATUS_ENABLED
|
||||
)
|
||||
(assets as ReturnType<typeof getData>)
|
||||
.filter((a) => a.status === AssetStatus.STATUS_ENABLED)
|
||||
.filter((a) => {
|
||||
const env = process.env['NX_VEGA_ENV'];
|
||||
|
||||
if (!env || !DENY_LIST[env]) return true;
|
||||
|
||||
if (DENY_LIST[env].includes(a.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
/** Returns all assets */
|
||||
export const useAssetsDataProvider = () =>
|
||||
useDataProvider({
|
||||
dataProvider: assetsProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
|
||||
/** Returns assets that are enabled and are not on the deny list */
|
||||
export const useEnabledAssets = () => {
|
||||
return useDataProvider({
|
||||
dataProvider: enabledAssetsProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,67 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
|
||||
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
|
||||
);
|
||||
|
||||
// List of defunct and no longer used assets that were created for various testnets
|
||||
export const DENY_LIST: Record<string, string[]> = {
|
||||
TESTNET: [
|
||||
'07f8ce4e3df6c649f3fd794112f545ede9fbd9cfa76a9e380a7502a8d525e4db',
|
||||
'0b87ac58d4af7fc11c8b417153fcb62631cfd9643835ef28db3f5a1caef0b379',
|
||||
'169fd651dd1303fc6f3e1dee8349439140cf467a190bc9a04bff3e617509cfd4',
|
||||
'16ae5dbb1fd7aa2ddef725703bfe66b3647a4da7b844bfdd04e985756f53d9d6',
|
||||
'177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
|
||||
'20ffe5b1f70364a6b976007d96d306345bc7b880256ddffbbea2c590096ef375',
|
||||
'2792b6ead78a812bd0169b2adbe9c2ea5b8c21510e40b93bf8d2cba6a0838e90',
|
||||
'27c0229ab9edc6a3ba6a3e9716c1b324d6515a3168004256879e2de7c240f453',
|
||||
'285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8',
|
||||
'2af02165ef5ea1d1284b4251117eaae45faa961ec918e3f69b66903a18822387',
|
||||
'2bf406160d06280d92412605eb397d08142736f4e17967e404dc892da278e9da',
|
||||
'373d90d97bd3f97f0c13cf449926cfd7f1ed5f3746c3091e8dd894d60bf5eac8',
|
||||
'3c7d6bd83dac3174923ba65f3af01f42858e267ca5b722fe2b0b3fec14e98ec2',
|
||||
'434b502cabb7a9a36d3036a599d589537da293d8bbab224692629351a7926249',
|
||||
'43bb50d6ba0a871088014cb204079ace4811970ce3d52b15896f6e7a88f900b7',
|
||||
'46e9d23142762db57751b1b507e15747fe9478de504cbc4a0bffd828c39e3776',
|
||||
'4b6957c6a184b002db80c7a4dd811d0e70866be4a4c8fffa4894e78aa132b407',
|
||||
'4cbe1ad4cf232fb2232176e3f440f6e6fdc3ba7b1d95a99cb69116b9e379eae8',
|
||||
'4e4e80abff30cab933b8c4ac6befc618372eb76b2cbddc337eff0b4a3a4d25b8',
|
||||
'51eed0c17e3287996ba6006ce56de2591550ebfad46631f3a0129886967420e1',
|
||||
'522e09779d984ae77b60036f4ecd80f0491f5f0c8cc386a0712b78a020ab62e6',
|
||||
'5c950d4cfe1aaf9174c4358ee5df03f7e6bc556eac06a012cf6c07db154e20c4',
|
||||
'5d8eb4e4e385b85c5c11f47111f315d5d201e04f4ef5e294763bf51036974b91',
|
||||
'5dd5c226e944b4c3df3e8dbbbc473e72fb134f4c5b82a1968cc3cf7c1b40fe9e',
|
||||
'638b4dbe4f3ce5b81546bef29108e8374a7293012c6189ca00f287f8c62a98a5',
|
||||
'644c3a6e258382542a108c4dca16fae93657b6ff40238b2bb55854a125316052',
|
||||
'71ea74ef1aa7582bcdd4ef843b3e79584ded4d16e692f3946cac4c9d61aed4f2',
|
||||
'72f051ea66686ae004086f1ad086866f720f25896319abf3427cae101a58d985',
|
||||
'827bbacde0dd5bfec857933dbbf642147f2afe6a9db0a543839b1b5239ca6786',
|
||||
'83f4970aa1aeab85d01d8a976cbfd0fb3afedf391e18197e8c94c4021b3dffa1',
|
||||
'84a20b554854c9af10ef84d308795dfae455834d1a207798292d66e19fd72c92',
|
||||
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f',
|
||||
'85771250f0aa84727a1c01ba8926e59217ca13a7706ba03cf2ad95abbbc2e3bc',
|
||||
'8a246b15bf185065d3f41a710f3eea0fbbe8754a8464501cf4a6d253d69c6423',
|
||||
'8e5553ee4e95db51523640d1d03b00c8ab31dd69d787e169b0d958ef52e19a0a',
|
||||
'92747340d922e9cda5bde17c46af8b409e47d6342f3b9c8fb058a34c6206d373',
|
||||
'9b43c6791aebed5f7b38d91907539173aa278de88294b6422f3dd8282cd2c6e2',
|
||||
'9e0bb9bd7ea2ec51efcdc98b432b6f0b055b2ed7973cfac9b44899d6e6c5deab',
|
||||
'a0cf0835cb5af7bd03c56d12d846eed81c182eab36ea61c074961c115da9c8c4',
|
||||
'a26dc4f6dea91e80d42e4736da7fe3c42f6275df9c4dca48be3ef96850192242',
|
||||
'a4c945aea50ef321eabf8a3bcf0b8bb74288452c4638876ee4df5fb59be4a0ec',
|
||||
'ae28c1043e79bdb95cae0e9919d4436157d14b183e24ab4572793812f3d89222',
|
||||
'b0cf8be4e4d1d2ac5156c2298aa42edc7a74d25b7838d06e2279e94018a47f3f',
|
||||
'b6d11705cb456768ff8271074dd8adcd78bd042a27fc205e9b52f9f945f42ea1',
|
||||
'be478570584211521890fb65a41bded4d2bf29beb0b61ac3cc3b97f828a27b22',
|
||||
'befde6d78ea1c996ae1d959342730220e274992097aeeae00c908a971314e6ae',
|
||||
'bf5b9464d154ffeff57f05f90b223c1045f0754befd01a9a42c99382115e7833',
|
||||
'd2239a7c92bc5c72a959a73ccee7727ba0966f4d88a33d71dbbc26e3f171555d',
|
||||
'd3f9cf6e1711595b4588366a96b725fc039f72a8a28b2e4c1b630016dd2eed39',
|
||||
'd9a9ab00bb79ce7dca7f6329ab627dcea2e53e5c16b85bf4aa8c3980b76fad57',
|
||||
'dd73f7d1e721ef5cba95f454a3019521456c1b43b97084ad29e3e6509148d45d',
|
||||
'eb30d55e90e1f9e5c4727d6fa2a5a8cd36ab9ae9738eb8f3faf53e2bee4861ee',
|
||||
'ede4076aef07fd79502d14326c54ab3911558371baaf697a19d077f4f89de399',
|
||||
'f481abe5700c6940ca996adfa9d07c9d9f07a486a7ce9cdb2e6ee0fa6450fe31',
|
||||
'f7355ec52730c2e6ba02216da8501b4b6934cd363511e9d47ae5b97056d02703',
|
||||
'fdf0ec118d98393a7702cf72e46fc87ad680b152f64b2aac59e093ac2d688fbb',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ export * from '../deposits/src/lib/deposit.mock';
|
||||
export * from '../environment/src/utils/node.mock';
|
||||
export * from '../environment/src/components/node-guard/node-guard.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 '../market-depth/src/lib/market-depth.mock';
|
||||
export * from '../markets/src/lib/components/market-info/market-info.mock';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user