Compare commits

..
Author SHA1 Message Date
Matthew Russell cd0b29c251 fix: use t function for text content in zero balance message 2023-06-08 09:15:10 -07:00
Matthew Russell 6471be323d test: update e2e tests with ac for button state 2023-06-07 22:25:16 -07:00
Matthew Russell 3e9aeb2f2f test: check that button is enabled before form is filled in 2023-06-07 22:14:36 -07:00
Matthew Russell e83a7915ad chore: make assertion check for enabled 2023-06-07 22:09:03 -07:00
Matthew Russell d2b2d16c9c test: update to check that button is always enabled 2023-06-07 22:03:23 -07:00
Matthew Russell 16b2360da7 chore: make deal ticket notification buttons smaller 2023-06-07 17:31:13 -07:00
Matthew Russell 1adbc2dbd7 chore: remove disabled submit button 2023-06-07 17:20:59 -07:00
Ciaran McGhie d6f39049bd chore(ui-toolkit,react-helpers,utils): publish new versions of ui-toolkit, react-helpers and utils (#4049) 2023-06-07 15:58:20 -07:00
Maciek 1d06be8f4e fix: wrong css class (#4053) 2023-06-07 20:44:28 +00:00
Edd 5eba8fe28f fix(explorer): fix broken protocol upgrade tx link (#4029) 2023-06-07 14:47:55 +01:00
m.ray 2ba0e9a1b2 chore(trading): add quantum formatting to deal ticket (#4030) 2023-06-07 11:49:50 +01:00
43d3754c64 feat(trading): 3945 orderbook enhancements (#4016)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-06-07 10:58:34 +02:00
m.ray aae5c44fa4 fix(trading): market selector volume update (#3989) 2023-06-06 18:45:52 -07:00
Art 878bed9c7a fix(withdraws): minimal withdrawal amount validation (#3993) 2023-06-06 22:21:48 +01:00
Sam Keen 6f9f432c90 feat(governance): multisig warning (#3994) 2023-06-06 22:21:31 +01:00
Sam Keen 3f01b93159 feat(governance,proposals,web3): remove use of word "we" in copy (#4034) 2023-06-06 22:21:17 +01:00
Edd 473c244d7b chore(governance,liquidity-provision-dashboard,trading): remove broken urls (#4042) 2023-06-06 22:19:42 +01:00
Joe Tsang d6a32f5090 chore(governance): fix flaky test failures (#4043) 2023-06-06 22:19:23 +01:00
Mikołaj Młodzikowski 71540a90fb fix(ci): resolving bucket name 2023-06-06 17:02:24 +02:00
Mikołaj Młodzikowski cefdbe6a3c fix(ci): missing space on resolving projects 2023-06-06 16:46:58 +02:00
Mikołaj Młodzikowski 3928dd5c0e fix(ci): way of resolving affected 2023-06-06 15:37:06 +02:00
Ciaran McGhie 1521bab4c4 chore(react-helpers,utils,logger): create logger lib and move sentry/logger utils there (#3990) 2023-06-06 14:10:03 +01:00
Mikołaj Młodzikowski b3036d520f fix(ci): way of resolving affected 2023-06-06 15:03:18 +02:00
Mikołaj Młodzikowski 429a5a23d3 fix(ci): way of resolving affected 2023-06-06 14:48:49 +02:00
Mikołaj Młodzikowski 5b02fd5d54 fix(ci): way of resolving affected 2023-06-06 14:39:28 +02:00
Mikołaj Młodzikowski b8309a76e7 fix(ci): way of resolving affected 2023-06-06 14:29:46 +02:00
Mikołaj Młodzikowski e8ae085c06 fix(ci): typo in app name 2023-06-06 14:18:47 +02:00
Mikołaj Młodzikowski fdcd24847c fix(ci): syntax for publishing dist 2023-06-06 14:07:56 +02:00
Mikołaj Młodzikowski e054db39b5 feat(ci): add deploys for multisig-signer (#4039) 2023-06-06 13:47:42 +02:00
Joe Tsang a01e48d508 chore(governance): nightly e2e test fixes (#4038) 2023-06-06 12:12:59 +01:00
Matthew Russell 0d96c487d9 fix(trading): use ag grid to handle error,loading and no data message (#4012) 2023-06-06 00:08:19 -07:00
m.rayandMatthew Russell 011e4e4cec fix(trading): market info refactor (#3985)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-06-05 19:00:29 -07:00
Bartłomiej Głownia d6e2432955 fix(accounts): avoid last row re-rendering (#3950) 2023-06-05 12:40:34 +02:00
Ben 18c034b910 test(trading): 6501-ASSE-assets e2e tests (#4026) 2023-06-05 09:13:24 +01:00
daro-maj 849cbb43c4 test(trading): 6001-MARK-find_markets e2e tests (#4025) 2023-06-05 08:25:31 +01:00
Edd cd34cb181f feat(explorer): properly represent batch error 71 (#4019) 2023-06-02 14:43:33 +01:00
Edd 97b3f4decf feat(explorer): do not colourise votes in tx list (#3988) 2023-06-01 12:35:33 +01:00
Ben 0465fb8ca9 test(trading): key to key transfer tests (#4014) 2023-06-01 11:37:41 +01:00
daro-maj 22af89e78c test(trading): reverted node tests (#3992) 2023-06-01 08:32:31 +01:00
173 changed files with 3515 additions and 3723 deletions
+34 -5
View File
@@ -113,15 +113,19 @@ jobs:
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
if [[ $affected == *"governance"* ]]; then
preview_tools="not deployed"
if echo "$affected" | grep -q governance; then
echo "Governance is affected"
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if [[ $affected == *"trading"* ]]; then
if echo "$affected" | grep -q trading; then
echo "Trading is affected"
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if [[ $affected == *"explorer"* ]]; then
if echo "$affected" | grep -q explorer; then
echo "Explorer is affected"
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
@@ -131,13 +135,30 @@ jobs:
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects+=' "multisig-signer" '
fi
if [[ "${{ github.ref }}" =~ .*develop$ ]]; then
echo "Deploying tools on s3"
projects+=' "multisig-signer" '
fi
fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
projects=${projects%?}
projects=[${projects// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
echo PROJECTS=$projects >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
echo PREVIEW_TOOLS=$preview_tools >> $GITHUB_ENV
outputs:
projects: ${{ env.PROJECTS }}
@@ -145,11 +166,12 @@ jobs:
preview_governance: ${{ env.PREVIEW_GOVERNANCE }}
preview_trading: ${{ env.PREVIEW_TRADING }}
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }}
cypress:
needs: lint-test-build
name: '(CI) cypress'
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
@@ -203,6 +225,12 @@ jobs:
sleep 5
done
fi
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
until curl -L --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
echo "waiting for tools preview"
sleep 5
done
fi
- name: Create comment
uses: peter-evans/create-or-update-comment@v3
@@ -214,6 +242,7 @@ jobs:
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
# Report single result at the end, to avoid mess with required checks in PR
cypress-check:
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
runs-on: self-hosted-runner
timeout-minutes: 60
timeout-minutes: 100
steps:
# Checks if skip cache was requested
- name: Set skip-nx-cache flag
+10 -2
View File
@@ -70,6 +70,10 @@ jobs:
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1"
if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
envName="mainnet"
bucketName="tools.vega.xyz"
fi
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
envName="mainnet"
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ "${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }}" = "true" ]]; then
@@ -78,10 +82,14 @@ jobs:
if [[ "${envName}" = "mainnet" ]]; then
domain="vega.xyz"
bucketName="${{ matrix.app }}.${domain}"
if [[ -z "${bucketName}" ]]; then
bucketName="${{ matrix.app }}.${domain}"
fi
elif [[ "${envName}" = "testnet" ]]; then
domain="fairground.wtf"
bucketName="${{ matrix.app }}.${domain}"
if [[ -z "${bucketName}" ]]; then
bucketName="${{ matrix.app }}.${domain}"
fi
fi
if [[ -z "${bucketName}" ]]; then
+1
View File
@@ -15,6 +15,7 @@ NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases/tag/
# App flags
NX_EXPLORER_ASSETS=1
@@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { PriceMonitoringBoundsInfoPanel } from '@vegaprotocol/markets';
import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
@@ -39,133 +40,69 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return [];
};
const oraclePanels = isEqual(
const showTwoOracles = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
]
: [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
];
);
const panels = [
{
title: t('Key details'),
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel noBorder={false} market={market} />,
},
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable noBorder={false} data={trigger} />,
})
),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<>
<MarketInfoTable
noBorder={false}
data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
/>
<MarketInfoTable
noBorder={false}
data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
</>
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
<LiquidityMonitoringParametersInfoPanel
noBorder={false}
market={market}
/>
),
},
{
title: t('Liquidity'),
content: <LiquidityInfoPanel market={market} noBorder={false} />,
},
{
title: t('Liquidity price range'),
content: (
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
),
},
...oraclePanels,
];
const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
return (
<>
{panels.map((p) => (
<div key={p.title} className="mb-3">
<h2 className="font-alpha calt text-xl">{p.title}</h2>
{p.content}
</div>
<div>
<h2 className={headerClassName}>{t('Key details')}</h2>
<KeyDetailsInfoPanel market={market} />
<h2 className={headerClassName}>{t('Instrument')}</h2>
<InstrumentInfoPanel market={market} />
<h2 className={headerClassName}>{t('Settlement asset')}</h2>
<SettlementAssetInfoPanel market={market} />
<h2 className={headerClassName}>{t('Metadata')}</h2>
<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('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring bounds %s', [(i + 1).toString()])}
</h2>
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={i + 1}
/>
</>
))}
</>
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
{showTwoOracles ? (
<>
<h2 className={headerClassName}>{t('Settlement oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
<h2 className={headerClassName}>{t('Termination oracle')}</h2>
<OracleInfoPanel market={market} type="termination" />
</>
) : (
<>
<h2 className={headerClassName}>{t('Oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
</>
)}
</div>
);
};
@@ -5,12 +5,13 @@ export const ErrorCodes = new Map([
[51, 'Transaction failed validation'],
[60, 'Transaction could not be decoded'],
[70, 'Error'],
[71, 'Partial success/error'],
[80, 'Unknown command'],
[89, 'Rejected as spam'],
[0, 'Success'],
]);
export const successCodes = new Set([0]);
export const successCodes = new Set([0, 71]);
interface ChainResponseCodeProps {
code: number;
@@ -29,11 +30,12 @@ export const ChainResponseCode = ({
error,
}: ChainResponseCodeProps) => {
const isSuccess = successCodes.has(code);
const successColour =
code === 71 ? 'fill-vega-orange' : 'fill-vega-green-600';
const icon = isSuccess ? (
<Icon name="tick-circle" className="fill-vega-green-550" />
<Icon name="tick-circle" className={successColour} />
) : (
<Icon name="cross" className="fill-vega-pink-550" />
<Icon name="cross" className="fill-vega-pink-600" />
);
const label = ErrorCodes.get(code) || 'Unknown response code';
@@ -160,6 +160,7 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
vote={command?.voteSubmission?.value === 'VALUE_YES'}
yesText="Proposal vote"
noText="Proposal vote"
useVoteColour={false}
/>
);
}
@@ -34,4 +34,17 @@ describe('Vote TX icon', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.getByRole('img')).toHaveAttribute('aria-label', 'delete icon');
});
it('useVoteColour prop can be used to override coloured background', () => {
const no = render(<VoteIcon vote={false} />);
expect(no.container.children[0]).toHaveClass('bg-vega-pink-550');
const monochromeNo = render(
<VoteIcon vote={false} useVoteColour={false} />
);
expect(monochromeNo.container.children[0]).not.toHaveClass(
'bg-vega-pink-550'
);
expect(monochromeNo.container.children[0]).toHaveClass('bg-vega-dark-200');
});
});
@@ -8,6 +8,32 @@ export interface VoteIconProps {
yesText?: string;
// Defaults to 'Against', but can be any text
noText?: string;
// If set to false the background will not be coloured
useVoteColour?: boolean;
}
function getBgColour(useVoteColour: boolean, vote: boolean) {
if (useVoteColour === false) {
return 'bg-vega-dark-200';
}
return vote ? 'bg-vega-green-550' : 'bg-vega-pink-550';
}
function getFillColour(useVoteColour: boolean, vote: boolean) {
if (useVoteColour === false) {
return 'white';
}
return vote ? 'vega-green-300' : 'vega-pink-300';
}
function getTextColour(useVoteColour: boolean, vote: boolean) {
if (useVoteColour === false) {
return 'white';
}
return vote ? 'vega-green-200' : 'vega-pink-200';
}
/**
@@ -18,14 +44,15 @@ export interface VoteIconProps {
*/
export function VoteIcon({
vote,
useVoteColour = true,
yesText = 'For',
noText = 'Against',
}: VoteIconProps) {
const label = vote ? yesText : noText;
const bg = vote ? 'bg-vega-green-550' : 'bg-vega-pink-550';
const icon: IconName = vote ? 'tick-circle' : 'delete';
const fill = vote ? 'vega-green-300' : 'vega-pink-300';
const text = vote ? 'vega-green-200' : 'vega-pink-200';
const bg = getBgColour(useVoteColour, vote);
const fill = getFillColour(useVoteColour, vote);
const text = getTextColour(useVoteColour, vote);
return (
<div
@@ -103,8 +103,6 @@ describe(
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
const proposalTitle = generateFreeFormProposalTitle();
const proposalTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
// const currentDate = new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
// const proposedDate = new Date(currentDate.getTime() + 60000)
submitUniqueRawProposal({
proposalTitle: proposalTitle,
@@ -14,6 +14,7 @@ import {
createUpdateNetworkProposalTxBody,
createFreeFormProposalTxBody,
} from '../../support/proposal.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
@@ -43,6 +44,7 @@ context(
waitForSpinner();
cy.connectVegaWallet();
ethereumWalletConnect();
ensureSpecifiedUnstakedTokensAreAssociated('1');
navigateTo(navigation.proposals);
});
@@ -1,5 +1,6 @@
import {
closeDialog,
dissociateFromSecondWalletKey,
navigateTo,
navigation,
turnTelemetryOff,
@@ -15,7 +16,11 @@ import {
governanceProposalType,
voteForProposal,
} from '../../support/governance.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
import {
ensureSpecifiedUnstakedTokensAreAssociated,
stakingPageAssociateTokens,
stakingPageDisassociateAllTokens,
} from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import {
vegaWalletSetSpecifiedApprovalAmount,
@@ -100,9 +105,9 @@ context(
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
cy.get(newProposalSubmitButton).click();
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
});
cy.get(newProposalSubmitButton).click();
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
});
// 3007-PNEC-001 3007-PNEC-003
@@ -182,13 +187,17 @@ context(
'have.text',
'Proposal will fail if enactment is earlier than the voting deadline'
);
cy.get(proposalDownloadBtn).click();
cy.wrap(
getDownloadedProposalJsonPath('vega-network-param-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-network-param-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.get(newProposalSubmitButton).click();
validateFeedBackMsg(
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
@@ -207,13 +216,17 @@ context(
delay: 2,
});
});
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(getDownloadedProposalJsonPath('vega-new-market-proposal-')).then(
(filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
}
);
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
});
});
});
it('Unable to submit new market proposal with missing/invalid fields', function () {
@@ -233,13 +246,17 @@ context(
delay: 2,
});
});
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(getDownloadedProposalJsonPath('vega-new-market-proposal-')).then(
(filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}
);
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(errorMsg);
@@ -248,6 +265,9 @@ context(
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
// 3002-PROP-022
it('Unable to submit update market proposal without equity-like share in the market', function () {
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click(); // switch to second wallet pub key
stakingPageAssociateTokens('1');
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal - rejected');
cy.get(newProposalDescription).type('E2E test for proposals');
@@ -259,17 +279,25 @@ context(
delay: 2,
});
});
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
validateDialogContentMsg('PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
ensureSpecifiedUnstakedTokensAreAssociated('1');
closeDialog();
ethereumWalletConnect();
stakingPageDisassociateAllTokens();
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
});
// 3002-PROP-020
@@ -291,13 +319,17 @@ context(
delay: 2,
});
});
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(
@@ -335,13 +367,17 @@ context(
delay: 2,
});
});
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-update-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
});
navigateTo(navigation.proposals);
cy.get('@EnactedMarketId').then((marketId) => {
cy.contains(String(marketId).slice(0, 6))
@@ -390,14 +426,17 @@ context(
cy.get(minVoteDeadline).click();
cy.get(minValidationDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(getDownloadedProposalJsonPath('vega-new-asset-proposal-')).then(
(filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
}
);
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-asset-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
});
});
cy.get(newProposalSubmitButton).should('be.visible').click();
closeDialog();
cy.get(newProposalSubmitButton).should('be.visible').click();
@@ -433,13 +472,17 @@ context(
enterUpdateAssetProposalDetails();
cy.get(minVoteDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
});
navigateTo(navigation.proposals);
cy.get(openProposals).within(() => {
cy.get(proposalType)
@@ -471,13 +514,17 @@ context(
enterUpdateAssetProposalDetails();
cy.get(maxVoteDeadline).click();
cy.get(maxEnactDeadline).click();
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.wrap(
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
});
});
it('Unable to submit edit asset proposal with missing/invalid fields', function () {
@@ -525,6 +572,13 @@ context(
});
});
after('Disassociate from second wallet key if present', function () {
cy.reload();
waitForSpinner();
ethereumWalletConnect();
dissociateFromSecondWalletKey();
});
function validateDialogContentMsg(expectedMsg: string) {
cy.getByTestId('dialog-content')
.last()
@@ -86,11 +86,12 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
});
it('Newly created proposals list - shows title and portion of summary', function () {
const proposalPath = '/proposals/new-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const proposalPath = 'src/fixtures/proposals/new-market-raw.json';
const proposalTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
enactmentTimestamp: proposalTimestamp,
closingTimestamp: proposalTimestamp,
}); // 3001-VOTE-052
// 3001-VOTE-008
// 3001-VOTE-034
@@ -29,13 +29,17 @@ context('rewards - flow', { tags: '@slow' }, function () {
turnTelemetryOff();
cy.visit('/');
waitForSpinner();
depositAsset(vegaAssetAddress, '1000', 18);
ethereumWalletConnect();
cy.connectVegaWallet();
depositAsset(vegaAssetAddress, '1000', 18);
cy.getByTestId('currency-title', txTimeout).should(
'contain.text',
'Collateral'
);
vegaWalletTeardown();
cy.associateTokensToVegaWallet('6000');
cy.VegaWalletTopUpRewardsPool(30, 200);
navigateTo(navigation.validators);
cy.VegaWalletTopUpRewardsPool();
cy.get(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
'6,000.0',
@@ -109,6 +109,7 @@ context(
cy.getByTestId(userStake, epochTimeout)
.first()
.should('have.text', '2.00');
waitForBeginningOfEpoch();
cy.getByTestId('total-stake').first().realHover();
cy.getByTestId('staked-by-user-tooltip')
.first()
@@ -379,6 +380,7 @@ context(
});
it('Disassociating some tokens - prioritizes unstaked tokens', function () {
vegaWalletSetSpecifiedApprovalAmount('1000');
stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0);
cy.get('button').contains('Select a validator to nominate').click();
@@ -485,6 +487,7 @@ context(
});
afterEach('Teardown Wallet', function () {
navigateTo(navigation.home);
vegaWalletTeardown();
});
@@ -24,7 +24,6 @@ const ethWalletContainer = '[data-testid="ethereum-wallet"]';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const currencyTitle = '[data-testid="currency-title"]:visible';
const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
@@ -39,7 +38,7 @@ const associatedKey = '[data-testid="associated-key"]';
const associatedAmount = '[data-testid="associated-amount"]';
const associateCompleteText = '[data-testid="transaction-complete-body"]';
const disassociationWarning = '[data-testid="disassociation-warning"]';
const vegaWallet = '[data-testid="vega-wallet"]';
const vegaWallet = 'aside [data-testid="vega-wallet"]';
context(
'Token association flow - with eth and vega wallets connected',
@@ -79,27 +78,15 @@ context(
//0005-ETXN-003
//0005-ETXN-005
stakingPageAssociateTokens('2', { skipConfirmation: true });
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
// 0005-ETXN-002
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
});
@@ -114,12 +101,11 @@ context(
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('6,002.00');
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageDisassociateTokens('2');
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '0.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
cy.get(
'[data-testid="eth-wallet-associated-balances"]:visible',
txTimeout
@@ -132,38 +118,26 @@ context(
stakingPageAssociateTokens('1001', { approve: true });
verifyEthWalletAssociatedBalance('1,001.00');
verifyEthWalletTotalAssociatedBalance('7,001.00');
cy.get(vegaWallet)
.last()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'1,001.00'
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'1,001.00'
);
});
});
it('Able to disassociate a partial amount of tokens currently associated', function () {
stakingPageAssociateTokens('2');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageDisassociateTokens('1');
verifyEthWalletAssociatedBalance('1.0');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
1.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 1.0);
});
});
it('Able to disassociate all tokens - using max', function () {
@@ -171,15 +145,11 @@ context(
const warningText =
'Warning: Any tokens that have been nominated to a node will sacrifice rewards they are due for the current epoch. If you do not wish to sacrifice these, you should remove stake from a node at the end of an epoch before disassociation.';
stakingPageAssociateTokens('2');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
cy.get(ethWalletDissociateButton).click();
cy.get(disassociationWarning).should('contain', warningText);
stakingPageDisassociateAllTokens();
@@ -197,14 +167,9 @@ context(
'not.exist'
);
});
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
0.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 0.0);
});
});
it('Able to associate and disassociate vesting contract tokens', function () {
@@ -219,32 +184,22 @@ context(
type: 'contract',
skipConfirmation: true,
});
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
stakingPageDisassociateTokens('1', {
type: 'contract',
skipConfirmation: true,
});
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '1.00');
validateWalletCurrency('Total associated after pending', '1.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
verifyEthWalletAssociatedBalance('1.0');
verifyEthWalletTotalAssociatedBalance('1.0');
});
@@ -256,6 +211,7 @@ context(
// 1004-ASSO-022
stakingPageAssociateTokens('21', { type: 'wallet' });
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageAssociateTokens('37', { type: 'contract' });
cy.get(vestingContractSection)
.first()
@@ -275,28 +231,18 @@ context(
);
cy.get(associatedAmount, txTimeout).should('contain', 21);
});
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
58
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 58);
});
stakingPageDisassociateTokens('6', { type: 'contract' });
cy.get(vestingContractSection)
.first()
.within(() => {
cy.get(associatedAmount, txTimeout).should('contain', 31);
});
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
52
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52);
});
navigateTo(navigation.validators);
stakingPageDisassociateTokens('9', { type: 'wallet' });
cy.get(vegaInWalletSection)
@@ -304,14 +250,9 @@ context(
.within(() => {
cy.get(associatedAmount, txTimeout).should('contain', 12);
});
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
43
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 43);
});
});
it('Not able to associate more tokens than owned', function () {
@@ -328,11 +269,9 @@ context(
// 1004-ASSO-004
it('Pending association outside of app is shown', function () {
vegaWalletAssociate('2');
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '0.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '2.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
validateWalletCurrency('Associated', '2.00');
});
@@ -341,11 +280,9 @@ context(
cy.wrap(validateWalletCurrency('Associated', '2.00')).then(() => {
vegaWalletDisassociate('2');
});
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
validateWalletCurrency('Associated', '2.00');
validateWalletCurrency('Pending association', '2.00');
validateWalletCurrency('Total associated after pending', '0.00');
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
validateWalletCurrency('Associated', '0.00');
});
@@ -364,14 +301,9 @@ context(
Cypress.env('vegaWalletPublicKey2')
);
stakingPageAssociateTokens('2');
cy.get(vegaWallet)
.first()
.within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get(associateCompleteText).should(
'have.text',
`Vega key ${Cypress.env(
@@ -1,8 +1,11 @@
import { stakingPageDisassociateAllTokens } from './staking.functions';
const tokenDropDown = 'state-trigger';
const txTimeout = Cypress.env('txTimeout');
export enum navigation {
section = 'nav',
home = '[href="/"]',
vesting = '[href="/token/redeem"]',
validators = '[href="/validators"]',
rewards = '[href="/rewards"]',
@@ -18,6 +21,7 @@ export function convertTokenValueToNumber(subject: string) {
}
const topLevelRoutes = [
navigation.home,
navigation.proposals,
navigation.validators,
navigation.rewards,
@@ -97,3 +101,25 @@ export function turnTelemetryOff() {
win.localStorage.setItem('vega_telemetry_on', 'false')
);
}
export function dissociateFromSecondWalletKey() {
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
cy.getByTestId('vega-in-wallet')
.first()
.within(() => {
cy.getByTestId('eth-wallet-associated-balances')
.last()
.within(() => {
cy.getByTestId('associated-key')
.invoke('text')
.as('associatedPubKey');
});
});
cy.get('@associatedPubKey').then((associatedPubKey) => {
if (associatedPubKey == secondWalletKey) {
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
stakingPageDisassociateAllTokens();
}
});
}
@@ -59,29 +59,29 @@ export function submitUniqueRawProposal(proposalFields: {
submit?: boolean;
}) {
goToMakeNewProposal(governanceProposalType.RAW);
let proposalBodyPath = '/proposals/raw.json';
let proposalBodyPath = 'src/fixtures/proposals/raw.json';
if (proposalFields.proposalBody) {
proposalBodyPath = proposalFields.proposalBody;
}
cy.readFile(proposalBodyPath).then((rawProposal) => {
if (!proposalFields.proposalBody) {
if (proposalFields.proposalTitle) {
rawProposal.rationale.title = proposalFields.proposalTitle;
cy.wrap(proposalFields.proposalTitle).as('proposalTitle');
}
if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription;
}
if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else {
const minTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
rawProposal.terms.closingTimestamp = minTimeStamp;
}
if (proposalFields.enactmentTimestamp) {
rawProposal.terms.enactmentTimestamp =
proposalFields.enactmentTimestamp;
}
if (proposalFields.proposalTitle) {
rawProposal.rationale.title = proposalFields.proposalTitle;
cy.wrap(proposalFields.proposalTitle).as('proposalTitle');
}
if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription;
}
if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else if (
!proposalFields.closingTimestamp &&
!proposalFields.proposalBody
) {
const minTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
rawProposal.terms.closingTimestamp = minTimeStamp;
}
if (proposalFields.enactmentTimestamp) {
rawProposal.terms.enactmentTimestamp = proposalFields.enactmentTimestamp;
}
const proposalPayload = JSON.stringify(rawProposal);
@@ -236,8 +236,8 @@ export function validateWalletCurrency(
currencyTitle: string,
expectedAmount: string
) {
cy.get("[data-testid='currency-title']")
.contains(currencyTitle)
cy.get("[data-testid='currency-title']", txTimeout)
.contains(currencyTitle, txTimeout)
.parent()
.parent()
.within(() => {
@@ -19,7 +19,7 @@ const ethStakingBridgeContractAddress = Cypress.env(
);
const ethProviderUrl = Cypress.env('ethProviderUrl');
const getAccount = (number = 0) => `m/44'/60'/0'/0/${number}`;
const transactionTimeout = 100000;
const transactionTimeout = { timeout: 100000, log: false };
const Erc20BridgeAddress = '0x9708FF7510D4A7B9541e1699d15b53Ecb1AFDc54';
const provider = new ethers.providers.JsonRpcProvider({ url: ethProviderUrl });
@@ -43,10 +43,7 @@ export async function depositAsset(
const faucet = new Token(assetEthAddress, signer);
cy.wrap(
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
{
timeout: transactionTimeout,
log: false,
}
transactionTimeout
).then(() => {
const collateralBridge = new CollateralBridge(Erc20BridgeAddress, signer);
cy.wrap(
@@ -55,7 +52,7 @@ export async function depositAsset(
amount + '0'.repeat(decimalPlaces),
'0x' + vegaWalletPubKey
),
{ timeout: transactionTimeout, log: false }
transactionTimeout
);
});
}
@@ -79,13 +76,13 @@ export async function vegaWalletTeardown() {
}
});
cy.get(vegaWalletContainer).within(() => {
cy.get(associatedAmountInWallet, {
timeout: transactionTimeout,
})
.should('have.length', 1, { timeout: transactionTimeout })
.contains('0.00', {
timeout: transactionTimeout,
});
cy.get(associatedAmountInWallet, transactionTimeout).should(
'have.length',
1
);
cy.get(associatedAmountInWallet)
.first(transactionTimeout)
.should('have.text', '0.00');
});
});
}
@@ -109,7 +106,7 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
cy.highlight('Tearing down staking tokens from vega wallet if present');
cy.wrap(
stakingBridgeContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
{ timeout: transactionTimeout }
transactionTimeout
).then((stakeBalance) => {
if (Number(stakeBalance) != 0) {
cy.get(vegaWalletContainer).within(() => {
@@ -122,31 +119,25 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
String(stakeBalance),
vegaWalletPubKey
),
{ timeout: transactionTimeout }
transactionTimeout
);
cy.wrap(
vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
{
timeout: transactionTimeout,
log: false,
}
transactionTimeout
).then((vestingAmount) => {
if (Number(vestingAmount) != 0) {
cy.contains('Associated', {
timeout: transactionTimeout,
})
cy.contains('Associated', transactionTimeout)
.parent()
.parent()
.within(() => {
cy.getByTestId('currency-value', {
timeout: transactionTimeout,
})
.should('have.length', 1)
cy.getByTestId('currency-value', transactionTimeout)
.first()
.invoke('text')
.as('displayedAmount');
cy.get('@displayedAmount', {
timeout: transactionTimeout,
}).should('not.eq', $associatedAmount);
cy.get('@displayedAmount', transactionTimeout).should(
'not.eq',
$associatedAmount
);
});
}
});
@@ -158,14 +149,14 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
async function vegaWalletTeardownVesting(vestingContract: TokenVesting) {
cy.highlight('Tearing down vesting tokens from vega wallet if present');
cy.wrap(vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey), {
timeout: transactionTimeout,
log: false,
}).then((vestingAmount) => {
cy.wrap(
vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
transactionTimeout
).then((vestingAmount) => {
if (Number(vestingAmount) != 0) {
cy.wrap(
vestingContract.remove_stake(String(vestingAmount), vegaWalletPubKey),
{ timeout: transactionTimeout }
transactionTimeout
);
}
});
+1 -1
View File
@@ -6,7 +6,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https:/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_EXPLORER_URL=#
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
+1 -1
View File
@@ -7,7 +7,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https:/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
@@ -0,0 +1 @@
export * from './multisig-incorrect-notice';
@@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useEnvironment } from '@vegaprotocol/environment';
import { MultisigIncorrectNotice } from './multisig-incorrect-notice';
jest.mock('@vegaprotocol/web3', () => ({
useEthereumConfig: jest.fn(),
}));
jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: jest.fn(),
}));
describe('MultisigIncorrectNotice', () => {
it('renders correctly when config is provided', () => {
(useEthereumConfig as jest.Mock).mockReturnValue({
config: {
multisig_control_contract: {
address: '0x1234',
},
},
});
(useEnvironment as unknown as jest.Mock).mockReturnValue({
ETHERSCAN_URL: 'https://etherscan.io',
});
render(<MultisigIncorrectNotice />);
expect(screen.getByTestId('multisig-contract-link')).toHaveAttribute(
'href',
'https://etherscan.io/address/0x1234'
);
expect(screen.getByTestId('multisig-contract-link')).toHaveAttribute(
'title',
'0x1234'
);
expect(
screen.getByTestId('multisig-validators-learn-more')
).toBeInTheDocument();
});
it('does not render when config is not provided', () => {
(useEthereumConfig as jest.Mock).mockReturnValue({
config: null,
});
const { container } = render(<MultisigIncorrectNotice />);
expect(container.firstChild).toBeNull();
});
});
@@ -0,0 +1,49 @@
import { useTranslation } from 'react-i18next';
import { Callout, Intent, Link } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import type { EthereumConfig } from '@vegaprotocol/web3';
export const MultisigIncorrectNotice = () => {
const { t } = useTranslation();
const { config } = useEthereumConfig();
const { ETHERSCAN_URL } = useEnvironment();
if (!config) {
return null;
}
const contract = config[
'multisig_control_contract' as keyof EthereumConfig
] as {
address: string;
};
return (
<div className="mb-10">
<Callout intent={Intent.Warning}>
<div>
<Link
title={contract.address}
href={`${ETHERSCAN_URL}/address/${contract.address}`}
target="_blank"
data-testid="multisig-contract-link"
>
{t('multisigContractLink')}
</Link>{' '}
{t('multisigContractIncorrect')}
</div>
<div className="mt-2">
<Link
href={DocsLinks?.VALIDATOR_SCORES_REWARDS}
target="_blank"
data-testid="multisig-validators-learn-more"
>
{t('learnMore')}
</Link>
</div>
</Callout>
</div>
);
};
@@ -116,7 +116,7 @@
"Showing tranches with <{{trancheMinimum}} VEGA, click to hide these tranches": "Showing tranches with ≤{{trancheMinimum}} $VEGA, click to hide these tranches",
"Not showing tranches with <{{trancheMinimum}} VEGA, click to show all tranches": "Not showing tranches with ≤{{trancheMinimum}} $VEGA, click to show all tranches",
"the holder": "the holder",
"We couldn't seem to load your data.": "We couldn't seem to load your data.",
"Your data couldn't be loaded": "Your data couldn't be loaded",
"Vesting VEGA": "Vesting VEGA",
"All the tokens in this tranche are locked and can not be redeemed yet.": "All the tokens in this tranche are locked and can not be redeemed yet.",
"Redeem unlocked VEGA from tranche {{id}}": "Redeem unlocked $VEGA from tranche {{id}}",
@@ -728,7 +728,7 @@
"ThisWillSetEnactmentDeadlineTo": "This will set the enactment date to",
"ThisWillSetValidationDeadlineTo": "This will set the validation deadline to",
"Hours": "hours",
"ThisWillAdd2MinutesToAllowTimeToConfirmInWallet": "Note: we add 2 minutes of extra time when you choose the minimum value. This gives you time to confirm the proposal in your wallet.",
"ThisWillAdd2MinutesToAllowTimeToConfirmInWallet": "Note: 2 minutes of extra time are added when you choose the minimum value. This gives you time to confirm the proposal in your wallet.",
"ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline": "Proposal will fail if enactment is earlier than the voting deadline",
"SelectAMarketToChange": "Select a market to change",
"MarketName": "Market name",
@@ -821,5 +821,8 @@
"disclaimer2": "The Vega Governance App is free, public and open source software. Software upgrades may contain bugs or security vulnerabilities that might result in loss of functionality.",
"disclaimer3": "The Vega Governance App uses data obtained from nodes on the Vega Blockchain. The developers of the Vega Governance App do not operate or run the Vega Blockchain or any other blockchain.",
"disclaimer4": "The Vega Governance App is provided “as is”. The developers of the Vega Governance App make no representations or warranties of any kind, whether express or implied, statutory or otherwise regarding the Vega Governance App. They disclaim all warranties of merchantability, quality, fitness for purpose. They disclaim all warranties that the Vega Governance App is free of harmful components or errors.",
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App."
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App.",
"multisigContractLink": "Ethereum Multisig Contract",
"multisigContractIncorrect": "is incorrectly configured. Validator and delegator rewards will be penalised until this is resolved.",
"learnMore": "Learn more"
}
@@ -0,0 +1,70 @@
import {
getMultisigStatusInfo,
MultisigStatus,
} from './get-multisig-status-info';
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch';
const createNode = (id: string, multisigScore: string) => ({
node: {
id,
stakedTotal: '1000',
rewardScore: { multisigScore },
},
});
describe('getMultisigStatus', () => {
it('should return MultisigStatus.noNodes when no nodes are present', () => {
const result = getMultisigStatusInfo({
epoch: { id: '1', validatorsConnection: { edges: [] } },
} as PreviousEpochQuery);
expect(result).toEqual({
multisigStatus: MultisigStatus.noNodes,
showMultisigStatusError: true,
});
});
it('should return MultisigStatus.correct when all nodes have multisigScore of 1', () => {
const result = getMultisigStatusInfo({
epoch: {
id: '1',
validatorsConnection: {
edges: [createNode('1', '1'), createNode('2', '1')],
},
},
} as PreviousEpochQuery);
expect(result).toEqual({
multisigStatus: MultisigStatus.correct,
showMultisigStatusError: false,
});
});
it('should return MultisigStatus.nodeNeedsRemoving when all nodes have multisigScore of 0', () => {
const result = getMultisigStatusInfo({
epoch: {
id: '1',
validatorsConnection: {
edges: [createNode('1', '0'), createNode('2', '0')],
},
},
} as PreviousEpochQuery);
expect(result).toEqual({
multisigStatus: MultisigStatus.nodeNeedsRemoving,
showMultisigStatusError: true,
});
});
it('should return MultisigStatus.nodeNeedsAdding when some nodes have multisigScore of 0 and others have 1', () => {
const result = getMultisigStatusInfo({
epoch: {
id: '1',
validatorsConnection: {
edges: [createNode('1', '0'), createNode('2', '1')],
},
},
} as PreviousEpochQuery);
expect(result).toEqual({
multisigStatus: MultisigStatus.nodeNeedsAdding,
showMultisigStatusError: true,
});
});
});
@@ -0,0 +1,42 @@
import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch';
export enum MultisigStatus {
'correct' = 'correct',
'nodeNeedsAdding' = 'nodeNeedsAdding',
'nodeNeedsRemoving' = 'nodeNeedsRemoving ',
'noNodes' = 'noNodes',
}
export const getMultisigStatusInfo = (
previousEpochData: PreviousEpochQuery
) => {
let status = MultisigStatus.noNodes;
const allNodesInPreviousEpoch = removePaginationWrapper(
previousEpochData?.epoch.validatorsConnection?.edges
);
const hasZero = allNodesInPreviousEpoch.some(
(node) => Number(node?.rewardScore?.multisigScore) === 0
);
const hasOne = allNodesInPreviousEpoch.some(
(node) => Number(node?.rewardScore?.multisigScore) === 1
);
if (hasZero && hasOne) {
// If any individual node has 0 it means that node is missing from the multisig and needs to be added
status = MultisigStatus.nodeNeedsAdding;
} else if (hasZero) {
// If all nodes have 0 it means there is an incorrect address in the multisig that needs to be removed
status = MultisigStatus.nodeNeedsRemoving;
} else if (allNodesInPreviousEpoch.length > 0) {
// If all nodes have 1 it means the multisig is correct
status = MultisigStatus.correct;
}
return {
showMultisigStatusError: status !== MultisigStatus.correct,
multisigStatus: status,
};
};
@@ -146,7 +146,7 @@ describe('Proposal form vote, validation and enactment deadline', () => {
it('should show the correct datetimes', () => {
renderComponent();
// Should be adding 2 mins to the vote deadline as the minimum is set by
// default, and we add 2 mins for wallet confirmation
// default, and 2 mins are added for wallet confirmation
expect(screen.getByTestId('voting-date')).toHaveTextContent(
'2022-01-01T01:02:00.000Z'
);
@@ -23,6 +23,9 @@ import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { DocsLinks } from '@vegaprotocol/environment';
import { ConnectToSeeRewards } from '../connect-to-see-rewards';
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
import { usePreviousEpochQuery } from '../../staking/__generated__/PreviousEpoch';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
type RewardsView = 'total' | 'individual';
@@ -41,12 +44,25 @@ export const RewardsPage = () => {
useRefreshAfterEpoch(epochData?.epoch.timestamps.expiry, refetch);
const { data: previousEpochData } = usePreviousEpochQuery({
variables: {
epochId: (Number(epochData?.epoch.id) - 1).toString(),
},
skip: !epochData?.epoch.id,
});
const multisigStatus = previousEpochData
? getMultisigStatusInfo(previousEpochData)
: undefined;
const {
params,
loading: paramsLoading,
error: paramsError,
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
console.log('params', params);
const payoutDuration = useMemo(() => {
if (!params) {
return 0;
@@ -78,14 +94,18 @@ export const RewardsPage = () => {
)}
</p>
{payoutDuration ? (
{multisigStatus?.showMultisigStatusError ? (
<MultisigIncorrectNotice />
) : null}
{!multisigStatus?.showMultisigStatusError && payoutDuration ? (
<div className="my-8">
<Callout
title={t('rewardsCallout', {
duration: formatDistance(new Date(0), payoutDuration),
})}
headingLevel={3}
intent={Intent.Warning}
intent={Intent.Primary}
>
<p className="mb-0">{t('rewardsCalloutDetail')}</p>
</Callout>
@@ -7,6 +7,8 @@ import { ValidatorTables } from './validator-tables';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ENV } from '../../../config';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
export const EpochData = () => {
// errorPolicy due to vegaprotocol/vega issue 5898
@@ -46,12 +48,20 @@ export const EpochData = () => {
userStakingRefetch();
});
const multisigStatus = previousEpochData
? getMultisigStatusInfo(previousEpochData)
: undefined;
return (
<AsyncRenderer
loading={nodesLoading || userStakingLoading}
error={nodesError || userStakingError}
data={nodesData}
>
{multisigStatus?.showMultisigStatusError ? (
<MultisigIncorrectNotice />
) : null}
{nodesData?.epoch &&
nodesData.epoch.timestamps.start &&
nodesData?.epoch.timestamps.expiry && (
@@ -5,4 +5,4 @@ NX_VEGA_ENV=DEVNET
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_EXPLORER_URL=#
+3
View File
@@ -3,3 +3,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=MAINNET
@@ -0,0 +1,177 @@
// #region consts
const asset = 'asset';
const assetDetailsDialog = 'dialog-content';
const assetRow = 'key-value-table-row';
const contractAddress = '7_value';
const dialogCloseBtn = 'close-asset-details-dialog';
const dialogCloseX = 'dialog-close';
const dialogTitle = 'dialog-title';
const indicesWithLabelTooltips = [4, 5, 6, 7, 8, 9, 11, 12, 13, 14];
const indicesWithValueTooltips = [1, 6];
const labelValueToolTipPairs = [
{
label: 'ID',
value: 'asset-id',
},
{
label: 'Type',
value: 'ERC20',
valueToolTip: 'An asset originated from an Ethereum ERC20 Token',
},
{
label: 'Name',
value: 'Euro',
},
{
label: 'Symbol',
value: 'tEURO',
},
{
label: 'Decimals',
value: '5',
labelTooltip: 'Number of decimal / precision handled by this asset',
},
{
label: 'Quantum',
value: '0.00001',
labelTooltip: 'The minimum economically meaningful amount of the asset',
},
{
label: 'Status',
value: 'Enabled',
labelTooltip: 'The status of the asset in the Vega network',
valueToolTip: 'Asset can be used on the Vega network',
},
{
label: 'Contract address',
value: '0x0158031158Bb4dF2AD02eAA31e8963E84EA978a4 ',
labelTooltip:
'The address of the contract for the token, on the ethereum network',
},
{
label: 'Withdrawal threshold',
value: '0.0005',
labelTooltip:
"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",
},
{
label: 'Lifetime limit',
value: '1,230.00',
labelTooltip:
'The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance',
},
{ label: '', value: '' },
{
label: 'Infrastructure fee account balance',
value: '0.00001',
labelTooltip: 'The infrastructure fee account in this asset',
},
{
label: 'Global reward pool account balance',
value: '0.00002',
labelTooltip: 'The global rewards acquired in this asset',
},
{
label: 'Maker paid fees account balance',
value: '0.00003',
labelTooltip:
'The rewards acquired based on the fees paid to makers in this asset',
},
{
label: 'Maker received fees account balance',
value: '0.00004',
labelTooltip:
'The rewards acquired based on fees received for being a maker on trades',
},
{
label: 'Liquidity provision fee reward account balance',
value: '0.00005',
labelTooltip:
'The rewards acquired based on the liquidity provision fees in this asset',
},
{
label: 'Market proposer reward account balance',
value: '0.00006',
labelTooltip:
'The rewards acquired based on the market proposer reward in this asset',
},
];
//endregion
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
const visitPortfolioAndClickAsset = (assetName: string) => {
cy.visit('/#/portfolio');
cy.getByTestId(asset).contains(assetName).click();
};
const testTooltip = (index: number, testId: string, tooltip: string) => {
cy.getByTestId(`${index}_${testId}`).realHover();
cy.get('[role="tooltip"]').find('div').should('have.text', tooltip);
cy.getByTestId(dialogTitle).click();
};
describe('assets', { tags: '@smoke', testIsolation: true }, () => {
it('asset details', () => {
visitPortfolioAndClickAsset('tBTC');
cy.getByTestId(assetRow).each((element, index) => {
if (index === 10) {
return;
}
const { label, value, labelTooltip, valueToolTip } =
labelValueToolTipPairs[index];
// 6501-ASSE-001
// 6501-ASSE-002
// 6501-ASSE-003
// 6501-ASSE-004
// 6501-ASSE-005
// 6501-ASSE-006
// 6501-ASSE-007
// 6501-ASSE-008
// 6501-ASSE-009
// 6501-ASSE-010
// 6501-ASSE-011
cy.getByTestId(`${index}_label`).should('have.text', label);
cy.getByTestId(`${index}_value`).should('have.text', value);
// 6501-ASSE-012
if (indicesWithLabelTooltips.includes(index)) {
if (labelTooltip) {
testTooltip(index, 'label', labelTooltip);
}
}
if (indicesWithValueTooltips.includes(index)) {
if (valueToolTip) {
testTooltip(index, 'value', valueToolTip);
}
}
});
// 6501-ASSE-013
cy.getByTestId(dialogCloseX).click();
cy.document().then((doc) => {
expect(doc.querySelector(assetDetailsDialog)).to.not.exist;
});
});
it('ERC20 Contract address', () => {
visitPortfolioAndClickAsset('tBTC');
cy.getByTestId(contractAddress).within(() => {
// 6501-ASSE-014
cy.getByTestId('external-link')
.should('have.attr', 'target', '_blank')
.should('have.text', '0x0158031158Bb4dF2AD02eAA31e8963E84EA978a4');
});
// 6501-ASSE-013
cy.getByTestId(dialogCloseBtn).click();
cy.document().then((doc) => {
expect(doc.querySelector(assetDetailsDialog)).to.not.exist;
});
});
});
@@ -114,6 +114,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
});
it('can key to key transfers', function () {
// 1003-TRAN-023
// 1003-TRAN-006
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(collateralTab).click();
@@ -199,7 +201,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.first()
.should('contain.text', 'Operational')
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname)
.next()
.then(($el) => {
const blockHeight = parseInt($el.text());
@@ -1,7 +1,8 @@
const dialogContent = 'dialog-content';
const nodeHealth = 'node-health';
const statusIncidentsLink = 'footer [data-testid=external-link]';
describe.skip('home', { tags: '@regression' }, () => {
describe('home', { tags: '@regression' }, () => {
before(() => {
cy.clearAllLocalStorage();
cy.mockTradingPage();
@@ -12,34 +13,17 @@ describe.skip('home', { tags: '@regression' }, () => {
describe('footer', () => {
it('shows current block height', () => {
// 0006-NETW-004
// 0006-NETW-005
// 0006-NETW-008
// 0006-NETW-009
// 0006-NETW-011
cy.intercept('POST', 'http://localhost:3008/graphql', (req) => {
req.on('response', (res) => {
res.setDelay(3001);
});
});
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Warning delay ( >3 sec)');
cy.intercept('POST', 'http://localhost:3008/graphql', (req) => {
req.on('response', (res) => {
res.setDelay(1);
});
});
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Operational', { timeout: 10000 })
.should('contain.text', 'Operational', {
timeout: 10000,
})
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).origin)
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname)
.next()
.should('contain.text', '100'); // all mocked queries have x-block-height header set to 100
});
@@ -82,12 +66,9 @@ describe.skip('home', { tags: '@regression' }, () => {
.focus()
.type(new URL(Cypress.env('VEGA_URL')).origin + '/graphql');
cy.getByTestId('connect').click();
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Operational');
});
});
describe('Network switcher', () => {
before(() => {
cy.mockTradingPage();
@@ -97,10 +78,21 @@ describe.skip('home', { tags: '@regression' }, () => {
// 0006-NETW-002
// 0006-NETW-003
it('switch to fairground network', () => {
cy.getByTestId('network-switcher').click();
// 0006-NETW-011
it('switch to fairground network and check status & incidents link', () => {
cy.getByTestId('navigation')
.find('[data-testid="network-switcher"]')
.click();
cy.getByTestId('network-item').contains('Fairground testnet').click();
cy.get('[aria-haspopup="menu"]').should('contain.text', 'Fairground');
cy.url().should('include', 'fairground.wtf');
cy.contains('Continue').click();
cy.get(statusIncidentsLink)
.children('span')
.should('have.text', 'Mainnet status & incidents');
cy.get(statusIncidentsLink)
.should('have.attr', 'href')
.and('contain', 'https://blog.vega.xyz/tagged/vega-incident-reports');
});
});
});
@@ -0,0 +1,174 @@
import * as Schema from '@vegaprotocol/types';
const rowSelector =
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
describe('markets all table', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/#/markets/all');
});
});
it('can see table headers', () => {
cy.wait('@Markets');
cy.wait('@MarketsData');
const headers = [
'Market',
'Description',
'Trading mode',
'Status',
'Best bid',
'Best offer',
'Mark price',
'Settlement asset',
'',
];
cy.getByTestId('tab-all-markets').within(($headers) => {
cy.wrap($headers)
.get('.ag-header-cell-text')
.each(($header, i) => {
cy.wrap($header).should('have.text', headers[i]);
});
});
});
it('markets tab should be rendered properly', () => {
cy.get('[data-testid="All markets"]').should(
'have.attr',
'data-state',
'active'
);
cy.get('[data-testid="Proposed markets"]').should(
'have.attr',
'data-state',
'inactive'
);
cy.get('[data-testid="Closed markets"]').should(
'have.attr',
'data-state',
'inactive'
);
});
it('renders markets correctly', () => {
// 6001-MARK-035
cy.get(rowSelector)
.first()
.find('[col-id="tradableInstrument.instrument.code"]')
.should('have.text', 'SOLUSD');
// 6001-MARK-036
cy.get(rowSelector)
.first()
.find('[col-id="tradableInstrument.instrument.name"]')
.should('have.text', 'SUSPENDED MARKET');
// 6001-MARK-037
cy.get(rowSelector)
.first()
.find('[col-id="tradingMode"]')
.should('have.text', 'Continuous');
// 6001-MARK-038
cy.get(rowSelector)
.first()
.find('[col-id="state"]')
.should('have.text', 'Active');
// 6001-MARK-039
cy.get(rowSelector)
.first()
.find('[col-id="data.bestBidPrice"]')
.should('have.text', '0.00');
// 6001-MARK-040
cy.get(rowSelector)
.first()
.find('[col-id="data.bestOfferPrice"]')
.should('have.text', '0.00');
// 6001-MARK-041
cy.get(rowSelector)
.first()
.find('[col-id="data.markPrice"]')
.should('have.text', '84.41');
// 6001-MARK-042
cy.get(rowSelector)
.first()
.find(
'[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"]'
)
.should('have.text', 'XYZalpha');
// 6001-MARK-043
cy.get(rowSelector)
.first()
.find(
'[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"] button'
)
.click();
cy.getByTestId('dialog-title').should('have.text', 'Asset details - tEURO');
cy.getByTestId('close-asset-details-dialog').click();
});
it('can open row actions', () => {
// 6001-MARK-044
cy.get('.ag-pinned-right-cols-container')
.find('[col-id="market-actions"]')
.first()
.find('button')
.click();
// 6001-MARK-045
const dropdownContent = '[data-testid="market-actions-content"]';
const dropdownContentItem = '[role="menuitem"]';
cy.get(dropdownContent)
.find(dropdownContentItem)
.eq(0)
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
.should('have.text', 'Copy Market ID');
// 6001-MARK-046
cy.get(dropdownContent)
.find(dropdownContentItem)
.eq(1)
.find('a')
.then(($el) => {
const href = $el.attr('href');
expect(/\/markets\/market-1/.test(href || '')).to.equal(true);
})
.should('have.text', 'View on Explorer');
// 6001-MARK-047
cy.get(dropdownContent)
.find(dropdownContentItem)
.eq(2)
.should('have.text', 'View asset');
cy.getByTestId('market-actions-content').click();
});
it('able to open and sort full market list - market page', () => {
const ExpectedSortedMarkets = [
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
];
cy.get('[data-testid="All markets"]').click({ force: true });
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
cy.contains('AAPL.MF21').should('be.visible');
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
for (let i = 0; i < ExpectedSortedMarkets.length; i++) {
cy.get(`[row-index=${i}]`)
.find('[col-id="tradableInstrument.instrument.code"]')
.should('have.text', ExpectedSortedMarkets[i]);
}
});
});
@@ -55,12 +55,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
// TODO: fix this test
// New volume check logic, added by https://github.com/vegaprotocol/frontend-monorepo/pull/3870 has caused the
// 24hr volume assertion to fail as it now reads 'Unknown'
it.skip('market volume displayed', () => {
it('market volume displayed', () => {
cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(0, '24 Hour Volume', '1');
validateMarketDataRow(1, 'Open Interest', '-');
validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3');
@@ -40,26 +40,26 @@ describe('markets selector', { tags: '@smoke' }, () => {
{
code: 'SOLUSD',
markPrice: '84.41XYZalpha',
change: '+200.00%',
vol: '324h vol',
change: '',
vol: '0.0024h vol',
},
{
code: 'ETHBTC.QM21',
markPrice: '46,126.90058tBTC',
change: '+200.00%',
vol: '324h vol',
change: '',
vol: '0.0024h vol',
},
{
code: 'BTCUSD.MF21',
markPrice: '46,126.90058tDAI',
change: '+200.00%',
vol: '324h vol',
change: '',
vol: '0.0024h vol',
},
{
code: 'AAPL.MF21',
markPrice: '46,126.90058tUSDC',
change: '+200.00%',
vol: '324h vol',
change: '',
vol: '0.0024h vol',
},
];
cy.getByTestId(list)
@@ -80,7 +80,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
market.change
);
// 6001-MARK-025
expect(item.find('[data-testid="sparkline-svg"]')).to.exist;
expect(item.find('[data-testid="sparkline-svg"]')).to.not.exist;
});
});
@@ -0,0 +1,199 @@
import { checkSorting } from '@vegaprotocol/cypress';
const rowSelector =
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
describe('markets proposed table', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/all');
cy.get('[data-testid="Proposed markets"]').click();
});
});
it('can see table headers', () => {
const headers = [
'Market',
'Description',
'Settlement asset',
'State',
'Voting',
'Closing date',
'Enactment date',
'',
];
cy.getByTestId('tab-proposed-markets').within(($headers) => {
cy.wrap($headers)
.get('.ag-header-cell-text')
.each(($header, i) => {
cy.wrap($header).should('have.text', headers[i]);
});
});
});
it('renders markets correctly', () => {
// 6001-MARK-049
cy.get(rowSelector)
.first()
.find('[col-id="market"]')
.should('have.text', 'ETHUSD');
// 6001-MARK-050
cy.get(rowSelector)
.first()
.find('[col-id="description"]')
.should('have.text', 'ETHUSD');
// 6001-MARK-051
cy.get(rowSelector)
.first()
.find('[col-id="asset"]')
.should('have.text', 'tDAI TEST');
// 6001-MARK-052
// 6001-MARK-053
cy.get(rowSelector)
.first()
.find('[col-id="state"]')
.should('have.text', 'Open');
// 6001-MARK-054
cy.get(rowSelector)
.first()
.find('[col-id="voting"]')
.should('have.text', '');
// 6001-MARK-056
cy.get(rowSelector)
.first()
.find('[col-id="closing-date"]')
.should('not.be.empty');
// 6001-MARK-057
cy.get(rowSelector)
.first()
.find('[col-id="enactment-date"]')
.should('not.be.empty');
});
it('can open row actions', () => {
// 6001-MARK-058
cy.get('.ag-pinned-right-cols-container')
.find('[col-id="proposal-actions"]')
.first()
.find('button')
.click();
const dropdownContent = '[data-testid="market-actions-content"]';
const dropdownContentItem = '[role="menuitem"]';
// 6001-MARK-059
cy.get(dropdownContent)
.find(dropdownContentItem)
.eq(0)
.find('a')
.should('have.text', 'View proposal')
.and(
'have.attr',
'href',
`${Cypress.env(
'VEGA_TOKEN_URL'
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
);
cy.getByTestId('market-actions-content').click();
});
// 6001-MARK-060
it('can see proposed market link', () => {
cy.getByTestId('tab-proposed-markets')
.find('[data-testid="external-link"]')
.should('have.length', 11)
.last()
.should('have.text', 'Propose a new market')
.and(
'have.attr',
'href',
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('proposed markets tab should be sorted properly', () => {
cy.get('[data-testid="Proposed markets"]').click({ force: true });
const marketColDefault = [
'ETHUSD',
'LINKUSD',
'ETHUSD',
'ETHDAI.MF21',
'AAPL.MF21',
'BTCUSD.MF21',
'TSLA.QM21',
'AAVEDAI.MF21',
'ETHBTC.QM21',
'UNIDAI.MF21',
];
const marketColAsc = [
'AAPL.MF21',
'AAVEDAI.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'ETHDAI.MF21',
'ETHUSD',
'ETHUSD',
'LINKUSD',
'TSLA.QM21',
'UNIDAI.MF21',
];
const marketColDesc = [
'UNIDAI.MF21',
'TSLA.QM21',
'LINKUSD',
'ETHUSD',
'ETHUSD',
'ETHDAI.MF21',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'AAPL.MF21',
];
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
const stateColDefault = [
'Open',
'Passed',
'Waiting for Node Vote',
'Open',
'Passed',
'Open',
'Passed',
'Open',
'Waiting for Node Vote',
'Open',
];
const stateColAsc = [
'Open',
'Open',
'Open',
'Open',
'Open',
'Passed',
'Passed',
'Passed',
'Waiting for Node Vote',
'Waiting for Node Vote',
];
const stateColDesc = [
'Waiting for Node Vote',
'Waiting for Node Vote',
'Passed',
'Passed',
'Passed',
'Open',
'Open',
'Open',
'Open',
'Open',
];
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
});
+2 -142
View File
@@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -16,147 +16,7 @@ describe('markets table', { tags: '@smoke' }, () => {
});
});
it('renders markets correctly', () => {
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
});
it('able to open and sort full market list - market page', () => {
const ExpectedSortedMarkets = [
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
];
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
cy.contains('AAPL.MF21').should('be.visible');
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
for (let i = 0; i < ExpectedSortedMarkets.length; i++) {
cy.get(`[row-index=${i}]`)
.find('[col-id="tradableInstrument.instrument.code"]')
.should('have.text', ExpectedSortedMarkets[i]);
}
});
it('proposed markets tab should be rendered properly', () => {
cy.get('[data-testid="All markets"]').should(
'have.attr',
'data-state',
'active'
);
cy.get('[data-testid="Proposed markets"]').should(
'have.attr',
'data-state',
'inactive'
);
cy.get('[data-testid="Proposed markets"]').click();
cy.get('[data-testid="Proposed markets"]').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-proposed-markets').should('be.visible');
cy.get('.ag-body-viewport .ag-center-cols-container .ag-row').should(
'have.length',
10
);
cy.getByTestId('tab-proposed-markets')
.find('[data-testid="external-link"]')
.should('have.length', 11)
.last()
.should('have.text', 'Propose a new market')
.and(
'have.attr',
'href',
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
);
});
it('proposed markets tab should be sorted properly', () => {
cy.get('[data-testid="Proposed markets"]').click();
const marketColDefault = [
'ETHUSD',
'LINKUSD',
'ETHUSD',
'ETHDAI.MF21',
'AAPL.MF21',
'BTCUSD.MF21',
'TSLA.QM21',
'AAVEDAI.MF21',
'ETHBTC.QM21',
'UNIDAI.MF21',
];
const marketColAsc = [
'AAPL.MF21',
'AAVEDAI.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'ETHDAI.MF21',
'ETHUSD',
'ETHUSD',
'LINKUSD',
'TSLA.QM21',
'UNIDAI.MF21',
];
const marketColDesc = [
'UNIDAI.MF21',
'TSLA.QM21',
'LINKUSD',
'ETHUSD',
'ETHUSD',
'ETHDAI.MF21',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'AAPL.MF21',
];
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
const stateColDefault = [
'Open',
'Passed',
'Waiting for Node Vote',
'Open',
'Passed',
'Open',
'Passed',
'Open',
'Waiting for Node Vote',
'Open',
];
const stateColAsc = [
'Open',
'Open',
'Open',
'Open',
'Open',
'Passed',
'Passed',
'Passed',
'Waiting for Node Vote',
'Waiting for Node Vote',
];
const stateColDesc = [
'Waiting for Node Vote',
'Waiting for Node Vote',
'Passed',
'Passed',
'Passed',
'Open',
'Open',
'Open',
'Open',
'Open',
];
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
it.skip('opening auction subsets should be properly displayed', () => {
it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
@@ -19,6 +19,7 @@ describe('accounts', { tags: '@smoke' }, () => {
// 7001-COLL-005
// 7001-COLL-006
// 7001-COLL-007
// 1003-TRAN-001
const tradingAccountRowId = '[row-id="t-0"]';
cy.getByTestId('Collateral').click();
@@ -62,6 +63,7 @@ describe('accounts', { tags: '@smoke' }, () => {
.click();
cy.getByTestId('deposit').should('be.visible');
cy.getByTestId('withdraw').should('be.visible');
cy.getByTestId('transfer').should('be.visible');
cy.getByTestId('breakdown').should('be.visible');
cy.getByTestId('Collateral').click({ force: true });
});
@@ -122,25 +124,6 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId(dialogClose).click();
});
it('sorting usage breakdown columns should work well', () => {
// 7001-COLL-010
cy.getByTestId('breakdown').contains('1.01').click();
cy.getByTestId('usage-breakdown')
.find('[col-id="type"]')
.eq(1)
.should('have.text', 'Margin');
cy.getByTestId('usage-breakdown')
.find('[col-id="type"]')
.eq(2)
.should('have.text', 'Margin');
cy.getByTestId('usage-breakdown')
.find('[col-id="type"]')
.eq(3)
.should('have.text', 'General');
cy.getByTestId(dialogClose).click();
});
describe('sorting by ag-grid columns should work well', () => {
before(() => {
const dialogs = Cypress.$('[data-testid="dialog-close"]:visible');
@@ -88,7 +88,8 @@ describe(
.pop()
?.toLowerCase()} and not accepting orders`
);
cy.getByTestId('place-order').should('be.disabled');
// 7002-SORD-060
cy.getByTestId('place-order').should('be.enabled');
});
});
});
@@ -77,7 +77,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId(orderSizeField).clear().type('1.234');
cy.getByTestId(placeOrderBtn).should('be.disabled');
// 7002-SORD-060
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size must be whole numbers for this market'
@@ -86,12 +87,13 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
it('must warn if order size is set to 0', function () {
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId('dealticket-error-message-size-market').should(
'have.text',
'Size cannot be lower than 1'
);
});
it('must have total margin available', () => {
// 7001-COLL-011
cy.getByTestId('tab-ticket')
@@ -100,9 +102,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
.within(() => {
cy.get('[data-state="closed"]').should(
'have.text',
'Total margin available'
'Total margin available100,000.01 tDAI'
);
cy.get('.text-neutral-500').should('have.text', '100,000.01 tDAI');
});
});
});
@@ -23,13 +23,14 @@ describe(
});
it('should show an error if your balance is zero', () => {
cy.getByTestId('place-order').should('be.disabled');
// 7002-SORD-060
cy.getByTestId('place-order').should('be.enabled');
// 7002-SORD-003
cy.getByTestId('dealticket-error-message-zero-balance').should(
'have.text',
'You need ' +
'tDAI' +
' in your wallet to trade in this market.See all your collateral.Make a deposit'
' in your wallet to trade in this market. See all your collateral.Make a deposit'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
});
@@ -34,9 +34,9 @@ describe('suspended market validation', { tags: '@regression' }, () => {
it('should show warning for market order', function () {
cy.getByTestId(toggleMarket).click();
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
// 7002-SORD-060
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId('dealticket-error-message-type').should(
'have.text',
'This market is in auction until it reaches sufficient liquidity. Only limit orders are permitted when market is in auction'
@@ -59,7 +59,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
cy.getByTestId(orderTIFDropDown).select(
TIFlist.filter((item) => item.code === 'FOK')[0].value
);
cy.getByTestId(placeOrderBtn).should('be.disabled');
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId('dealticket-error-message-tif').should(
'have.text',
'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
@@ -98,23 +98,14 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
},
},
];
const marketData = marketsDataQuery();
const edges = marketData.marketsConnection?.edges.map((market) => {
const replace =
market.node.data?.market.id === 'market-2' ? null : market.node.data;
return { ...market, node: { ...market.node, data: replace } };
});
const overrides = {
...marketData,
marketsConnection: { ...marketData.marketsConnection, edges },
marketsConnection: { edges: [] },
};
cy.mockGQL((req) => {
aliasGQLQuery(req, 'MarketsData', overrides, errors);
});
cy.visit('/#/markets/market-0');
cy.get('.pointer-events-none.absolute.inset-0').contains(
'Something went wrong:'
);
cy.get('[data-testid="tab-positions"]').contains('no market data');
});
});
@@ -1,95 +1,141 @@
import { selectAsset } from '../support/helpers';
const formFieldError = 'input-error-text';
const toAddressField = '[name="toAddress"]';
// #region consts
const amountField = 'input[name="amount"]';
const submitTransferBtn = '[type="submit"]';
const transferForm = 'transfer-form';
const errorText = 'input-error-text';
const openTransferDialog = 'open-transfer-dialog';
const amountShortName = 'input[name="amount"] + div + span.text-xs';
const assetSelection = 'select-asset';
const assetBalance = 'asset-balance';
const assetOption = 'rich-select-option';
const closeDialog = 'dialog-close';
const dialogTitle = 'dialog-title';
const dialogTransferText = 'dialog-transfer-text';
const dropdownMenu = 'dropdown-menu';
const errorText = 'input-error-text';
const formFieldError = 'input-error-text';
const includeTransferFeeRadioBtn = 'include-transfer-fee';
const keyID = '[data-testid="dialog-transfer-text"] > .rounded-md';
const manageVegaWallet = 'manage-vega-wallet';
const openTransferDialog = 'open-transfer-dialog';
const submitTransferBtn = '[type="submit"]';
const toAddressField = '[name="toAddress"]';
const totalTransferfee = 'total-transfer-fee';
const transfer = 'transfer';
const transferAmount = 'transfer-amount';
const transferForm = 'transfer-form';
const transferFee = 'transfer-fee';
const walletTransfer = 'wallet-transfer';
const ASSET_SEPOLIA_TBTC = 2;
const ASSET_EURO = 1;
const ASSET_SEPOLIA_TBTC = 2;
const toastContent = 'toast-content';
const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close';
const toastContent = 'toast-content';
// #endregion
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.getByTestId('Trading').first().click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(dropdownMenu).first().click();
cy.getByTestId(transfer).click();
cy.wait('@Accounts');
cy.wait('@Assets');
cy.mockVegaWalletTransaction();
});
it('transfer fees tooltips', () => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
// 1003-TRAN-018
// 1003-TRAN-019
cy.getByTestId(transferForm);
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 });
/// Check Include Transfer Fee tooltip
cy.get('label[for="include-transfer-fee"] div').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
});
it('transfer fees', () => {
// 1003-TRAN-020
// 1003-TRAN-021
// 1003-TRAN-022
// 1003-TRAN-023
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '1.00');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.01');
cy.getByTestId(includeTransferFeeRadioBtn).click();
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '0.99');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.00');
});
});
describe(
'transfer form validation and transfer from options',
{ tags: '@smoke' },
() => {
before(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).click();
cy.wait('@Accounts');
cy.wait('@Assets');
});
it('empty fields', () => {
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
// only 2 despite 3 fields because the ethereum address will be auto populated
cy.getByTestId(formFieldError).should('have.length', 3);
});
it('min amount', () => {
// 1002-WITH-010
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', () => {
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'
);
});
it('can start transfer from vega wallet', () => {
cy.getByTestId(closeDialog).click();
cy.getByTestId('manage-vega-wallet').click();
cy.getByTestId('wallet-transfer').should('have.text', 'Transfer').click();
cy.getByTestId(dialogTransferText).should(
'contain.text',
'Transfer funds to another Vega key from 02ecea…342f65 If you are at all unsure, stop and seek advice.'
);
});
it('can start transfer from trading collateral table', () => {
cy.getByTestId(closeDialog).click();
cy.getByTestId('Trading').first().click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).should('not.exist');
cy.getByTestId('Portfolio').eq(0).click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).click();
cy.getByTestId(dialogTransferText).should(
'contain.text',
'Transfer funds to another Vega key from 02ecea…342f65 If you are at all unsure, stop and seek advice.'
);
});
}
);
describe(
'withdraw actions',
'transfer form validation',
{ tags: '@regression', testIsolation: true },
() => {
beforeEach(() => {
@@ -99,43 +145,141 @@ describe(
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferDialog).click();
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.wait('@Accounts');
cy.wait('@Assets');
cy.mockVegaWalletTransaction();
});
it('key to key transfers by select key', function () {
cy.getByTestId(transferForm).should('be.visible');
cy.getByTestId(transferForm).find(toAddressField).select(1);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm).find(amountField).type('1', { delay: 100 });
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(toastContent).should(
'contain.text',
'Awaiting confirmation'
);
cy.getByTestId(toastCloseBtn).click();
it('transfer Text', () => {
// 1003-TRAN-003
cy.getByTestId(dialogTransferText)
.should('exist')
.get(keyID)
.invoke('text')
.should('match', /[\w.]{6}…[\w.]{6}/);
});
it('key to key transfers by enter manual key', function () {
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(
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm).find(amountField).type('1', { delay: 100 });
cy.getByTestId(transferForm).find(toAddressField).type('asd');
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(toastContent).should(
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',
'Awaiting confirmation'
'Value is below minimum'
);
cy.getByTestId(toastCloseBtn).click();
});
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'
);
cy.getByTestId(closeDialog).click();
});
}
);
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(openTransferDialog).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 -1
View File
@@ -9,7 +9,7 @@ NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_DOCS_URL=#
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
@@ -11,14 +11,12 @@ import {
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { updateGridData } from '@vegaprotocol/datagrid';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useDataProvider } from '@vegaprotocol/data-provider';
import {
AsyncRenderer,
Tab,
Tabs,
Link as UiToolkitLink,
@@ -26,14 +24,13 @@ import {
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity';
import type { Filter } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
@@ -74,21 +71,12 @@ export const LiquidityContainer = ({
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const update = useCallback(
({ data }: { data: LiquidityProvisionData[] | null }) => {
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);
const { data, loading, error } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
update,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
@@ -103,36 +91,16 @@ export const LiquidityContainer = ({
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
const getRows = useCallback(
async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
},
[]
);
return (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
datasource={{ getRows }}
rowModelType="infinite"
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No liquidity provisions')}
noDataCondition={(data) => !data?.length}
/>
</div>
</div>
);
};
@@ -6,25 +6,32 @@ import { MemoryRouter } from 'react-router-dom';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type {
MarketCandlesQuery,
MarketCandlesQueryVariables,
MarketDataUpdateFieldsFragment,
MarketDataUpdateSubscription,
} from '@vegaprotocol/markets';
import { MarketCandlesDocument } from '@vegaprotocol/markets';
import { MarketDataUpdateDocument } from '@vegaprotocol/markets';
import {
AuctionTrigger,
Interval,
MarketState,
MarketTradingMode,
} from '@vegaprotocol/types';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { subDays } from 'date-fns';
describe('MarketSelectorItem', () => {
const yesterday = new Date();
yesterday.setHours(yesterday.getHours() - 20);
const market = createMarketFragment({
id: 'market-0',
decimalPlaces: 2,
// @ts-ignore fragment doesn't contain candles
candles: [
{ close: '5', volume: '50' },
{ close: '10', volume: '50' },
{ close: '5', volume: '50', periodStart: yesterday.toISOString() },
{ close: '10', volume: '50', periodStart: yesterday.toISOString() },
],
tradableInstrument: {
instrument: {
@@ -36,6 +43,7 @@ describe('MarketSelectorItem', () => {
},
},
});
const marketData: MarketDataUpdateFieldsFragment = {
__typename: 'ObservableMarketData',
marketId: market.id,
@@ -63,26 +71,32 @@ describe('MarketSelectorItem', () => {
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
priceMonitoringBounds: null,
};
const mock: MockedResponse<MarketDataUpdateSubscription> = {
request: {
query: MarketDataUpdateDocument,
variables: {
marketId: market.id,
},
const candles = [
{
open: '5',
close: '5',
high: '5',
low: '5',
volume: '50',
periodStart: yesterday.toISOString(),
},
result: {
data: {
marketsData: [marketData],
},
{
open: '10',
close: '10',
high: '10',
low: '10',
volume: '50',
periodStart: yesterday.toISOString(),
},
};
];
const mockOnSelect = jest.fn();
const renderJsx = () => {
const renderJsx = (mocks: MockedResponse[]) => {
return render(
<MemoryRouter>
<MockedProvider mocks={[mock]}>
<MockedProvider mocks={mocks}>
<MarketSelectorItem
market={market}
currentMarketId={market.id}
@@ -94,11 +108,66 @@ describe('MarketSelectorItem', () => {
);
};
let dateSpy: jest.SpyInstance;
const ts = 1685577600000; // 2023-06-01
beforeAll(() => {
dateSpy = jest.spyOn(Date, 'now').mockImplementation(() => ts);
});
afterAll(() => {
dateSpy.mockRestore();
});
it('renders market information', async () => {
const symbol =
market.tradableInstrument.instrument.product.settlementAsset.symbol;
renderJsx();
const mock: MockedResponse<MarketDataUpdateSubscription> = {
request: {
query: MarketDataUpdateDocument,
variables: {
marketId: market.id,
},
},
result: {
data: {
marketsData: [marketData],
},
},
};
const since = subDays(Date.now(), 5).toISOString();
const variables: MarketCandlesQueryVariables = {
marketId: market.id,
interval: Interval.INTERVAL_I1H,
since,
};
const mockCandles: MockedResponse<MarketCandlesQuery> = {
request: {
query: MarketCandlesDocument,
variables,
},
result: {
data: {
marketsConnection: {
edges: [
{
node: {
candlesConnection: {
edges: candles.map((c) => ({
node: c,
})),
},
},
},
],
},
},
},
};
renderJsx([mock, mockCandles]);
const link = screen.getByRole('link');
// link renders and is styled
@@ -106,18 +175,17 @@ describe('MarketSelectorItem', () => {
expect(link).toHaveClass('ring-1');
expect(screen.getByTitle('24h vol')).toHaveTextContent('100');
expect(screen.getByTitle('24h vol')).toHaveTextContent('0.00');
expect(screen.getByTitle(symbol)).toHaveTextContent('-');
// candles are loaded immediately
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
'+100.00%'
);
await waitFor(() => {
expect(screen.getByTitle('24h vol')).toHaveTextContent('100');
expect(screen.getByTitle(symbol)).toHaveTextContent(
addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
);
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
'+100.00%'
);
});
await userEvent.click(link);
@@ -1,4 +1,4 @@
import type { CSSProperties } from 'react';
import type { CSSProperties, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import {
@@ -8,6 +8,7 @@ import {
} from '@vegaprotocol/utils';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import { calcCandleVolume } from '@vegaprotocol/markets';
import { useCandles } from '@vegaprotocol/markets';
import { useMarketDataUpdateSubscription } from '@vegaprotocol/markets';
import { Sparkline } from '@vegaprotocol/ui-toolkit';
import {
@@ -80,8 +81,9 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
: '';
const instrument = market.tradableInstrument.instrument;
const { oneDayCandles } = useCandles({ marketId: market.id });
const vol = market.candles ? calcCandleVolume(market.candles) : '0';
const vol = oneDayCandles ? calcCandleVolume(oneDayCandles) : '0';
const volume =
vol && vol !== '0'
? addDecimalsFormatNumber(vol, market.positionDecimalPlaces)
@@ -111,20 +113,20 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
value={price}
label={instrument.product.settlementAsset.symbol}
/>
<div className="relative">
{market.candles && (
<PriceChange candles={market.candles.map((c) => c.close)} />
<div className="relative text-xs p-1">
{oneDayCandles && (
<PriceChange candles={oneDayCandles.map((c) => c.close)} />
)}
<div
// absolute so height is not larger than price change value
className="absolute right-0 bottom-0 w-[120px]"
>
{market.candles && (
{oneDayCandles && (
<Sparkline
width={120}
height={20}
data={market.candles.filter(Boolean).map((c) => Number(c.close))}
data={oneDayCandles.map((c) => Number(c.close))}
/>
)}
</div>
@@ -133,7 +135,13 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
);
};
const DataRow = ({ value, label }: { value: string; label: string }) => {
const DataRow = ({
value,
label,
}: {
value: string | ReactNode;
label: string;
}) => {
return (
<div
className="text-ellipsis whitespace-nowrap overflow-hidden leading-tight"
@@ -296,7 +296,7 @@ const MainGrid = memo(
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
preferredSize={sizesMiddle[2] || 430}
preferredSize={sizesMiddle[2] || 300}
minSize={200}
>
<TradeGridChild>
@@ -53,7 +53,7 @@ export const useMarketSelectorList = ({
});
if (sort === Sort.None) {
// Sort by market state primarilly and AtoZ secondarilly
// Sort by market state primarily and AtoZ secondarily
return orderBy(
markets,
[
+11 -14
View File
@@ -27,7 +27,6 @@ import type { ColDef } from 'ag-grid-community';
import { SettlementDateCell } from './settlement-date-cell';
import { SettlementPriceCell } from './settlement-price-cell';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
type SettlementAsset =
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
@@ -55,7 +54,6 @@ export const Closed = () => {
const { pubKey } = useVegaWallet();
const {
data: marketData,
loading,
error,
reload,
} = useDataProvider({
@@ -117,21 +115,20 @@ export const Closed = () => {
});
return (
<div className="h-full relative">
<ClosedMarketsDataGrid rowData={rowData} />
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={marketData}
noDataMessage={t('No markets')}
reload={reload}
/>
</div>
<ClosedMarketsDataGrid rowData={rowData} error={error} reload={reload} />
</div>
);
};
const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
const ClosedMarketsDataGrid = ({
rowData,
error,
reload,
}: {
rowData: Row[];
error: Error | undefined;
reload: () => void;
}) => {
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const colDefs = useMemo(() => {
const cols: ColDef[] = [
@@ -315,7 +312,7 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
resizable: true,
minWidth: 100,
}}
overlayNoRowsTemplate="No data"
overlayNoRowsTemplate={error ? error.message : t('No markets')}
storeKey="closedMarkets"
/>
);
@@ -1,4 +1,4 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
@@ -11,7 +11,7 @@ import type { AgGridReact } from 'ag-grid-react';
export const DepositsContainer = () => {
const gridRef = useRef<AgGridReact | null>(null);
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@@ -23,21 +23,10 @@ export const DepositsContainer = () => {
<div className="h-full relative">
<DepositsTable
rowData={data || []}
suppressLoadingOverlay
suppressNoRowsOverlay
ref={gridRef}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No deposits')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
@@ -1,4 +1,4 @@
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import {
withdrawalProvider,
useWithdrawalDialog,
@@ -11,7 +11,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error, reload } = useDataProvider({
const { data, error } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@@ -24,19 +24,8 @@ export const WithdrawalsContainer = () => {
<WithdrawalsTable
data-testid="withdrawals-history"
rowData={data}
suppressLoadingOverlay
suppressNoRowsOverlay
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
loading={loading}
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
</div>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
@@ -1,11 +1,11 @@
import { renderHook, act, waitFor } from '@testing-library/react';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
const mockSetValue = jest.fn();
const mockRemoveValue = jest.fn();
jest.mock('@vegaprotocol/utils');
jest.mock('@vegaprotocol/logger');
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useLocalStorage: jest
@@ -1,6 +1,6 @@
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { useCallback } from 'react';
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
import { ENV } from '../config';
export const STORAGE_KEY = 'vega_telemetry_approval';
+1 -1
View File
@@ -1,5 +1,5 @@
import { ENV } from './lib/config/env';
import { LocalStorage, SentryInit } from '@vegaprotocol/utils';
import { LocalStorage, SentryInit } from '@vegaprotocol/logger';
import { STORAGE_KEY } from './lib/hooks/use-telemetry-approval';
const { dsn, envName } = ENV;
+1
View File
@@ -3,6 +3,7 @@ server {
listen 80;
location / {
add_header 'Cache-Control' 'max-age=60';
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
+28 -22
View File
@@ -1,11 +1,12 @@
import { assetsProvider } from '@vegaprotocol/assets';
import { marketsProvider } from '@vegaprotocol/markets';
import { assetsMapProvider } from '@vegaprotocol/assets';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { marketsMapProvider } from '@vegaprotocol/markets';
import {
makeDataProvider,
makeDerivedDataProvider,
} from '@vegaprotocol/data-provider';
import * as Schema from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/markets';
import produce from 'immer';
import {
@@ -20,7 +21,6 @@ import type {
AccountEventsSubscription,
AccountsQueryVariables,
} from './__generated__/Accounts';
import type { Market } from '@vegaprotocol/markets';
import type { Asset } from '@vegaprotocol/assets';
const AccountType = Schema.AccountType;
@@ -45,9 +45,9 @@ export const getId = (
? `${account.type}-${account.asset.id}-${account.market?.id || 'null'}`
: `${account.type}-${account.assetId}-${account.marketId || 'null'}`;
export type Account = Omit<AccountFieldsFragment, 'market' | 'asset'> & {
market?: Market | null;
export type Account = Omit<AccountFieldsFragment, 'asset' | 'market'> & {
asset: Asset;
market?: Market | null;
};
const update = (
@@ -170,31 +170,22 @@ export const accountsDataProvider = makeDerivedDataProvider<
>(
[
accountsOnlyDataProvider,
(callback, client) => marketsProvider(callback, client, undefined),
(callback, client) => assetsProvider(callback, client, undefined),
(callback, client) => marketsMapProvider(callback, client, undefined),
(callback, client) => assetsMapProvider(callback, client, undefined),
],
([accounts, markets, assets]): Account[] | null => {
return accounts
? accounts
.map((account: AccountFieldsFragment) => {
const market = markets.find(
(market: Market) => market.id === account.market?.id
);
const asset = assets.find(
(asset: Asset) => asset.id === account.asset?.id
);
const asset = (assets as Record<string, Asset>)[account.asset.id];
const market =
account.market?.id &&
(markets as Record<string, Asset>)[account.market?.id];
if (asset) {
return {
...account,
partyId: account.party?.id,
asset: {
...asset,
},
market: market
? {
...market,
}
: null,
asset,
market,
};
}
return null;
@@ -212,3 +203,18 @@ export const aggregatedAccountsDataProvider = makeDerivedDataProvider<
[accountsDataProvider],
(parts) => parts[0] && getAccountData(parts[0] as Account[])
);
export const aggregatedAccountDataProvider = makeDerivedDataProvider<
AccountFields,
never,
AccountsQueryVariables & { assetId: string }
>(
[
(callback, client, { partyId }) =>
aggregatedAccountsDataProvider(callback, client, { partyId }),
],
(parts, { assetId }) =>
(parts[0] as AccountFields[]).find(
(account) => account.asset.id === assetId
) || null
);
@@ -97,7 +97,7 @@ describe('AccountManager', () => {
});
});
it('splash loading should be displayed', async () => {
it('loading should be displayed', async () => {
mockedUseDataProvider.mockImplementation((args) => {
return {
loading: true,
@@ -113,15 +113,6 @@ describe('AccountManager', () => {
/>
);
});
await waitFor(() => {
expect(
screen.getByText(
(content, element) =>
Boolean(
element?.className.endsWith('flex items-center justify-center')
) && content.startsWith('Loading')
)
).toBeInTheDocument();
});
expect(await screen.findByText('Loading...')).toBeInTheDocument();
});
});
+91 -19
View File
@@ -1,12 +1,51 @@
import { useRef, useMemo, memo } from 'react';
import { useRef, memo, useCallback, useState } from 'react';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
import type { AccountFields } from './accounts-data-provider';
import {
aggregatedAccountsDataProvider,
aggregatedAccountDataProvider,
} from './accounts-data-provider';
import type { PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
import isEqual from 'lodash/isEqual';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import BreakdownTable from './breakdown-table';
const AccountBreakdown = ({
assetId,
partyId,
}: {
assetId: string;
partyId: string;
}) => {
const { data } = useDataProvider({
dataProvider: aggregatedAccountDataProvider,
variables: { partyId, assetId },
});
return (
<div
className="h-[35vh] w-full m-auto flex flex-col"
data-testid="usage-breakdown"
>
<h1 className="text-xl mb-4">
{data?.asset?.symbol} {t('usage breakdown')}
</h1>
{data && (
<p className="mb-2 text-sm">
{t('You have %s %s in total.', [
addDecimalsFormatNumber(data.total, data.asset.decimals),
data.asset.symbol,
])}
</p>
)}
<BreakdownTable data={data?.breakdown || null} domLayout="autoHeight" />
</div>
);
};
interface AccountManagerProps {
partyId: string;
@@ -30,10 +69,40 @@ export const AccountManager = ({
storeKey,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const { data, loading, error, reload } = useDataProvider({
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
if (!data || !gridRef.current?.api) {
return false;
}
const pinnedAssetRowData =
pinnedAsset && data.find((d) => d.asset.id === pinnedAsset.id);
if (pinnedAssetRowData) {
const pinnedTopRow = gridRef.current.api.getPinnedTopRow(0);
if (
pinnedTopRow?.data?.balance === '0' &&
pinnedAssetRowData.balance !== '0'
) {
return false;
}
if (!isEqual(pinnedTopRow?.data, pinnedAssetRowData)) {
gridRef.current.api.setPinnedTopRowData([pinnedAssetRowData]);
}
}
gridRef.current.api.setRowData(
pinnedAssetRowData
? data?.filter((d) => d !== pinnedAssetRowData)
: data
);
return true;
},
[gridRef, pinnedAsset]
);
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables,
variables: { partyId },
update,
});
const bottomPlaceholderProps = useBottomPlaceholder({
gridRef,
@@ -44,27 +113,30 @@ export const AccountManager = ({
<div className="relative h-full">
<AccountTable
ref={gridRef}
rowData={error ? [] : data}
rowData={data}
onClickAsset={onClickAsset}
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
onClickBreakdown={setBreakdownAssetId}
isReadOnly={isReadOnly}
suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
data={data}
noDataCondition={(data) => !(data && data.length)}
error={error}
loading={loading}
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
reload={reload}
/>
</div>
<Dialog
size="medium"
open={Boolean(breakdownAssetId)}
onChange={(isOpen) => {
if (!isOpen) {
setBreakdownAssetId(undefined);
}
}}
>
{breakdownAssetId && (
<AccountBreakdown assetId={breakdownAssetId} partyId={partyId} />
)}
</Dialog>
</div>
);
};
@@ -10,13 +10,6 @@ const singleRow = {
balance: '125600000',
market: {
__typename: 'Market',
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
},
},
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
},
asset: {
@@ -136,13 +129,6 @@ describe('AccountsTable', () => {
market: {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
},
},
},
type: 'ACCOUNT_TYPE_MARGIN',
used: '125600000',
+193 -221
View File
@@ -1,4 +1,4 @@
import { forwardRef, useCallback, useMemo, useState } from 'react';
import { forwardRef, useMemo, useCallback } from 'react';
import {
addDecimalsFormatNumber,
isNumeric,
@@ -11,28 +11,25 @@ import type {
} from '@vegaprotocol/datagrid';
import { COL_DEFS } from '@vegaprotocol/datagrid';
import {
Button,
ButtonLink,
Dialog,
Button,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import {
AgGridLazy as AgGrid,
CenteredGridCellWrapper,
} from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { AgGridColumn } from 'ag-grid-react';
import type {
IDatasource,
IGetRowsParams,
RowHeightParams,
RowNode,
RowHeightParams,
} from 'ag-grid-community';
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
import BreakdownTable from './breakdown-table';
import type { AccountFields } from './accounts-data-provider';
import type { Asset } from '@vegaprotocol/types';
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
import BigNumber from 'bignumber.js';
import classNames from 'classnames';
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
@@ -96,34 +93,42 @@ export interface AccountTableProps extends AgGridReactProps {
onClickAsset: (assetId: string) => void;
onClickWithdraw?: (assetId: string) => void;
onClickDeposit?: (assetId: string) => void;
onClickBreakdown?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
storeKey?: string;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
const [openBreakdown, setOpenBreakdown] = useState(false);
const [row, setRow] = useState<AccountFields>();
const pinnedAssetId = props.pinnedAsset?.id;
(
{
onClickAsset,
onClickWithdraw,
onClickDeposit,
onClickBreakdown,
rowData,
...props
},
ref
) => {
const pinnedAsset = useMemo(() => {
const currentPinnedAssetRow = props.rowData?.find(
(row) => row.asset.id === pinnedAssetId
if (!props.pinnedAsset) {
return;
}
const currentPinnedAssetRow = rowData?.find(
(row) => row.asset.id === props.pinnedAsset?.id
);
if (!currentPinnedAssetRow) {
if (props.pinnedAsset) {
return {
asset: props.pinnedAsset,
available: '0',
used: '0',
total: '0',
balance: '0',
};
}
return {
asset: props.pinnedAsset,
available: '0',
used: '0',
total: '0',
balance: '0',
};
}
return currentPinnedAssetRow;
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
}, [props.pinnedAsset, rowData]);
const { getRowHeight } = props;
@@ -131,225 +136,192 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(params: RowHeightParams) => {
if (
params.node.rowPinned &&
params.data.asset.id === pinnedAssetId &&
params.data.asset.id === props.pinnedAsset?.id &&
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
) {
return 32;
}
return getRowHeight ? getRowHeight(params) : undefined;
},
[pinnedAssetId, getRowHeight]
[props.pinnedAsset?.id, getRowHeight]
);
const accountForPinnedAsset = props?.rowData?.find(
(a) => a.asset.id === pinnedAssetId
);
const showDepositButton = accountForPinnedAsset
? new BigNumber(accountForPinnedAsset.total).isLessThanOrEqualTo(0)
: true;
const showDepositButton = pinnedAsset?.balance === '0';
return (
<>
<AgGrid
{...props}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No accounts')}
getRowId={({
data,
}: {
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
ref={ref}
tooltipShowDelay={500}
rowData={props.rowData?.filter(
(data) => data.asset.id !== pinnedAssetId
<AgGrid
{...props}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No accounts')}
getRowId={({
data,
}: {
data: AccountFields & { isLastPlaceholder?: boolean; id?: string };
}) => (data.isLastPlaceholder && data.id ? data.id : data.asset.id)}
ref={ref}
tooltipShowDelay={500}
rowData={rowData?.filter(
(data) => data.asset.id !== props.pinnedAsset?.id
)}
defaultColDef={{
resizable: true,
tooltipComponent: TooltipCellComponent,
sortable: true,
comparator: accountValuesComparator,
}}
getRowHeight={getPinnedAssetRowHeight}
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
>
<AgGridColumn
headerName={t('Asset')}
field="asset.symbol"
headerTooltip={t(
'Asset is the collateral that is deposited into the Vega protocol.'
)}
defaultColDef={{
resizable: true,
tooltipComponent: TooltipCellComponent,
sortable: true,
comparator: accountValuesComparator,
cellRenderer={({
value,
data,
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
return (
<ButtonLink
data-testid="asset"
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
}}
>
{value}
</ButtonLink>
);
}}
getRowHeight={getPinnedAssetRowHeight}
pinnedTopRowData={pinnedAsset ? [pinnedAsset] : undefined}
>
<AgGridColumn
headerName={t('Asset')}
field="asset.symbol"
headerTooltip={t(
'Asset is the collateral that is deposited into the Vega protocol.'
)}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<AccountFields, 'asset.symbol'>) => {
return (
/>
<AgGridColumn
headerName={t('Used')}
type="rightAligned"
field="used"
headerTooltip={t(
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
)}
cellRenderer={({
data,
value,
}: VegaICellRendererParams<AccountFields, 'used'>) => {
if (!data) return null;
const percentageUsed = percentageValue(value, data.total);
const valueFormatted = formatWithAssetDecimals(data, value);
return data.breakdown ? (
<>
<ButtonLink
data-testid="asset"
data-testid="breakdown"
onClick={() => {
if (data) {
onClickAsset(data.asset.id);
}
onClickBreakdown && onClickBreakdown(data.asset.id);
}}
>
{value}
<span>{valueFormatted}</span>
</ButtonLink>
);
}}
/>
<AgGridColumn
headerName={t('Used')}
type="rightAligned"
field="used"
headerTooltip={t(
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
)}
cellRenderer={({
data,
value,
}: VegaICellRendererParams<AccountFields, 'used'>) => {
if (!data) return null;
const percentageUsed = percentageValue(value, data.total);
const valueFormatted = formatWithAssetDecimals(data, value);
<span
className={classNames(
colorClass(percentageUsed),
'ml-2 inline-block w-14'
)}
>
{percentageUsed.toFixed(2)}%
</span>
</>
) : (
<>
<span>{valueFormatted}</span>
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
0.00%
</span>
</>
);
}}
/>
<AgGridColumn
headerName={t('Available')}
field="available"
type="rightAligned"
headerTooltip={t(
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
)}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<AccountFields, 'available'>) => {
const percentageUsed = percentageValue(data?.used, data?.total);
return data.breakdown ? (
<>
<ButtonLink
data-testid="breakdown"
return (
<span className={colorClass(percentageUsed, true)}>
{formatWithAssetDecimals(data, value)}
</span>
);
}}
/>
<AgGridColumn
headerName={t('Total')}
type="rightAligned"
field="total"
headerTooltip={t(
'The total amount of each asset on this key. Includes used and available collateral.'
)}
valueFormatter={({
data,
}: VegaValueFormatterParams<AccountFields, 'total'>) =>
formatWithAssetDecimals(data, data?.total)
}
/>
<AgGridColumn
colId="accounts-actions"
field="asset.id"
{...COL_DEFS.actions}
minWidth={showDepositButton ? 130 : COL_DEFS.actions.minWidth}
maxWidth={showDepositButton ? 130 : COL_DEFS.actions.maxWidth}
cellRenderer={({
value: assetId,
node,
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
if (!assetId) return null;
if (node.rowPinned && node.data?.balance === '0') {
return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
<Button
size="xs"
variant="primary"
data-testid="deposit"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setRow(data);
onClickDeposit && onClickDeposit(assetId);
}}
>
<span>{valueFormatted}</span>
</ButtonLink>
<span
className={classNames(
colorClass(percentageUsed),
'ml-2 inline-block w-14'
)}
>
{percentageUsed.toFixed(2)}%
</span>
</>
) : (
<>
<span>{valueFormatted}</span>
<span className="ml-2 inline-block w-14 text-neutral-500 dark:text-neutral-400">
0.00%
</span>
</>
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
</Button>
</CenteredGridCellWrapper>
);
}}
/>
<AgGridColumn
headerName={t('Available')}
field="available"
type="rightAligned"
headerTooltip={t(
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
)}
cellRenderer={({
value,
data,
}: VegaICellRendererParams<AccountFields, 'available'>) => {
const percentageUsed = percentageValue(data?.used, data?.total);
return (
<span className={colorClass(percentageUsed, true)}>
{formatWithAssetDecimals(data, value)}
</span>
);
}}
/>
<AgGridColumn
headerName={t('Total')}
type="rightAligned"
field="total"
headerTooltip={t(
'The total amount of each asset on this key. Includes used and available collateral.'
)}
valueFormatter={({
data,
}: VegaValueFormatterParams<AccountFields, 'total'>) =>
formatWithAssetDecimals(data, data?.total)
}
/>
<AgGridColumn
colId="accounts-actions"
{...COL_DEFS.actions}
minWidth={showDepositButton ? 130 : COL_DEFS.actions.minWidth}
maxWidth={showDepositButton ? 130 : COL_DEFS.actions.maxWidth}
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
if (!data) return null;
else {
if (showDepositButton && data.asset.id === pinnedAssetId) {
return (
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
<Button
size="xs"
variant="primary"
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
</Button>
</CenteredGridCellWrapper>
);
return props.isReadOnly ? null : (
<AccountsActionsDropdown
assetId={assetId}
assetContractAddress={
node.data?.asset.source?.__typename === 'ERC20'
? node.data.asset.source.contractAddress
: undefined
}
return (
!props.isReadOnly && (
<AccountsActionsDropdown
assetId={data.asset.id}
assetContractAddress={
data.asset.source?.__typename === 'ERC20'
? data.asset.source.contractAddress
: undefined
}
onClickDeposit={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
onClickWithdraw={() => {
onClickWithdraw && onClickWithdraw(data.asset.id);
}}
onClickBreakdown={() => {
setOpenBreakdown(!openBreakdown);
setRow(data);
}}
/>
)
);
}
}}
/>
</AgGrid>
<Dialog size="medium" open={openBreakdown} onChange={setOpenBreakdown}>
<div
className="h-[35vh] w-full m-auto flex flex-col"
data-testid="usage-breakdown"
>
<h1 className="text-xl mb-4">
{row?.asset?.symbol} {t('usage breakdown')}
</h1>
{row && (
<p className="mb-2 text-sm">
{t('You have %s %s in total.', [
addDecimalsFormatNumber(row.total, row.asset.decimals),
row.asset.symbol,
])}
</p>
)}
<BreakdownTable
data={row?.breakdown || null}
domLayout="autoHeight"
/>
</div>
</Dialog>
</>
onClickDeposit={() => {
onClickDeposit && onClickDeposit(assetId);
}}
onClickWithdraw={() => {
onClickWithdraw && onClickWithdraw(assetId);
}}
onClickBreakdown={() => {
onClickBreakdown && onClickBreakdown(assetId);
}}
/>
);
}}
/>
</AgGrid>
);
}
);
+1 -1
View File
@@ -13,7 +13,7 @@ import { createClient as createWSClient } from 'graphql-ws';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';
import ApolloLinkTimeout from 'apollo-link-timeout';
import { localLoggerFactory } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { useHeaderStore } from './header-store';
const isBrowser = typeof window !== 'undefined';
@@ -31,6 +31,29 @@ export const assetsProvider = makeDataProvider<
getData,
});
export const assetsMapProvider = makeDerivedDataProvider<
Record<string, Asset>,
never,
undefined
>(
[(callback, client) => assetsProvider(callback, client, undefined)],
([assets]) => {
return ((assets as ReturnType<typeof getData>) || []).reduce(
(assets, asset) => {
assets[asset.id] = asset;
return assets;
},
{} as Record<string, Asset>
);
}
);
export const useAssetsMapProvider = () =>
useDataProvider({
dataProvider: assetsMapProvider,
variables: undefined,
});
export const enabledAssetsProvider = makeDerivedDataProvider<
ReturnType<typeof getData>,
never
@@ -7,44 +7,54 @@ declare global {
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
VegaWalletTopUpRewardsPool(
transferStartEpoch: number,
transferEndEpoch: number
): void;
VegaWalletTopUpRewardsPool(): void;
}
}
}
export function addVegaWalletTopUpRewardsPool() {
Cypress.Commands.add(
'VegaWalletTopUpRewardsPool',
(transferStartEpoch, transferEndEpoch) => {
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
const assetAddress =
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b';
Cypress.Commands.add('VegaWalletTopUpRewardsPool', () => {
let transferStartEpoch = 0;
let transferEndEpoch = 0;
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
const assetAddress =
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b';
createWalletClient(vegaWalletUrl, token);
cy.getByTestId('epoch-countdown')
.within(() => {
cy.get('h3')
.invoke('text')
.then((epochText) => {
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
transferEndEpoch = transferStartEpoch + 100;
const transactionBody: TransferBody = {
transfer: {
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
to: '0000000000000000000000000000000000000000000000000000000000000000',
asset: assetAddress,
amount: '1000000000000000000',
recurring: {
factor: '1',
startEpoch: transferStartEpoch,
endEpoch: transferEndEpoch,
console.log(transferStartEpoch);
console.log(transferEndEpoch);
});
})
.then(() => {
createWalletClient(vegaWalletUrl, token);
const transactionBody: TransferBody = {
transfer: {
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
to: '0000000000000000000000000000000000000000000000000000000000000000',
asset: assetAddress,
amount: '1000000000000000000',
recurring: {
factor: '1',
startEpoch: transferStartEpoch,
endEpoch: transferEndEpoch,
},
},
},
};
};
cy.highlight('Topping up rewards pool');
cy.highlight('Topping up rewards pool');
sendVegaTx(vegaPubKey, transactionBody);
}
);
sendVegaTx(vegaPubKey, transactionBody);
});
});
}
@@ -1,6 +1,7 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames';
@@ -23,6 +24,8 @@ export const AgGridThemed = ({
rowHeight: 22,
headerHeight: 22,
enableCellTextSelection: true,
overlayLoadingTemplate: t('Loading...'),
overlayNoRowsTemplate: t('No data'),
};
const wrapperClasses = classNames('vega-ag-grid', {
+7 -2
View File
@@ -1,10 +1,12 @@
import { forwardRef } from 'react';
import classNames from 'classnames';
import { getDecimalSeparator, isNumeric } from '@vegaprotocol/utils';
interface NumericCellProps {
value: number | bigint | null | undefined;
valueFormatted: string;
testId?: string;
className?: string;
}
/**
@@ -12,7 +14,7 @@ interface NumericCellProps {
* use, right aligned, monospace and decimals deemphasised
*/
export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
({ value, valueFormatted, testId }, ref) => {
({ value, valueFormatted, testId, className }, ref) => {
if (!isNumeric(value)) {
return (
<span ref={ref} data-testid={testId}>
@@ -29,7 +31,10 @@ export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
return (
<span
ref={ref}
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
className={classNames(
'font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir',
className
)}
data-testid={testId}
title={valueFormatted}
>
@@ -16,7 +16,7 @@ export const OrderTypeCell = ({
data: order,
onClick,
}: OrderTypeCellProps) => {
const id = order ? order.market.id : '';
const id = order?.market?.id ?? '';
const label = useMemo(() => {
if (!order) {
+6 -1
View File
@@ -6,11 +6,15 @@ export interface IPriceCellProps {
valueFormatted: string;
testId?: string;
onClick?: (price?: string | number) => void;
className?: string;
}
export const PriceCell = memo(
forwardRef<HTMLSpanElement, IPriceCellProps>(
({ value, valueFormatted, testId, onClick }: IPriceCellProps, ref) => {
(
{ value, valueFormatted, testId, onClick, className }: IPriceCellProps,
ref
) => {
if (!isNumeric(value)) {
return (
<span data-testid="price" ref={ref}>
@@ -27,6 +31,7 @@ export const PriceCell = memo(
value={value}
valueFormatted={valueFormatted}
testId={testId || 'price'}
className={className}
/>
</button>
) : (
+7 -6
View File
@@ -4,17 +4,18 @@ import type {
ValueFormatterParams,
ValueGetterParams,
} from 'ag-grid-community';
import type { IDatasource, IGetRowsParams } from 'ag-grid-community';
import type { IDatasource, IGetRowsParams, RowNode } from 'ag-grid-community';
import type { AgGridReactProps } from 'ag-grid-react';
type Field = string | readonly string[];
type RowHelper<TObj, TRow, TField extends Field> = Omit<
TObj,
'data' | 'value'
'data' | 'value' | 'node'
> & {
data?: TRow;
value?: Get<TRow, TField>;
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
};
export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
@@ -29,10 +30,10 @@ export type VegaValueGetterParams<TRow, TField extends Field> = RowHelper<
TField
>;
export type VegaICellRendererParams<
TRow,
TField extends Field = string
> = RowHelper<ICellRendererParams, TRow, TField>;
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
RowHelper<ICellRendererParams, TRow, TField>,
'node'
> & { node: NonNullable<RowHelper<ICellRendererParams, TRow, TField>['node']> };
export interface GetRowsParams<T> extends IGetRowsParams {
successCallback(rowsThisBlock: T[], lastRow?: number): void;
@@ -31,6 +31,7 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => {
text: t(`Deposit ${asset.symbol}`),
action: () => openDepositDialog(asset.id),
dataTestId: 'deal-ticket-deposit-dialog-button',
size: 'sm',
}}
/>
);
@@ -21,10 +21,14 @@ export const ZeroBalanceError = ({
testId="dealticket-error-message-zero-balance"
message={
<>
You need {asset.symbol} in your wallet to trade in this market.
{t(
'You need %s in your wallet to trade in this market. ',
asset.symbol
)}
{onClickCollateral && (
<>
See all your <Link onClick={onClickCollateral}>collateral</Link>.
{t('See all your')}{' '}
<Link onClick={onClickCollateral}>collateral</Link>.
</>
)}
</>
@@ -33,7 +37,7 @@ export const ZeroBalanceError = ({
text: t(`Make a deposit`),
action: () => openDepositDialog(asset.id),
dataTestId: 'deal-ticket-deposit-dialog-button',
size: 'md',
size: 'sm',
}}
/>
);
@@ -1,25 +1,15 @@
import { t } from '@vegaprotocol/i18n';
import type { ButtonVariant } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
interface Props {
disabled: boolean;
variant: ButtonVariant;
}
export const DealTicketButton = ({ disabled, variant }: Props) => {
const { pubKey, isReadOnly } = useVegaWallet();
const isDisabled = !pubKey || isReadOnly || disabled;
export const DealTicketButton = ({ variant }: Props) => {
return (
<div className="mb-2">
<Button
variant={variant}
fill
type="submit"
disabled={isDisabled}
data-testid="place-order"
>
<Button variant={variant} fill type="submit" data-testid="place-order">
{t('Place order')}
</Button>
</div>
@@ -33,24 +33,35 @@ export const DealTicketFeeDetails = (props: FeeDetails) => {
const details = getFeeDetailsValues(props);
return (
<div>
{details.map(({ label, value, labelDescription, symbol, indent }) => (
<div
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
>
<div>
<Tooltip description={labelDescription}>
<div>{label}</div>
{details.map(
({
label,
value,
labelDescription,
symbol,
indent,
formattedValue,
}) => (
<div
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
>
<div>
<Tooltip description={labelDescription}>
<div>{label}</div>
</Tooltip>
</div>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
<div className="text-neutral-500 dark:text-neutral-300">{`${
formattedValue ?? '-'
} ${symbol || ''}`}</div>
</Tooltip>
</div>
<div className="text-neutral-500 dark:text-neutral-300">{`${
value ?? '-'
} ${symbol || ''}`}</div>
</div>
))}
)
)}
</div>
);
};
@@ -42,6 +42,9 @@ describe('DealTicket', () => {
it('should display ticket defaults', () => {
const { container } = render(generateJsx());
// place order button should always be enabled
expect(screen.getByTestId('place-order')).toBeEnabled();
// Assert defaults are used
expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
@@ -217,6 +217,8 @@ export const DealTicket = ({
});
return;
}
// No error found above clear the error in case it was active on a previous render
clearErrors('summary');
}, [
marketState,
@@ -480,7 +482,6 @@ export const DealTicket = ({
onClickCollateral={onClickCollateral}
/>
<DealTicketButton
disabled={Object.keys(errors).length >= 1 || isReadOnly}
variant={
order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'
}
@@ -562,7 +563,7 @@ const SummaryMessage = memo(
text: t('Connect wallet'),
action: openVegaWalletDialog,
dataTestId: 'order-connect-wallet',
size: 'md',
size: 'sm',
}}
/>
</div>
@@ -0,0 +1,68 @@
import { formatRange, formatValue } from './use-fee-deal-ticket-details';
describe('useFeeDealTicketDetails', () => {
it.each([
{ v: 123000, d: 5, o: '1.23' },
{ v: 123000, d: 3, o: '123.00' },
{ v: 123000, d: 1, o: '12,300.0' },
{ v: 123001000, d: 2, o: '1,230,010.00' },
{ v: 123001, d: 2, o: '1,230.01' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.91234568',
},
])('formats values correctly', ({ v, d, o }) => {
expect(formatValue(v, d)).toStrictEqual(o);
});
it.each([
{ v: 123000, d: 5, o: '1.23', q: '0.1' },
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
{ v: 123001, d: 2, o: '1,230', q: '100' },
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
{
v: '123456789123456789',
d: 10,
o: '12,345,678.9123457',
q: '0.00003846',
},
])(
'formats with formatValue with quantum given number correctly',
({ v, d, o, q }) => {
expect(formatValue(v.toString(), d, q)).toStrictEqual(o);
}
);
it.each([
{ min: 123000, max: 12300011111, d: 5, o: '1.23 - 123,000.111', q: '0.1' },
{
min: 123000,
max: 12300011111,
d: 3,
o: '123.00 - 12,300,011.111',
q: '0.1',
},
{
min: 123000,
max: 12300011111,
d: 1,
o: '12,300.00 - 1,230,001,111.10',
q: '0.1',
},
{
min: 123001000,
max: 12300011111,
d: 2,
o: '1,230,010 - 123,000,111',
q: '100',
},
])(
'formats with formatValue with quantum given number correctly',
({ min, max, d, o, q }) => {
expect(formatRange(min, max, d, q)).toStrictEqual(o);
}
);
});
@@ -1,5 +1,9 @@
import { FeesBreakdown } from '@vegaprotocol/markets';
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
isNumeric,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Market } from '@vegaprotocol/markets';
@@ -52,21 +56,25 @@ export interface FeeDetails {
}
const emptyValue = '-';
const formatValue = (
export const formatValue = (
value: string | number | null | undefined,
formatDecimals: number
formatDecimals: number,
quantum?: string
): string => {
return isNumeric(value)
? addDecimalsFormatNumber(value, formatDecimals)
: emptyValue;
if (!isNumeric(value)) return emptyValue;
if (!quantum) return addDecimalsFormatNumber(value, formatDecimals);
return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum);
};
const formatRange = (
export const formatRange = (
min: string | number | null | undefined,
max: string | number | null | undefined,
formatDecimals: number
formatDecimals: number,
quantum?: string
) => {
const minFormatted = formatValue(min, formatDecimals);
const maxFormatted = formatValue(max, formatDecimals);
const minFormatted = formatValue(min, formatDecimals, quantum);
const maxFormatted = formatValue(max, formatDecimals, quantum);
if (minFormatted !== maxFormatted) {
return `${minFormatted} - ${maxFormatted}`;
}
@@ -93,9 +101,12 @@ export const getFeeDetailsValues = ({
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quantum =
market.tradableInstrument.instrument.product.settlementAsset.quantum;
const details: {
label: string;
value?: string | null;
formattedValue?: string | null;
symbol: string;
indent?: boolean;
labelDescription?: React.ReactNode;
@@ -103,6 +114,7 @@ export const getFeeDetailsValues = ({
{
label: t('Notional'),
value: formatValue(notionalSize, assetDecimals),
formattedValue: formatValue(notionalSize, assetDecimals, quantum),
symbol: assetSymbol,
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
},
@@ -111,6 +123,9 @@ export const getFeeDetailsValues = ({
value:
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`,
formattedValue:
feeEstimate?.totalFeeAmount &&
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`,
labelDescription: (
<>
<span>
@@ -154,6 +169,12 @@ export const getFeeDetailsValues = ({
}
details.push({
label: t('Margin required'),
formattedValue: formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals,
quantum
),
value: formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
@@ -172,12 +193,13 @@ export const getFeeDetailsValues = ({
details.push({
indent: true,
label: t('Total margin available'),
formattedValue: formatValue(totalMarginAvailable, assetDecimals, quantum),
value: formatValue(totalMarginAvailable, assetDecimals),
symbol: assetSymbol,
labelDescription: TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals),
formatValue(marginAccountBalance, assetDecimals),
formatValue(currentMaintenanceMargin, assetDecimals),
formatValue(generalAccountBalance, assetDecimals, quantum),
formatValue(marginAccountBalance, assetDecimals, quantum),
formatValue(currentMaintenanceMargin, assetDecimals, quantum),
assetSymbol
),
});
@@ -203,6 +225,16 @@ export const getFeeDetailsValues = ({
: '0',
assetDecimals
),
formattedValue: formatRange(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals,
quantum
),
symbol: assetSymbol,
labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol),
});
@@ -214,6 +246,12 @@ export const getFeeDetailsValues = ({
marginEstimate?.worstCase.initialLevel,
assetDecimals
),
formattedValue: formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals,
quantum
),
symbol: assetSymbol,
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
});
@@ -223,9 +261,11 @@ export const getFeeDetailsValues = ({
value: formatValue(marginAccountBalance, assetDecimals),
symbol: assetSymbol,
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
formattedValue: formatValue(marginAccountBalance, assetDecimals, quantum),
});
let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateFormatted;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
@@ -262,11 +302,24 @@ export const getFeeDetailsValues = ({
).toString(),
assetDecimals
);
liquidationPriceEstimateFormatted = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
(liquidationEstimateBestCase > liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
assetDecimals,
quantum
);
}
details.push({
label: t('Liquidation price estimate'),
value: liquidationPriceEstimate,
formattedValue: liquidationPriceEstimateFormatted,
symbol: assetSymbol,
labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
});
+1
View File
@@ -32,6 +32,7 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
symbol: 'tDAI',
name: 'tDAI',
decimals: 5,
quantum: '1',
__typename: 'Asset',
},
dataSourceSpecForTradingTermination: {
@@ -1,14 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
export const validateMarketState = (state: Schema.MarketState) => {
export const validateMarketState = (state: MarketState) => {
if (
[
Schema.MarketState.STATE_SETTLED,
Schema.MarketState.STATE_REJECTED,
Schema.MarketState.STATE_TRADING_TERMINATED,
Schema.MarketState.STATE_CANCELLED,
Schema.MarketState.STATE_CLOSED,
MarketState.STATE_SETTLED,
MarketState.STATE_REJECTED,
MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_CANCELLED,
MarketState.STATE_CLOSED,
].includes(state)
) {
return t(
@@ -16,7 +16,7 @@ export const validateMarketState = (state: Schema.MarketState) => {
);
}
if (state === Schema.MarketState.STATE_PROPOSED) {
if (state === MarketState.STATE_PROPOSED) {
return t(
`This market is ${marketTranslations(
state
@@ -27,11 +27,11 @@ export const validateMarketState = (state: Schema.MarketState) => {
return true;
};
const marketTranslations = (marketState: Schema.MarketState) => {
const marketTranslations = (marketState: MarketState) => {
switch (marketState) {
case Schema.MarketState.STATE_TRADING_TERMINATED:
case MarketState.STATE_TRADING_TERMINATED:
return t('terminated');
default:
return t(Schema.MarketStateMapping[marketState]).toLowerCase();
return t(MarketStateMapping[marketState]).toLowerCase();
}
};
@@ -1,10 +1,10 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { MarketTradingMode } from '@vegaprotocol/types';
export const validateMarketTradingMode = (
marketTradingMode: Schema.MarketTradingMode
marketTradingMode: MarketTradingMode
) => {
if (marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_NO_TRADING) {
if (marketTradingMode === MarketTradingMode.TRADING_MODE_NO_TRADING) {
return t('Trading terminated');
}
@@ -8,7 +8,8 @@ import {
useIsExemptDepositor,
} from './use-get-deposit-maximum';
import { useGetDepositedAmount } from './use-get-deposited-amount';
import { isAssetTypeERC20, localLoggerFactory } from '@vegaprotocol/utils';
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { useAccountBalance } from '@vegaprotocol/accounts';
import type { Asset } from '@vegaprotocol/assets';
import { useWeb3React } from '@web3-react/core';
+2 -1
View File
@@ -4,7 +4,8 @@ import { useCallback } from 'react';
import { useEthereumConfig } from '@vegaprotocol/web3';
import BigNumber from 'bignumber.js';
import type { Asset } from '@vegaprotocol/assets';
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
import { addDecimal } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
export const useGetAllowance = (
contract: Token | null,
@@ -1,7 +1,8 @@
import { useCallback } from 'react';
import BigNumber from 'bignumber.js';
import type { Asset } from '@vegaprotocol/assets';
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
import { addDecimal } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';
export const useGetDepositMaximum = (
@@ -3,7 +3,8 @@ import { ethers } from 'ethers';
import { useEthereumConfig } from '@vegaprotocol/web3';
import BigNumber from 'bignumber.js';
import type { Asset } from '@vegaprotocol/assets';
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
import { addDecimal } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { useWeb3React } from '@web3-react/core';
export const useGetDepositedAmount = (asset: Asset | undefined) => {
+1
View File
@@ -72,6 +72,7 @@ export const DocsLinks = VEGA_DOCS_URL
POSITION_RESOLUTION: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-protections#position-resolution`,
LIQUIDITY: `${VEGA_DOCS_URL}/concepts/liquidity/provision`,
WITHDRAWAL_LIMITS: `${VEGA_DOCS_URL}/concepts/assets/deposits-withdrawals#withdrawal-limits`,
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
}
: undefined;
+1 -1
View File
@@ -1,4 +1,4 @@
import { localLoggerFactory } from '@vegaprotocol/utils';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { useCallback, useEffect, useState } from 'react';
import z from 'zod';
+10 -61
View File
@@ -1,9 +1,8 @@
import compact from 'lodash/compact';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useRef } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
import { useFillsList } from './use-fills-list';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
@@ -22,80 +21,30 @@ export const FillsManager = ({
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
const {
data,
error,
loading,
addNewRows,
getRows,
reload,
makeBottomPlaceholders,
} = useFillsList({
const { data, error } = useFillsList({
partyId,
marketId,
gridRef,
scrolledToTop,
});
const checkBottomPlaceholder = useCallback(() => {
const rowCont = gridRef.current?.api?.getModel().getRowCount() ?? 0;
const lastRowIndex = gridRef.current?.api?.getLastDisplayedRow();
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
const lastrow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
lastrow?.setRowHeight(50);
makeBottomPlaceholders(lastrow?.data);
gridRef.current?.api.onRowHeightChanged();
gridRef.current?.api.refreshInfiniteCache();
}
}, [makeBottomPlaceholders]);
const bottomPlaceholderProps = useBottomPlaceholder({
gridRef,
});
const onBodyScrollEnd = useCallback(
(event: BodyScrollEndEvent) => {
if (event.top === 0) {
addNewRows();
}
checkBottomPlaceholder();
},
[addNewRows, checkBottomPlaceholder]
);
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
scrolledToTop.current = event.top <= 0;
}, []);
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
useBottomPlaceholder({
gridRef,
});
const fills = compact(data).map((e) => e.node);
return (
<div className="h-full relative">
<FillsTable
ref={gridRef}
rowData={fills}
partyId={partyId}
rowModelType="infinite"
datasource={{ getRows }}
onBodyScrollEnd={onBodyScrollEnd}
onBodyScroll={onBodyScroll}
onMarketClick={onMarketClick}
suppressLoadingOverlay
suppressNoRowsOverlay
isFullWidthRow={isFullWidthRow}
fullWidthCellRenderer={fullWidthCellRenderer}
rowClassRules={rowClassRules}
getRowHeight={getRowHeight}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No fills')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
);
};
+1
View File
@@ -75,6 +75,7 @@ export const generateFill = (override?: PartialDeep<Trade>) => {
name: 'assset-id',
symbol: 'SYM',
decimals: 18,
quantum: '1',
},
quoteName: '',
dataSourceSpecForTradingTermination: {
+5 -24
View File
@@ -1,11 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import type * as Schema from '@vegaprotocol/types';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { FilterChangedEvent } from 'ag-grid-community';
import type { AgGridReact } from 'ag-grid-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useRef, useState } from 'react';
import { subDays, formatRFC3339 } from 'date-fns';
import type { AggregatedLedgerEntriesNode } from './ledger-entries-data-provider';
import { useLedgerEntriesDataProvider } from './ledger-entries-data-provider';
import { LedgerTable } from './ledger-table';
import type * as Types from '@vegaprotocol/types';
@@ -27,9 +25,8 @@ const defaultFilter = {
export const LedgerManager = ({ partyId }: { partyId: string }) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter>(defaultFilter);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = useLedgerEntriesDataProvider({
const { data, error } = useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
@@ -39,16 +36,9 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
}, []);
const extractNodesDecorator = useCallback(
(data: AggregatedLedgerEntriesNode[] | null, loading: boolean) =>
data && !loading ? data.map((item) => item.node) : null,
[]
);
const extractedData = extractNodesDecorator(data, loading);
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [extractedData]);
// allow passing undefined to grid so that loading state is shown
const extractedData = data?.map((item) => item.node);
return (
<div className="h-full relative">
@@ -56,20 +46,11 @@ export const LedgerManager = ({ partyId }: { partyId: string }) => {
ref={gridRef}
rowData={extractedData}
onFilterChanged={onFilterChanged}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
/>
{extractedData && (
<LedgerExportLink entries={extractedData} partyId={partyId} />
)}
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={data}
noDataMessage={t('No entries')}
noDataCondition={() => !dataCount}
reload={reload}
/>
</div>
</div>
);
};
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+18
View File
@@ -0,0 +1,18 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
+7
View File
@@ -0,0 +1,7 @@
# logger
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test logger` to execute the unit tests via [Jest](https://jestjs.io).
+10
View File
@@ -0,0 +1,10 @@
/* eslint-disable */
export default {
displayName: 'logger',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/logger',
};
+5
View File
@@ -0,0 +1,5 @@
{
"name": "@vegaprotocol/logger",
"version": "0.0.1",
"type": "commonjs"
}
+34
View File
@@ -0,0 +1,34 @@
{
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/logger/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/js:tsc",
"outputs": ["{options.outputPath}"],
"format": ["esm", "cjs"],
"options": {
"outputPath": "dist/libs/logger",
"main": "libs/logger/src/index.ts",
"tsConfig": "libs/logger/tsconfig.lib.json",
"assets": ["libs/logger/*.md"]
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/logger/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/libs/logger"],
"options": {
"jestConfig": "libs/logger/jest.config.ts",
"passWithNoTests": true
}
}
}
}

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