Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fb8c0965 | ||
|
|
d61ecaed5d | ||
|
|
72bad93ef5 | ||
|
|
ea69e916ad | ||
|
|
9aaaec779e | ||
|
|
251d0369ca | ||
|
|
f51f827cc4 | ||
|
|
a7ad3a4acd | ||
|
|
13f2e51798 | ||
|
|
fc047feeee | ||
|
|
005455c870 | ||
|
|
2c2bc391e8 | ||
|
|
445a085190 | ||
|
|
bb1b236cdf | ||
|
|
da66b7b20d | ||
|
|
ec12811f72 | ||
|
|
180de8cf25 | ||
|
|
df88e77cdf | ||
|
|
9441aee8cf | ||
|
|
2353812834 | ||
|
|
bba2b3c177 | ||
|
|
ae57bd92f4 | ||
|
|
115b642140 | ||
|
|
ce3da97a8a | ||
|
|
cabd99d3ef | ||
|
|
597e07608f | ||
|
|
e451dc54b3 | ||
|
|
9703c3b7a6 | ||
|
|
c22b6f3ce9 | ||
|
|
c740c11eaa | ||
|
|
d1fabb31a3 | ||
|
|
94f045a60b | ||
|
|
1af375bab8 | ||
|
|
5a48ba4a33 | ||
|
|
d1fd9184ce |
@@ -173,7 +173,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: dockerhub-push
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }}
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
@@ -238,24 +238,34 @@ jobs:
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) }}
|
||||
run: |
|
||||
if echo ${{ github.ref }} | grep -q main; then
|
||||
fleek_id='f8f2e051-f18e-49e6-b876-0a39369dc0d8'
|
||||
elif echo ${{ github.ref }} | grep -q release/testnet; then
|
||||
fleek_id='79baaeca-1952-4ae7-a256-f668cfc1d68e'
|
||||
fi
|
||||
# display info about app
|
||||
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query{getSiteById(siteId:\"$fleek_id\"){id latestDeploy{id status}}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
# display info about app
|
||||
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
|
||||
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "mutation{triggerDeploy(siteId:\"$fleek_id\"){id status}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
|
||||
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
elif echo ${{ github.ref }} | grep -q release/testnet; then
|
||||
# display info about app
|
||||
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query{getSiteById(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id latestDeploy{id status}}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
|
||||
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "mutation{triggerDeploy(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id status}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
fi
|
||||
|
||||
- name: Check out ipfs-redirect
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }}
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'vegaprotocol/ipfs-redirect'
|
||||
@@ -263,11 +273,12 @@ jobs:
|
||||
fetch-depth: '0'
|
||||
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Update console.vega.xyz DNS to redirect to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }}
|
||||
- name: Update interstitial page to point to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
# set CID
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
@@ -275,64 +286,28 @@ jobs:
|
||||
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
ls -al ipfs-redirect
|
||||
|
||||
echo $new_hash > ipfs-redirect/cidv0.txt
|
||||
echo $new_cid > ipfs-redirect/cidv1.txt
|
||||
|
||||
(
|
||||
cd ipfs-redirect
|
||||
|
||||
# configure git
|
||||
git status
|
||||
cat .git/config
|
||||
git config --global user.email "vega-ci-bot@vega.xyz"
|
||||
git config --global user.name "vega-ci-bot"
|
||||
|
||||
branch_name="update-hash-${{ github.ref }}"
|
||||
git checkout -b "$branch_name"
|
||||
# update CID files
|
||||
if echo ${{ github.ref }} | grep -q main; then
|
||||
echo $new_hash > cidv0-mainnet.txt
|
||||
echo $new_cid > cidv1-mainnet.txt
|
||||
git add cidv0-mainnet.txt cidv1-mainnet.txt
|
||||
elif echo ${{ github.ref }} | grep -q release/testnet; then
|
||||
echo $new_hash > cidv0-fairground.txt
|
||||
echo $new_cid > cidv1-fairground.txt
|
||||
git add cidv0-fairground.txt cidv1-fairground.txt
|
||||
fi
|
||||
|
||||
# create commit
|
||||
commit_msg="Automated hash update from ${{ github.ref }}"
|
||||
git add cidv0.txt cidv1.txt
|
||||
git commit -m "$commit_msg"
|
||||
git push -u origin "$branch_name"
|
||||
pr_url="$(gh pr create --title "${commit_msg}" --body 'automated pull request to update CIDs')"
|
||||
echo $pr_url
|
||||
# once auto merge get's enabled on documentation repo let's do follow up
|
||||
sleep 5
|
||||
gh pr merge "${pr_url}" --delete-branch --squash --admin
|
||||
git push -u origin "main"
|
||||
)
|
||||
|
||||
- name: Update console.fairground.wtf DNS to redirect to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'testnet') }}
|
||||
run: |
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
which ipfs
|
||||
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
# Generate console URL
|
||||
|
||||
new_console_url_type=ipfs
|
||||
# new_console_url_type=ipns
|
||||
|
||||
new_console_url_domain=cf-ipfs.com
|
||||
# new_console_url_domain=dweb.link
|
||||
|
||||
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
|
||||
echo "new_console_url=${new_console_url}"
|
||||
|
||||
# Update record in DNSimple
|
||||
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
|
||||
dnsimple_account_id=84895
|
||||
dnsimple_zone_name=fairground.wtf
|
||||
dnsimple_record_id=44571545
|
||||
# see: https://dnsimple.com/a/84895/domains/fairground.wtf/records/44571545/edit
|
||||
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
-d "{
|
||||
\"content\": \"${new_console_url}\"
|
||||
}" \
|
||||
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
|
||||
|
||||
@@ -77,7 +77,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
|
||||
hide={window.innerWidth <= BREAKPOINT_MD}
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => {
|
||||
}: VegaValueGetterParams<MarketFieldsFragment>) => {
|
||||
return data?.state ? MarketStateMapping[data?.state] : '-';
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -14,9 +14,16 @@ import {
|
||||
submitUniqueRawProposal,
|
||||
voteForProposal,
|
||||
} from '../../../../governance-e2e/src/support/governance.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governance-e2e/src/support/staking.functions';
|
||||
import {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated,
|
||||
stakingPageAssociateTokens,
|
||||
stakingPageDisassociateAllTokens,
|
||||
} from '../../../../governance-e2e/src/support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions';
|
||||
import {
|
||||
switchVegaWalletPubKey,
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
} from '../../support/wallet-functions';
|
||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
|
||||
@@ -44,7 +51,7 @@ describe(
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.visit('/');
|
||||
ethereumWalletConnect();
|
||||
cy.associateTokensToVegaWallet('1');
|
||||
// cy.associateTokensToVegaWallet('1');
|
||||
});
|
||||
|
||||
beforeEach('visit proposals tab', function () {
|
||||
@@ -297,5 +304,33 @@ describe(
|
||||
.and('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to vote for proposal twice by switching public key', function () {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.get(viewProposalButton).click()
|
||||
);
|
||||
voteForProposal('for');
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
ethereumWalletConnect();
|
||||
switchVegaWalletPubKey();
|
||||
stakingPageAssociateTokens('2');
|
||||
navigateTo(navigation.proposals);
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() =>
|
||||
cy.get(viewProposalButton).click()
|
||||
);
|
||||
cy.getByTestId('you-voted').should('not.exist');
|
||||
voteForProposal('against');
|
||||
cy.contains('You voted: Against').should('be.visible');
|
||||
switchVegaWalletPubKey();
|
||||
cy.get(proposalVoteProgressForTokens).should('contain.text', '1.00');
|
||||
// Checking vote status for different public keys is displayed correctly
|
||||
cy.contains('You voted: For').should('be.visible');
|
||||
});
|
||||
switchVegaWalletPubKey();
|
||||
stakingPageDisassociateAllTokens();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '../../support/proposal.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const closedProposals = '[data-testid="closed-proposals"]';
|
||||
@@ -89,8 +89,14 @@ context(
|
||||
});
|
||||
cy.get(proposalStatus).should('have.text', 'Open');
|
||||
voteForProposal('for');
|
||||
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Passed');
|
||||
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
|
||||
cy.get(proposalStatus, proposalTimeout)
|
||||
.should('have.text', 'Passed')
|
||||
.then(() => {
|
||||
cy.get(proposalStatus, proposalTimeout).should(
|
||||
'have.text',
|
||||
'Enacted'
|
||||
);
|
||||
});
|
||||
cy.get(votesTable).within(() => {
|
||||
cy.contains('Vote passed.').should('be.visible');
|
||||
cy.contains('Voting has ended.').should('be.visible');
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
import {
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
} from '../../support/wallet-functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||
|
||||
@@ -130,7 +130,7 @@ context(
|
||||
createRawProposal();
|
||||
});
|
||||
|
||||
it.skip('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
|
||||
it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
|
||||
goToMakeNewProposal(governanceProposalType.FREEFORM);
|
||||
enterUniqueFreeFormProposalBody('0.1', generateFreeFormProposalTitle());
|
||||
cy.get('input:invalid')
|
||||
@@ -138,7 +138,7 @@ context(
|
||||
.should('equal', 'Value must be greater than or equal to 1.');
|
||||
});
|
||||
|
||||
it.skip('Creating a proposal - proposal rejected - when closing time later than system default', function () {
|
||||
it('Creating a proposal - proposal rejected - when closing time later than system default', function () {
|
||||
goToMakeNewProposal(governanceProposalType.FREEFORM);
|
||||
enterUniqueFreeFormProposalBody(
|
||||
'100000',
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
getDownloadedProposalJsonPath,
|
||||
getProposalFromTitle,
|
||||
submitUniqueRawProposal,
|
||||
} from '../../support/governance.functions';
|
||||
import {
|
||||
@@ -23,10 +24,11 @@ import {
|
||||
} from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import {
|
||||
switchVegaWalletPubKey,
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
|
||||
} from '../../support/wallet-functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const openProposals = '[data-testid="open-proposals"]';
|
||||
@@ -54,7 +56,8 @@ const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalTermsSection = 'proposal';
|
||||
const proposalJsonToggle = 'proposal-json-toggle';
|
||||
const proposalJsonSection = 'proposal-json';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const fUSDCId =
|
||||
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc';
|
||||
@@ -185,7 +188,7 @@ context(
|
||||
cy.get(maxVoteDeadline).click();
|
||||
cy.get(enactmentDeadlineError).should(
|
||||
'have.text',
|
||||
'Proposal will fail if enactment is earlier than the voting deadline'
|
||||
'The proposal will fail if enactment is earlier than the voting deadline'
|
||||
);
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
@@ -209,6 +212,7 @@ context(
|
||||
'Able to submit valid new market proposal',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
const proposalTitle = 'Test new market proposal';
|
||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -230,6 +234,23 @@ context(
|
||||
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
|
||||
});
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
getProposalFromTitle(proposalTitle).within(() =>
|
||||
cy.getByTestId('view-proposal-btn').click()
|
||||
);
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.getByTestId('proposal-market-data-toggle').click();
|
||||
cy.contains('Key details').click();
|
||||
getMarketProposalDetailsFromTable('Name').should(
|
||||
'have.text',
|
||||
'Token test market'
|
||||
);
|
||||
cy.contains('Settlement asset').click();
|
||||
// Settlement asset symbol
|
||||
cy.getByTestId('3_value').should('have.text', 'fBTC');
|
||||
cy.contains('Oracle').click();
|
||||
cy.getByTestId('oracle-spec-links').should('have.attr', 'href');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -269,8 +290,7 @@ 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
|
||||
switchVegaWalletPubKey();
|
||||
stakingPageAssociateTokens('1');
|
||||
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
@@ -300,8 +320,7 @@ context(
|
||||
closeDialog();
|
||||
ethereumWalletConnect();
|
||||
stakingPageDisassociateAllTokens();
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
switchVegaWalletPubKey();
|
||||
});
|
||||
|
||||
// 3002-PROP-020
|
||||
@@ -455,8 +474,8 @@ context(
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
cy.getByTestId('proposal-terms-toggle').click();
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.getByTestId(proposalJsonSection).within(() => {
|
||||
cy.contains('USDT Coin').should('be.visible');
|
||||
cy.contains('USDT').should('be.visible');
|
||||
});
|
||||
@@ -502,13 +521,11 @@ context(
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
// 3001-VOTE-030 3001-VOTE-031
|
||||
cy.getByTestId('proposal-terms-toggle').click();
|
||||
cy.getByTestId('proposal-terms').within(() => {
|
||||
getProposalInformationFromTable('assetId').should('have.text', assetId);
|
||||
getProposalInformationFromTable('lifetimeLimit').should(
|
||||
'have.text',
|
||||
'10'
|
||||
);
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.getByTestId(proposalJsonSection).within(() => {
|
||||
cy.contains(assetId).should('be.visible');
|
||||
cy.contains('lifetimeLimit').should('be.visible');
|
||||
cy.contains('10').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -606,5 +623,13 @@ context(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getMarketProposalDetailsFromTable(heading: string) {
|
||||
return cy
|
||||
.getByTestId('key-value-table-row')
|
||||
.contains(heading)
|
||||
.parent()
|
||||
.siblings();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '../../support/governance.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
|
||||
|
||||
const proposalListItem = 'proposals-list-item';
|
||||
const openProposals = '[data-testid="open-proposals"]';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import {
|
||||
depositAsset,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
} from '../../support/wallet-functions';
|
||||
|
||||
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
|
||||
const vegaWalletUnstakedBalance =
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import {
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
} from '../../support/wallet-functions';
|
||||
const stakeValidatorListTotalStake = 'total-stake';
|
||||
const stakeValidatorListTotalShare = 'total-stake-share';
|
||||
const stakeValidatorListStakePercentage = 'stake-percentage';
|
||||
|
||||
@@ -14,11 +14,12 @@ import {
|
||||
} from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import {
|
||||
switchVegaWalletPubKey,
|
||||
vegaWalletAssociate,
|
||||
vegaWalletDisassociate,
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-teardown.functions';
|
||||
} from '../../support/wallet-functions';
|
||||
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
@@ -301,8 +302,7 @@ context(
|
||||
Cypress.env('vegaWalletPublicKey')
|
||||
);
|
||||
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
switchVegaWalletPubKey();
|
||||
cy.get(connectedVegaKey).should(
|
||||
'have.text',
|
||||
Cypress.env('vegaWalletPublicKey2')
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
waitForSpinner,
|
||||
} from '../../support/common.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { depositAsset } from '../../support/wallet-teardown.functions';
|
||||
import { depositAsset } from '../../support/wallet-functions';
|
||||
|
||||
const withdraw = 'withdraw';
|
||||
const withdrawalForm = 'withdraw-form';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
goToMakeNewProposal,
|
||||
governanceProposalType,
|
||||
} from '../../support/governance.functions';
|
||||
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
|
||||
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-functions';
|
||||
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey2');
|
||||
const vegaPubkeyTruncated = Cypress.env('vegaWalletPublicKey2Short');
|
||||
|
||||
@@ -30,7 +30,6 @@ const normalisedVotingPowerToolTip =
|
||||
'[data-testid="normalised-voting-power-tooltip"]';
|
||||
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
|
||||
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
|
||||
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
|
||||
const epochCountDown = '[data-testid="epoch-countdown"]';
|
||||
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
|
||||
|
||||
@@ -143,9 +142,6 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.get(overstakedPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
|
||||
cy.get(totalPenaltyToolTip)
|
||||
.invoke('text')
|
||||
.should('contain', 'Total penalties: 60.00%');
|
||||
});
|
||||
|
||||
it('Should be able to see validator pending stake', function () {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { waitForSpinner } from '../../support/common.functions';
|
||||
import { vegaWalletTeardown } from '../../support/wallet-teardown.functions';
|
||||
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
|
||||
import {
|
||||
vegaWalletFaucetAssetsWithoutCheck,
|
||||
vegaWalletTeardown,
|
||||
} from '../../support/wallet-functions';
|
||||
|
||||
const walletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
@@ -280,13 +282,6 @@ context(
|
||||
// 2002-SINC-016
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
},
|
||||
{
|
||||
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
|
||||
name: 'DAI (fake)',
|
||||
@@ -308,6 +303,13 @@ context(
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
{
|
||||
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
name: 'USDC (fake)',
|
||||
symbol: 'fUSDC',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
},
|
||||
];
|
||||
|
||||
before('faucet assets to connected vega wallet', function () {
|
||||
|
||||
@@ -5,8 +5,7 @@ import './common.functions.ts';
|
||||
import './staking.functions.ts';
|
||||
import './governance.functions.ts';
|
||||
import './wallet-eth.functions.ts';
|
||||
import './wallet-teardown.functions.ts';
|
||||
import './wallet-vega.functions.ts';
|
||||
import './wallet-functions.ts';
|
||||
import './proposal.functions.ts';
|
||||
import 'cypress-mochawesome-reporter/register';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { closeDialog } from './common.functions';
|
||||
import { vegaWalletTeardown } from './wallet-teardown.functions';
|
||||
import { vegaWalletTeardown } from './wallet-functions';
|
||||
|
||||
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
|
||||
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
|
||||
+24
@@ -173,3 +173,27 @@ export async function vegaWalletDisassociate(amount: string) {
|
||||
amount = amount + '0'.repeat(18);
|
||||
stakingBridgeContract.remove_stake(amount, vegaWalletPubKey);
|
||||
}
|
||||
|
||||
export function vegaWalletFaucetAssetsWithoutCheck(
|
||||
asset: string,
|
||||
amount: string,
|
||||
vegaWalletPublicKey: string
|
||||
) {
|
||||
cy.highlight(`Topping up vega wallet with ${asset}, amount: ${amount}`);
|
||||
cy.exec(
|
||||
`curl -X POST -d '{"amount": "${amount}", "asset": "${asset}", "party": "${vegaWalletPublicKey}"}' http://localhost:1790/api/v1/mint`
|
||||
)
|
||||
.its('stdout')
|
||||
.then((response) => {
|
||||
assert.include(
|
||||
response,
|
||||
`"success":true`,
|
||||
'Ensuring curl command was successfully undertaken'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function switchVegaWalletPubKey() {
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export function vegaWalletFaucetAssetsWithoutCheck(
|
||||
asset: string,
|
||||
amount: string,
|
||||
vegaWalletPublicKey: string
|
||||
) {
|
||||
cy.highlight(`Topping up vega wallet with ${asset}, amount: ${amount}`);
|
||||
cy.exec(
|
||||
`curl -X POST -d '{"amount": "${amount}", "asset": "${asset}", "party": "${vegaWalletPublicKey}"}' http://localhost:1790/api/v1/mint`
|
||||
)
|
||||
.its('stdout')
|
||||
.then((response) => {
|
||||
assert.include(
|
||||
response,
|
||||
`"success":true`,
|
||||
'Ensuring curl command was successfully undertaken'
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -596,6 +596,8 @@
|
||||
"noPercentage": "No percentage",
|
||||
"proposalJson": "Full proposal JSON",
|
||||
"proposalDetails": "Proposal details",
|
||||
"marketSpecification": "Market specification",
|
||||
"viewMarketJson": "View market JSON",
|
||||
"proposalDescription": "Description",
|
||||
"currentlySetTo": "Currently expected to ",
|
||||
"currently": "currently",
|
||||
@@ -729,7 +731,11 @@
|
||||
"ThisWillSetValidationDeadlineTo": "This will set the validation deadline to",
|
||||
"Hours": "hours",
|
||||
"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",
|
||||
"ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline": "The proposal will fail if enactment is earlier than the voting deadline",
|
||||
"ProposalWillFailIfEnactmentIsBelowTheMinimumDeadline": "The proposal will fail if enactment deadline is below the minimum",
|
||||
"ProposalWillFailIfVotingIsBelowTheMinimumDeadline": "The proposal will fail if voting deadline is below the minimum",
|
||||
"ProposalWillFailIfEnactmentIsAboveTheMaximumDeadline": "The proposal will fail if enactment deadline is above the maximum",
|
||||
"ProposalWillFailIfVotingIsAboveTheMaximumDeadline": "The proposal will fail if voting deadline is above the maximum",
|
||||
"SelectAMarketToChange": "Select a market to change",
|
||||
"MarketName": "Market name",
|
||||
"MarketCode": "Market code",
|
||||
@@ -780,6 +786,7 @@
|
||||
"performancePenalty": "Performance penalty",
|
||||
"overstaked": "Overstaked",
|
||||
"overstakedPenalty": "Overstaked penalty",
|
||||
"multisigPenalty": "Multisig penalty",
|
||||
"homeProposalsIntro": "Decisions on the Vega network are on-chain, with tokenholders creating proposals that other tokenholders vote to approve or reject. Network upgrades are proposed and approved by validators.",
|
||||
"homeProposalsButtonText": "Browse, vote, and propose",
|
||||
"homeValidatorsIntro": "Vega runs on a delegated proof of stake blockchain, where validators earn fees for validating block transactions. Tokenholders can nominate validators by staking tokens to them.",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
|
||||
export const collapsibleToggleStyles = (toggleState: boolean) =>
|
||||
classnames('mb-4 transition-transform ease-in-out duration-300', {
|
||||
'rotate-180': toggleState,
|
||||
});
|
||||
+2
-5
@@ -1,9 +1,9 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import classnames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
|
||||
|
||||
export const ProposalDescription = ({
|
||||
description,
|
||||
@@ -12,9 +12,6 @@ export const ProposalDescription = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
const showDescriptionIconClasses = classnames('mb-4', {
|
||||
'rotate-180': showDescription,
|
||||
});
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-description">
|
||||
@@ -24,7 +21,7 @@ export const ProposalDescription = ({
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('proposalDescription')} />
|
||||
<div className={showDescriptionIconClasses}>
|
||||
<div className={collapsibleToggleStyles(showDescription)}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import classnames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
@@ -13,9 +13,6 @@ export const ProposalJson = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const showDetailsIconClasses = classnames('mb-4', {
|
||||
'rotate-180': showDetails,
|
||||
});
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-json">
|
||||
@@ -25,7 +22,7 @@ export const ProposalJson = ({
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('proposalJson')} />
|
||||
<div className={showDetailsIconClasses}>
|
||||
<div className={collapsibleToggleStyles(showDetails)}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './proposal-market-data';
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
InstrumentInfoPanel,
|
||||
KeyDetailsInfoPanel,
|
||||
LiquidityMonitoringParametersInfoPanel,
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
MetadataInfoPanel,
|
||||
OracleInfoPanel,
|
||||
PriceMonitoringBoundsInfoPanel,
|
||||
RiskFactorsInfoPanel,
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
Dialog,
|
||||
Icon,
|
||||
SyntaxHighlighter,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import { create } from 'zustand';
|
||||
|
||||
type MarketDataDialogState = {
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
export const useMarketDataDialogStore = create<MarketDataDialogState>(
|
||||
(set) => ({
|
||||
isOpen: false,
|
||||
open: () => set({ isOpen: true }),
|
||||
close: () => set({ isOpen: false }),
|
||||
})
|
||||
);
|
||||
|
||||
export const ProposalMarketData = ({
|
||||
marketData,
|
||||
}: {
|
||||
marketData: MarketInfoWithData;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, open, close } = useMarketDataDialogStore();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
if (!marketData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const settlementData = marketData.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
|
||||
const terminationData = marketData.tradableInstrument.instrument.product
|
||||
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
|
||||
|
||||
const getSigners = (data: DataSourceDefinition) => {
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
|
||||
return signers.map(({ signer }) => {
|
||||
return (
|
||||
(signer.__typename === 'ETHAddress' && signer.address) ||
|
||||
(signer.__typename === 'PubKey' && signer.key)
|
||||
);
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative" data-testid="proposal-market-data">
|
||||
<button
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
data-testid="proposal-market-data-toggle"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('marketSpecification')} />
|
||||
<div className={collapsibleToggleStyles(showDetails)}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showDetails && (
|
||||
<>
|
||||
<div className="float-right">
|
||||
<Button onClick={open} data-testid="view-market-json">
|
||||
{t('viewMarketJson')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<Accordion>
|
||||
<AccordionItem
|
||||
itemId="key-details"
|
||||
title={t('Key details')}
|
||||
content={<KeyDetailsInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="instrument"
|
||||
title={t('Instrument')}
|
||||
content={<InstrumentInfoPanel market={marketData} />}
|
||||
/>
|
||||
{isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
) ? (
|
||||
<AccordionItem
|
||||
itemId="oracles"
|
||||
title={t('Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<AccordionItem
|
||||
itemId="settlement-oracle"
|
||||
title={t('Settlement Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<AccordionItem
|
||||
itemId="termination-oracle"
|
||||
title={t('Termination Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel market={marketData} type="termination" />
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<AccordionItem
|
||||
itemId="settlement-asset"
|
||||
title={t('Settlement asset')}
|
||||
content={<SettlementAssetInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="metadata"
|
||||
title={t('Metadata')}
|
||||
content={<MetadataInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-model"
|
||||
title={t('Risk model')}
|
||||
content={<RiskModelInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-parameters"
|
||||
title={t('Risk parameters')}
|
||||
content={<RiskParametersInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-factors"
|
||||
title={t('Risk factors')}
|
||||
content={<RiskFactorsInfoPanel market={marketData} />}
|
||||
/>
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<AccordionItem
|
||||
itemId={`trigger-${triggerIndex}`}
|
||||
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
content={
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<AccordionItem
|
||||
itemId="liqudity-monitoring-parameters"
|
||||
title={t('Liquidity monitoring parameters')}
|
||||
content={
|
||||
<LiquidityMonitoringParametersInfoPanel market={marketData} />
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="liquidity-price-range"
|
||||
title={t('Liquidity price range')}
|
||||
content={<LiquidityPriceRangeInfoPanel market={marketData} />}
|
||||
/>
|
||||
</Accordion>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
title={marketData.tradableInstrument.instrument.code}
|
||||
open={isOpen}
|
||||
onChange={(isOpen) => (isOpen ? open() : close())}
|
||||
size="medium"
|
||||
dataTestId="market-json-dialog"
|
||||
>
|
||||
<CopyWithTooltip text={JSON.stringify(marketData)}>
|
||||
<button className="bg-vega-dark-100 rounded-sm py-2 px-3 mb-4 text-white">
|
||||
<span>
|
||||
<Icon name="duplicate" />
|
||||
</span>
|
||||
<span className="ml-2">Copy</span>
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
<SyntaxHighlighter data={marketData} />
|
||||
</Dialog>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
+2
-6
@@ -1,4 +1,3 @@
|
||||
import classnames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -13,6 +12,7 @@ import { SubHeading } from '../../../../components/heading';
|
||||
import { useVoteInformation } from '../../hooks';
|
||||
import { useAppState } from '../../../../contexts/app-state/app-state-context';
|
||||
import { ProposalType } from '../proposal/proposal';
|
||||
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
@@ -57,10 +57,6 @@ export const ProposalVotesTable = ({
|
||||
? t('byTokenVote')
|
||||
: t('byLiquidityVote');
|
||||
|
||||
const showDetailsIconClasses = classnames('mb-4', {
|
||||
'rotate-180': showDetails,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
@@ -69,7 +65,7 @@ export const ProposalVotesTable = ({
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('voteBreakdown')} />
|
||||
<div className={showDetailsIconClasses}>
|
||||
<div className={collapsibleToggleStyles(showDetails)}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,9 +29,6 @@ jest.mock('../proposal-change-table', () => ({
|
||||
jest.mock('../proposal-json', () => ({
|
||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-terms/proposal-terms', () => ({
|
||||
ProposalTerms: () => <div data-testid="proposal-terms"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-votes-table', () => ({
|
||||
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
|
||||
}));
|
||||
@@ -74,7 +71,6 @@ it('renders each section', async () => {
|
||||
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-terms')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { AsyncRenderer, Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalTerms } from '../proposal-terms';
|
||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
||||
import { VoteDetails } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Routes from '../../../routes';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
|
||||
export enum ProposalType {
|
||||
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
|
||||
@@ -28,11 +28,16 @@ export enum ProposalType {
|
||||
}
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
newMarketData?: MarketInfoWithData | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: any;
|
||||
}
|
||||
|
||||
export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
export const Proposal = ({
|
||||
proposal,
|
||||
restData,
|
||||
newMarketData,
|
||||
}: ProposalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { params, loading, error } = useNetworkParams([
|
||||
NetworkParams.governance_proposal_market_minVoterBalance,
|
||||
@@ -97,51 +102,58 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
</div>
|
||||
<ProposalHeader proposal={proposal} isListItem={false} />
|
||||
|
||||
<div className="my-10">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
<div id="details">
|
||||
<div className="my-10">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
<ListAsset
|
||||
assetId={proposal.id}
|
||||
withdrawalThreshold={
|
||||
proposal.terms.change.source.withdrawThreshold
|
||||
}
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename !== 'NewMarket' &&
|
||||
proposal.terms.change.__typename !== 'UpdateMarket' &&
|
||||
proposal.terms.change.__typename !== 'NewFreeform' && (
|
||||
{newMarketData && (
|
||||
<div className="mb-4">
|
||||
<ProposalTerms data={proposal.terms} />
|
||||
<ProposalMarketData marketData={newMarketData} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={restData?.data?.proposal} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<VoteDetails
|
||||
<div id="voting">
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<VoteDetails
|
||||
proposal={proposal}
|
||||
proposalType={proposalType}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
params?.spam_protection_voting_min_tokens
|
||||
}
|
||||
/>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalVotesTable
|
||||
proposal={proposal}
|
||||
proposalType={proposalType}
|
||||
minVoterBalance={minVoterBalance}
|
||||
spamProtectionMinTokens={
|
||||
params?.spam_protection_voting_min_tokens
|
||||
}
|
||||
/>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</AsyncRenderer>
|
||||
|
||||
+41
-1
@@ -224,7 +224,47 @@ describe('Proposal form vote, validation and enactment deadline', () => {
|
||||
expect(
|
||||
screen.getByTestId('enactment-before-voting-deadline')
|
||||
).toHaveTextContent(
|
||||
'Proposal will fail if enactment is earlier than the voting deadline'
|
||||
'The proposal will fail if enactment is earlier than the voting deadline'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays error text if the vote deadline is set earlier than the minimum allowed', () => {
|
||||
renderComponent();
|
||||
const voteDeadlineInput = screen.getByTestId('proposal-vote-deadline');
|
||||
fireEvent.change(voteDeadlineInput, { target: { value: 0.01 } });
|
||||
expect(screen.getByTestId('voting-less-than-min')).toHaveTextContent(
|
||||
'The proposal will fail if voting deadline is below the minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays error text if the vote deadline is set later than the maximum allowed', () => {
|
||||
renderComponent();
|
||||
const voteDeadlineInput = screen.getByTestId('proposal-vote-deadline');
|
||||
fireEvent.change(voteDeadlineInput, { target: { value: 100000 } });
|
||||
expect(screen.getByTestId('voting-greater-than-max')).toHaveTextContent(
|
||||
'The proposal will fail if voting deadline is above the maximum'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays error text if the enactment deadline is set earlier than the minimum allowed', () => {
|
||||
renderComponent();
|
||||
const enactmentDeadlineInput = screen.getByTestId(
|
||||
'proposal-enactment-deadline'
|
||||
);
|
||||
fireEvent.change(enactmentDeadlineInput, { target: { value: 0.01 } });
|
||||
expect(screen.getByTestId('enactment-less-than-min')).toHaveTextContent(
|
||||
'The proposal will fail if enactment deadline is below the minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('displays error text if the enactment deadline is set later than the maximum allowed', () => {
|
||||
renderComponent();
|
||||
const enactmentDeadlineInput = screen.getByTestId(
|
||||
'proposal-enactment-deadline'
|
||||
);
|
||||
fireEvent.change(enactmentDeadlineInput, { target: { value: 100000 } });
|
||||
expect(screen.getByTestId('enactment-greater-than-max')).toHaveTextContent(
|
||||
'The proposal will fail if enactment deadline is above the maximum'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+62
-22
@@ -220,21 +220,41 @@ const EnactmentForm = ({
|
||||
<span data-testid="enactment-date" className="pl-2">
|
||||
{getDateTimeFormat().format(deadlineDates.enactment)}
|
||||
</span>
|
||||
{deadlines.enactment === minEnactmentHours && (
|
||||
<span
|
||||
data-testid="enactment-2-mins-extra"
|
||||
className="block mt-4 font-light"
|
||||
>
|
||||
{t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.enactment && deadlines.enactment < deadlines.vote && (
|
||||
<span
|
||||
data-testid="enactment-before-voting-deadline"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline')}
|
||||
</span>
|
||||
{deadlines.enactment && (
|
||||
<>
|
||||
{deadlines.enactment === minEnactmentHours && (
|
||||
<span
|
||||
data-testid="enactment-2-mins-extra"
|
||||
className="block mt-4 font-light"
|
||||
>
|
||||
{t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.enactment < deadlines.vote && (
|
||||
<span
|
||||
data-testid="enactment-before-voting-deadline"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.enactment < minEnactmentHours && (
|
||||
<span
|
||||
data-testid="enactment-less-than-min"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfEnactmentIsBelowTheMinimumDeadline')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.enactment > maxEnactmentHours && (
|
||||
<span
|
||||
data-testid="enactment-greater-than-max"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfEnactmentIsAboveTheMaximumDeadline')}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
@@ -500,13 +520,33 @@ export function ProposalFormVoteAndEnactmentDeadline({
|
||||
<span data-testid="voting-date" className="pl-2">
|
||||
{getDateTimeFormat().format(deadlineDates.vote)}
|
||||
</span>
|
||||
{deadlines.vote === minVoteHours && (
|
||||
<span
|
||||
data-testid="voting-2-mins-extra"
|
||||
className="block mt-4 font-light"
|
||||
>
|
||||
{t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
|
||||
</span>
|
||||
{deadlines.vote && (
|
||||
<>
|
||||
{deadlines.vote === minVoteHours && (
|
||||
<span
|
||||
data-testid="voting-2-mins-extra"
|
||||
className="block mt-4 font-light"
|
||||
>
|
||||
{t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.vote < minVoteHours && (
|
||||
<span
|
||||
data-testid="voting-less-than-min"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfVotingIsBelowTheMinimumDeadline')}
|
||||
</span>
|
||||
)}
|
||||
{deadlines.vote > maxVoteHours && (
|
||||
<span
|
||||
data-testid="voting-greater-than-max"
|
||||
className="block mt-4 text-vega-pink"
|
||||
>
|
||||
{t('ProposalWillFailIfVotingIsAboveTheMaximumDeadline')}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { captureMessage } from '@sentry/minimal';
|
||||
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { VoteValue } from '@vegaprotocol/types';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUserVoteQuery } from './__generated__/Vote';
|
||||
import type { FinalizedVote } from '@vegaprotocol/proposals';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import type { FinalizedVote } from '@vegaprotocol/proposals';
|
||||
|
||||
export enum VoteState {
|
||||
NotCast = 'NotCast',
|
||||
@@ -45,21 +44,23 @@ export function useUserVote(
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizedVote?.vote.value) {
|
||||
if (finalizedVote?.vote.value && finalizedVote.pubKey === pubKey) {
|
||||
setUserVote(finalizedVote);
|
||||
} else if (data?.party?.votesConnection?.edges) {
|
||||
// This sets the vote (if any) when the user first loads the page
|
||||
setUserVote(
|
||||
removePaginationWrapper(data?.party?.votesConnection?.edges).find(
|
||||
({ proposalId: pId }) => proposalId === pId
|
||||
)
|
||||
);
|
||||
} else if (data?.party?.votesConnection?.edges && pubKey) {
|
||||
const vote = removePaginationWrapper(
|
||||
data?.party?.votesConnection?.edges
|
||||
).find(({ proposalId: pId }) => proposalId === pId);
|
||||
|
||||
if (vote) {
|
||||
setUserVote({ ...vote, pubKey });
|
||||
}
|
||||
}
|
||||
}, [
|
||||
finalizedVote?.vote.value,
|
||||
data?.party?.votesConnection?.edges,
|
||||
finalizedVote,
|
||||
proposalId,
|
||||
pubKey,
|
||||
]);
|
||||
|
||||
// If user vote changes update the vote state
|
||||
@@ -94,6 +95,8 @@ export function useUserVote(
|
||||
return {
|
||||
voteState,
|
||||
userVote,
|
||||
voteDatetime: userVote ? new Date(userVote.vote.datetime) : null,
|
||||
voteDatetime: userVote?.vote?.datetime
|
||||
? new Date(userVote.vote.datetime)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ProposalNotFound } from '../components/proposal-not-found';
|
||||
import { useProposalQuery } from './__generated__/Proposal';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { ENV } from '../../../config';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
|
||||
|
||||
export const ProposalContainer = () => {
|
||||
const params = useParams<{ proposalId: string }>();
|
||||
@@ -20,15 +22,36 @@ export const ProposalContainer = () => {
|
||||
skip: !params.proposalId,
|
||||
});
|
||||
|
||||
const {
|
||||
data: newMarketData,
|
||||
loading: newMarketLoading,
|
||||
error: newMarketError,
|
||||
} = useDataProvider({
|
||||
dataProvider: marketInfoWithDataProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: data?.proposal?.id || '',
|
||||
skip: !data?.proposal?.id,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(refetch, 1000);
|
||||
const interval = setInterval(refetch, 2000);
|
||||
return () => clearInterval(interval);
|
||||
}, [refetch]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<AsyncRenderer
|
||||
loading={loading || newMarketLoading}
|
||||
error={error || newMarketError}
|
||||
data={newMarketData ? { newMarketData, data } : data}
|
||||
>
|
||||
{data?.proposal ? (
|
||||
<Proposal proposal={data.proposal} restData={restData} />
|
||||
<Proposal
|
||||
proposal={data.proposal}
|
||||
restData={restData}
|
||||
newMarketData={newMarketData}
|
||||
/>
|
||||
) : (
|
||||
<ProposalNotFound />
|
||||
)}
|
||||
|
||||
+8
@@ -36,6 +36,7 @@ import {
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { VALIDATOR_LOGO_MAP } from './logo-map';
|
||||
import { getMultisigStatusInfo } from '../../../../lib/get-multisig-status-info';
|
||||
|
||||
interface CanonisedConsensusNodeProps {
|
||||
id: string;
|
||||
@@ -137,6 +138,10 @@ export const ConsensusValidatorsTable = ({
|
||||
[totalStake]
|
||||
);
|
||||
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
|
||||
const allNodesInPreviousEpoch = removePaginationWrapper(
|
||||
previousEpochData?.epoch.validatorsConnection?.edges
|
||||
);
|
||||
@@ -223,6 +228,8 @@ export const ConsensusValidatorsTable = ({
|
||||
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare), 2)
|
||||
: undefined,
|
||||
[ValidatorFields.MULTISIG_ERROR]:
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -332,6 +339,7 @@ export const ConsensusValidatorsTable = ({
|
||||
data,
|
||||
decimals,
|
||||
hideTopThird,
|
||||
multisigStatus?.showMultisigStatusError,
|
||||
previousEpochData,
|
||||
thirdOfTotalStake,
|
||||
validatorsView,
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum ValidatorFields {
|
||||
STAKED_BY_USER = 'stakedByUser',
|
||||
PENDING_USER_STAKE = 'pendingUserStake',
|
||||
USER_STAKE_SHARE = 'userStakeShare',
|
||||
MULTISIG_ERROR = 'multisigError',
|
||||
}
|
||||
|
||||
export const addUserDataToValidator = (
|
||||
@@ -326,6 +327,7 @@ interface TotalPenaltiesRendererProps {
|
||||
overstakedAmount: string;
|
||||
overstakingPenalty: string;
|
||||
totalPenalties: string;
|
||||
multisigError?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -344,10 +346,11 @@ export const TotalPenaltiesRenderer = ({
|
||||
<div data-testid="overstaked-penalty-tooltip">
|
||||
{t('overstakedPenalty')}: {data.overstakingPenalty}
|
||||
</div>
|
||||
<div data-testid="total-penalty-tooltip">
|
||||
{t('totalPenalties')}:{' '}
|
||||
<span className="font-bold">{data.totalPenalties}</span>
|
||||
</div>
|
||||
{data.multisigError && (
|
||||
<div data-testid="multisig-error-tooltip">
|
||||
{t('multisigPenalty')}: 100%
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -14,6 +14,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_SENTRY_DSN=https://dummy@o999999.ingest.sentry.io/9999999
|
||||
|
||||
# Expose some env vars to cypress environment for market setup
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
describe('charts', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('Depth').click();
|
||||
});
|
||||
|
||||
it('can see market depth chart', () => {
|
||||
// 6006-DEPC-001
|
||||
cy.getByTestId('tab-depth').should('be.visible');
|
||||
cy.get('.depth-chart-module_canvas__260De').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,8 @@
|
||||
const dialogContent = 'dialog-content';
|
||||
const nodeHealth = 'node-health';
|
||||
const statusIncidentsLink = 'footer [data-testid=external-link]';
|
||||
|
||||
describe('home', { tags: '@regression' }, () => {
|
||||
before(() => {
|
||||
cy.clearAllLocalStorage();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
@@ -76,23 +74,14 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
// 0006-NETW-002
|
||||
// 0006-NETW-003
|
||||
// 0006-NETW-011
|
||||
it('switch to fairground network and check status & incidents link', () => {
|
||||
// 0006-NETW-002
|
||||
// 0006-NETW-003
|
||||
cy.getByTestId('navigation')
|
||||
.find('[data-testid="network-switcher"]')
|
||||
.should('have.text', 'Custom')
|
||||
.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');
|
||||
cy.getByTestId('network-item').contains('Fairground testnet');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0');
|
||||
validateMarketDataRow(0, 'Triggering Ratio', '0.7');
|
||||
validateMarketDataRow(1, 'Time Window', '3,600');
|
||||
validateMarketDataRow(2, 'Scaling Factor', '10');
|
||||
});
|
||||
|
||||
@@ -81,6 +81,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.get(rowSelector).first().find(colFee).should('have.text', '0.09%');
|
||||
|
||||
// 5002-LIQP-013
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
@@ -165,7 +166,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('can see liquidity supplied', () => {
|
||||
//// 5002-LIQP-008
|
||||
// 5002-LIQP-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('liquidity-supplied').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
|
||||
@@ -237,6 +238,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
.find(colFee)
|
||||
.should('have.text', '0.09%');
|
||||
|
||||
// 5002-LIQP-013
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
@@ -268,7 +270,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('renders liquidity inactive table correctly', () => {
|
||||
//// 5002-LIQP-012
|
||||
// 5002-LIQP-012
|
||||
cy.getByTestId('Inactive').click();
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
|
||||
@@ -128,6 +128,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
cy.wrap(btn).click();
|
||||
});
|
||||
}
|
||||
cy.contains('Loading...').should('not.exist');
|
||||
});
|
||||
// 7001-COLL-010
|
||||
it('sorting by asset', () => {
|
||||
@@ -146,12 +147,17 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
it('sorting by total', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
'1,000.01',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
@@ -188,19 +194,24 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('sorting by total', () => {
|
||||
it('sorting by available', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
const marketsSortedDefault = [
|
||||
'1,000.00',
|
||||
'1,000.01',
|
||||
'1,000.00002',
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'1,000.00',
|
||||
'1,000.00',
|
||||
'1,000.00001',
|
||||
'1,000.00002',
|
||||
];
|
||||
const marketsSortedAsc = ['1,000.00', '1,000.00', '1,000.00', '1,000.01'];
|
||||
const marketsSortedDesc = Array.from(marketsSortedAsc).reverse();
|
||||
|
||||
checkSorting(
|
||||
'total',
|
||||
'available',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { accountsQuery, amendGeneralAccountBalance } from '@vegaprotocol/mock';
|
||||
import {
|
||||
accountsQuery,
|
||||
amendGeneralAccountBalance,
|
||||
amendMarginAccountBalance,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
|
||||
@@ -12,8 +16,9 @@ describe(
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
const accounts = accountsQuery();
|
||||
amendGeneralAccountBalance(accounts, 'market-0', '0');
|
||||
let accounts = accountsQuery();
|
||||
accounts = amendMarginAccountBalance(accounts, 'market-0', '1000');
|
||||
accounts = amendGeneralAccountBalance(accounts, 'market-0', '0');
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Accounts', accounts);
|
||||
});
|
||||
@@ -23,6 +28,11 @@ describe(
|
||||
});
|
||||
|
||||
it('should show an error if your balance is zero', () => {
|
||||
const accounts = accountsQuery();
|
||||
amendMarginAccountBalance(accounts, 'market-0', '0');
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Accounts', accounts);
|
||||
});
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId('place-order').should('be.enabled');
|
||||
// 7002-SORD-003
|
||||
@@ -40,8 +50,9 @@ describe(
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
const accounts = accountsQuery();
|
||||
amendGeneralAccountBalance(accounts, 'market-0', '1');
|
||||
let accounts = accountsQuery();
|
||||
accounts = amendMarginAccountBalance(accounts, 'market-0', '1000');
|
||||
accounts = amendGeneralAccountBalance(accounts, 'market-0', '1');
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Accounts', accounts);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
toggleMarket,
|
||||
} from '../support/deal-ticket';
|
||||
|
||||
const tooltipContent = 'tooltip-content';
|
||||
const reduceOnly = 'reduce-only';
|
||||
const postOnly = 'post-only';
|
||||
|
||||
describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setVegaWallet();
|
||||
@@ -122,13 +126,18 @@ describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-026
|
||||
|
||||
it(`post and reduce order market for ${tif.code}`, function () {
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-056
|
||||
// 7003-SORD-057
|
||||
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
cy.getByTestId('post-only').should('be.disabled');
|
||||
cy.getByTestId('reduce-only').should('be.enabled');
|
||||
cy.getByTestId(postOnly).should('be.disabled');
|
||||
cy.getByTestId(reduceOnly).should('be.enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -144,14 +153,33 @@ describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
|
||||
validTIFLimit.forEach((tif) => {
|
||||
it(`post and reduce order for limit ${tif.code}`, function () {
|
||||
// 7003-SORD-054
|
||||
// 7003-SORD-055
|
||||
// 7003-SORD-056
|
||||
// 7003-SORD-057
|
||||
cy.getByTestId(orderTIFDropDown).select(tif.value);
|
||||
cy.get(`[data-testid=${orderTIFDropDown}] option:selected`).should(
|
||||
'have.text',
|
||||
tif.text
|
||||
);
|
||||
cy.getByTestId('post-only').should('be.enabled');
|
||||
cy.getByTestId('reduce-only').should('be.disabled');
|
||||
cy.getByTestId(postOnly).should('be.enabled');
|
||||
cy.getByTestId(reduceOnly).should('be.disabled');
|
||||
});
|
||||
});
|
||||
it(`can see explanation of what post only and reduce only is/does`, function () {
|
||||
// 7003-SORD-058
|
||||
cy.get('[for="post-only"]').should('have.text', 'Post only').realHover();
|
||||
cy.getByTestId(tooltipContent).should(
|
||||
'contain.text',
|
||||
`"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.`
|
||||
);
|
||||
cy.get('[for="reduce-only"]')
|
||||
.should('have.text', 'Reduce only')
|
||||
.realHover();
|
||||
cy.getByTestId(tooltipContent).should(
|
||||
'contain.text',
|
||||
`"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,79 @@
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
});
|
||||
const colHeader = '.ag-header-cell-text';
|
||||
const colIdPrice = '[col-id=price]';
|
||||
const colIdSize = '[col-id=size]';
|
||||
const colIdCreatedAt = '[col-id=createdAt]';
|
||||
const tradesTab = 'Trades';
|
||||
const tradesTable = 'tab-trades';
|
||||
|
||||
describe('trades', { tags: '@smoke' }, () => {
|
||||
const colIdPrice = 'price';
|
||||
const colIdSize = 'size';
|
||||
const colIdCreatedAt = 'createdAt';
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
});
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(tradesTab).click();
|
||||
});
|
||||
|
||||
it('renders trades', () => {
|
||||
cy.getByTestId('Trades').click();
|
||||
cy.getByTestId('tab-trades').should('be.visible');
|
||||
it('show trades', () => {
|
||||
// 6005-THIS-001
|
||||
// 6005-THIS-002
|
||||
cy.getByTestId(tradesTab).should('be.visible');
|
||||
cy.getByTestId(tradesTable).should('be.visible');
|
||||
cy.getByTestId(tradesTable).should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get(`[col-id=${colIdPrice}]`).each(($tradePrice) => {
|
||||
it('show trades prices', () => {
|
||||
// 6005-THIS-003
|
||||
cy.get(`${colIdPrice} ${colHeader}`).first().should('have.text', 'Price');
|
||||
cy.get(colIdPrice).each(($tradePrice) => {
|
||||
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.get(`[col-id=${colIdSize}]`).each(($tradeSize) => {
|
||||
});
|
||||
|
||||
it('show trades sizes', () => {
|
||||
// 6005-THIS-004
|
||||
cy.get(`${colIdSize} ${colHeader}`).first().should('have.text', 'Size');
|
||||
cy.get(colIdSize).each(($tradeSize) => {
|
||||
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('show trades date and time', () => {
|
||||
// 6005-THIS-005
|
||||
cy.get(`${colIdCreatedAt} ${colHeader}`).should('have.text', 'Created at');
|
||||
const dateTimeRegex =
|
||||
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
cy.get(`[col-id=${colIdCreatedAt}]`).each(($tradeDateTime, index) => {
|
||||
cy.get(colIdCreatedAt).each(($tradeDateTime, index) => {
|
||||
if (index != 0) {
|
||||
//ignore header
|
||||
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('trades are sorted descending by datetime', () => {
|
||||
// 6005-THIS-006
|
||||
const dateTimes: Date[] = [];
|
||||
cy.get(colIdCreatedAt)
|
||||
.each(($tradeDateTime, index) => {
|
||||
if (index != 0) {
|
||||
//ignore header
|
||||
dateTimes.push(new Date($tradeDateTime.text()));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
expect(dateTimes).to.deep.equal(
|
||||
dateTimes.sort((a, b) => b.getTime() - a.getTime())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('copy price to deal ticket form', () => {
|
||||
// 6005-THIS-007
|
||||
cy.get(colIdPrice).last().click();
|
||||
cy.getByTestId('order-price').should('have.value', '171.16898');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://eth-mainnet.gateway.pokt.network/v1/lb/af6a2d529a11f8158bc8ca2a
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
|
||||
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
|
||||
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
|
||||
@@ -2,7 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_TRADING_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks
|
||||
|
||||
@@ -83,6 +83,8 @@ export const LiquidityContainer = ({
|
||||
|
||||
const assetDecimalPlaces =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
|
||||
const quantum =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
|
||||
const symbol =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
@@ -98,6 +100,7 @@ export const LiquidityContainer = ({
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
quantum={quantum}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No data')}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@@ -184,7 +184,6 @@ const MarketList = ({
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
@@ -204,6 +203,29 @@ const MarketList = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface ListItemData {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}
|
||||
|
||||
const ListItem = ({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}: {
|
||||
index: number;
|
||||
style: CSSProperties;
|
||||
data: ListItemData;
|
||||
}) => (
|
||||
<MarketSelectorItem
|
||||
market={data.data[index]}
|
||||
currentMarketId={data.currentMarketId}
|
||||
style={style}
|
||||
onSelect={data.onSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
const List = ({
|
||||
data,
|
||||
loading,
|
||||
@@ -212,28 +234,20 @@ const List = ({
|
||||
onSelect,
|
||||
noItems,
|
||||
currentMarketId,
|
||||
}: {
|
||||
data: MarketMaybeWithDataAndCandles[];
|
||||
}: ListItemData & {
|
||||
loading: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
noItems: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
currentMarketId?: string;
|
||||
}) => {
|
||||
const row = ({ index, style }: { index: number; style: CSSProperties }) => {
|
||||
const market = data[index];
|
||||
|
||||
return (
|
||||
<MarketSelectorItem
|
||||
market={market}
|
||||
currentMarketId={currentMarketId}
|
||||
style={style}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const itemKey = useCallback(
|
||||
(index: number, data: ListItemData) => data.data[index].id,
|
||||
[]
|
||||
);
|
||||
const itemData = useMemo(
|
||||
() => ({ data, onSelect, currentMarketId }),
|
||||
[data, onSelect, currentMarketId]
|
||||
);
|
||||
if (!data || loading) {
|
||||
return (
|
||||
<div style={{ width, height }}>
|
||||
@@ -259,11 +273,13 @@ const List = ({
|
||||
<FixedSizeList
|
||||
className="virtualized-list"
|
||||
itemCount={data.length}
|
||||
itemData={itemData}
|
||||
itemSize={130}
|
||||
itemKey={itemKey}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
{row}
|
||||
{ListItem}
|
||||
</FixedSizeList>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ const MarketBottomPanel = memo(
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
return 'xxxl' === screenSize ? (
|
||||
<ResizableGrid
|
||||
|
||||
@@ -39,7 +39,7 @@ export const TradePanels = ({
|
||||
}: TradePanelsProps) => {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
const renderView = () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const DepositsContainer = () => {
|
||||
<div className="h-full">
|
||||
<div className="h-full relative">
|
||||
<DepositsTable
|
||||
rowData={data || []}
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const Portfolio = () => {
|
||||
}, [updateTitle]);
|
||||
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
||||
return (
|
||||
|
||||
@@ -125,14 +125,19 @@ export const MarketLiquiditySupplied = ({
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
<br />
|
||||
<Link href={`/#/liquidity/${marketId}`} data-testid="view-liquidity-link">
|
||||
{t('View liquidity provision table')}
|
||||
</Link>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY} className="mt-2">
|
||||
{t('Learn about providing liquidity')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Link
|
||||
href={`/#/liquidity/${marketId}`}
|
||||
data-testid="view-liquidity-link"
|
||||
>
|
||||
{t('View liquidity provision table')}
|
||||
</Link>
|
||||
{DocsLinks && (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY} className="mt-2">
|
||||
{t('Learn about providing liquidity')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</div>
|
||||
{showMessage && (
|
||||
<p className="mt-4">
|
||||
{t(
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
jest.mock('@vegaprotocol/logger', () => ({
|
||||
SentryInit: () => undefined,
|
||||
SentryClose: () => undefined,
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
}));
|
||||
|
||||
describe('TelemetryApproval', () => {
|
||||
it('click on checkbox should be properly handled', () => {
|
||||
it('click on checkbox should be properly handled', async () => {
|
||||
const helpText = 'My help text';
|
||||
render(<TelemetryApproval helpText={helpText} />);
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
);
|
||||
act(() => {
|
||||
screen.getByRole('checkbox').click();
|
||||
});
|
||||
await userEvent.click(screen.getByRole('checkbox'));
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
|
||||
@@ -11,6 +11,7 @@ import { WelcomeNoticeDialog } from './welcome-notice-dialog';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
import { isTestEnv } from '@vegaprotocol/utils';
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
@@ -31,9 +32,7 @@ export const WelcomeDialog = () => {
|
||||
);
|
||||
|
||||
const isRiskDialogNeeded =
|
||||
riskAccepted !== 'true' &&
|
||||
VEGA_ENV !== Networks.MAINNET &&
|
||||
!('Cypress' in window);
|
||||
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
|
||||
|
||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
const windowOrDefault = (key: string, defaultValue?: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (window._env_ && window._env_[key]) {
|
||||
return window._env_[key];
|
||||
}
|
||||
}
|
||||
return defaultValue || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Need to have default value as next in-lines environment variables. Cannot figure out dynamic keys.
|
||||
* So must provide the default with the key so that next can figure it out.
|
||||
*/
|
||||
export const ENV = {
|
||||
envName: windowOrDefault('NX_VEGA_ENV', process.env['NX_VEGA_ENV']),
|
||||
dsn: windowOrDefault(
|
||||
'NX_TRADING_SENTRY_DSN',
|
||||
process.env['NX_TRADING_SENTRY_DSN']
|
||||
),
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './env';
|
||||
@@ -20,20 +20,8 @@ export const useMarketClickHandler = (replace = false) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useMarketLiquidityClickHandler = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
const { marketId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const isLiquidityPage = pathname.match(/^\/liquidity\/(.+)/);
|
||||
return useCallback(
|
||||
(selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.LIQUIDITY](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else if (selectedId !== marketId || !isLiquidityPage) {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
},
|
||||
[navigate, marketId, replace, isLiquidityPage]
|
||||
);
|
||||
export const useMarketLiquidityClickHandler = () => {
|
||||
return useCallback((selectedId: string, metaKey?: boolean) => {
|
||||
window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self');
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -12,22 +12,26 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
.fn()
|
||||
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
|
||||
}));
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
}));
|
||||
|
||||
describe('useTelemetryApproval', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('hook should return proper array', () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
expect(res.result.current[0]).toEqual(false);
|
||||
expect(res.result.current[1]).toEqual(expect.any(Function));
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual(false);
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('hook should init stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](true);
|
||||
result.current[1](true);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryInit).toHaveBeenCalled();
|
||||
@@ -36,9 +40,9 @@ describe('useTelemetryApproval', () => {
|
||||
});
|
||||
|
||||
it('hook should close stuff properly', async () => {
|
||||
const res = renderHook(() => useTelemetryApproval());
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
res.result.current[1](false);
|
||||
result.current[1](false);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryClose).toHaveBeenCalled();
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { ENV } from '../config';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
export const STORAGE_KEY = 'vega_telemetry_approval';
|
||||
|
||||
export const useTelemetryApproval = (): [
|
||||
value: boolean,
|
||||
setValue: (value: boolean) => void
|
||||
] => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
|
||||
const setApprove = useCallback(
|
||||
(value: boolean) => {
|
||||
if (value) {
|
||||
SentryInit(ENV.dsn, ENV.envName);
|
||||
if (value && SENTRY_DSN) {
|
||||
SentryInit(SENTRY_DSN, VEGA_ENV);
|
||||
return setValue('1');
|
||||
}
|
||||
SentryClose();
|
||||
removeValue();
|
||||
},
|
||||
[setValue, removeValue]
|
||||
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
|
||||
);
|
||||
return [Boolean(value), setApprove];
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@ import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
|
||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { ENV } from '../lib/config';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
@@ -155,10 +154,11 @@ const PartyData = () => {
|
||||
};
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
useVegaEagerConnect(Connectors);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: ENV.dsn, env: ENV.envName } : {}
|
||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||
);
|
||||
|
||||
const { pubKey, connect } = useVegaWallet();
|
||||
|
||||
@@ -20,10 +20,6 @@ export default function Document() {
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<script src="/theme-setter.js" type="text/javascript" async />
|
||||
{['1', 'true'].includes(process.env['NX_USE_ENV_OVERRIDES'] || '') ? (
|
||||
/* eslint-disable-next-line @next/next/no-sync-scripts */
|
||||
<script src="/assets/env-config.js" type="text/javascript" />
|
||||
) : null}
|
||||
</Head>
|
||||
<body className="font-alpha dark:bg-black dark:text-white">
|
||||
<Main />
|
||||
|
||||
@@ -72,7 +72,6 @@ export const AccountBreakdownDialog = memo(
|
||||
onClose: () => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
console.log('render');
|
||||
return (
|
||||
<Dialog
|
||||
size="medium"
|
||||
|
||||
@@ -151,19 +151,53 @@ export const amendGeneralAccountBalance = (
|
||||
marketId: string,
|
||||
balance: string
|
||||
) => {
|
||||
if (accounts.party?.accountsConnection?.edges) {
|
||||
const marginAccount = accounts.party.accountsConnection.edges.find(
|
||||
(edge) => edge?.node.market?.id === marketId
|
||||
);
|
||||
if (marginAccount) {
|
||||
const generalAccount = accounts.party.accountsConnection.edges.find(
|
||||
(edge) =>
|
||||
edge?.node.asset.id === marginAccount.node.asset.id &&
|
||||
!edge?.node.market
|
||||
);
|
||||
if (generalAccount) {
|
||||
generalAccount.node.balance = balance;
|
||||
}
|
||||
}
|
||||
if (!accounts.party?.accountsConnection?.edges) {
|
||||
return accounts;
|
||||
}
|
||||
const marginAccount = accounts.party?.accountsConnection?.edges?.find(
|
||||
(edge) => edge?.node.market?.id === marketId
|
||||
);
|
||||
if (marginAccount) {
|
||||
const edges = accounts.party.accountsConnection.edges.map((edge) =>
|
||||
edge?.node.asset.id === marginAccount.node.asset.id && !edge?.node.market
|
||||
? { ...edge, node: { ...edge.node, balance } }
|
||||
: edge
|
||||
);
|
||||
return {
|
||||
...accounts,
|
||||
party: {
|
||||
...accounts.party,
|
||||
accountsConnection: {
|
||||
...accounts.party.accountsConnection,
|
||||
edges,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return accounts;
|
||||
};
|
||||
|
||||
export const amendMarginAccountBalance = (
|
||||
accounts: AccountsQuery,
|
||||
marketId: string,
|
||||
balance: string
|
||||
) => {
|
||||
if (!accounts.party?.accountsConnection?.edges) {
|
||||
return accounts;
|
||||
}
|
||||
const edges = accounts.party?.accountsConnection?.edges?.map((edge) =>
|
||||
edge?.node.market?.id === marketId
|
||||
? { ...edge, node: { ...edge?.node, balance } }
|
||||
: edge
|
||||
);
|
||||
return {
|
||||
...accounts,
|
||||
party: {
|
||||
...accounts.party,
|
||||
accountsConnection: {
|
||||
...accounts.party.accountsConnection,
|
||||
edges,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -290,6 +290,14 @@ export const TransferFee = ({
|
||||
decimals?: number;
|
||||
}) => {
|
||||
if (!feeFactor || !amount || !transferAmount || !fee) return null;
|
||||
if (
|
||||
isNaN(Number(feeFactor)) ||
|
||||
isNaN(Number(amount)) ||
|
||||
isNaN(Number(transferAmount)) ||
|
||||
isNaN(Number(fee))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { sendVegaTx } from './wallet-client';
|
||||
import { waitForProposal } from './propose-market';
|
||||
import { determineId } from '../utils';
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
|
||||
export async function submitProposal(proposalTx: ProposalSubmissionBody) {
|
||||
cy.highlight('Submitting proposal');
|
||||
const result = await sendVegaTx(vegaPubKey, proposalTx);
|
||||
await waitForProposal(determineId(result.result.transaction.signature.value));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
import { createWalletClient } from '../capsule/wallet-client';
|
||||
import type { ProposalSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import { submitProposal } from '../capsule/submit-proposal';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
@@ -15,11 +16,8 @@ export const addVegaWalletSubmitProposal = () => {
|
||||
Cypress.Commands.add('VegaWalletSubmitProposal', (proposalTx) => {
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
|
||||
cy.highlight('Submitting proposal');
|
||||
sendVegaTx(vegaPubKey, proposalTx);
|
||||
cy.wrap(submitProposal(proposalTx));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,27 +44,35 @@ export const checkSorting = (
|
||||
orderTabDesc: string[]
|
||||
) => {
|
||||
checkSortChange(orderTabDefault, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).last().click();
|
||||
});
|
||||
checkSortChange(orderTabAsc, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
checkSortChange(orderTabDesc, column);
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
cy.get('.ag-header-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(`[col-id="${column}"]`).click();
|
||||
});
|
||||
};
|
||||
|
||||
const checkSortChange = (tabsArr: string[], column: string) => {
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
tabsArr.forEach((entry, i) => {
|
||||
cy.get(`[row-index="${i}"]`).within(() => {
|
||||
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
|
||||
cy.get('.ag-center-cols-container')
|
||||
.last()
|
||||
.within(() => {
|
||||
tabsArr.forEach((entry, i) => {
|
||||
cy.get(`[row-index="${i}"]`).within(() => {
|
||||
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
type Edges = { node: unknown }[];
|
||||
|
||||
@@ -24,11 +24,13 @@ export type VegaValueFormatterParams<TRow, TField extends Field> = RowHelper<
|
||||
TField
|
||||
>;
|
||||
|
||||
export type VegaValueGetterParams<TRow, TField extends Field> = RowHelper<
|
||||
export type VegaValueGetterParams<TRow> = Omit<
|
||||
ValueGetterParams,
|
||||
TRow,
|
||||
TField
|
||||
>;
|
||||
'data' | 'node'
|
||||
> & {
|
||||
data?: TRow;
|
||||
node: (Omit<RowNode, 'data'> & { data?: TRow }) | null;
|
||||
};
|
||||
|
||||
export type VegaICellRendererParams<TRow, TField extends Field = string> = Omit<
|
||||
RowHelper<ICellRendererParams, TRow, TField>,
|
||||
|
||||
@@ -11,11 +11,7 @@ import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||
import type { EstimateFeesQuery } from '../../hooks/__generated__/EstimateOrder';
|
||||
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
isNumeric,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
@@ -31,32 +27,6 @@ import {
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
export const formatValue = (
|
||||
value: string | number | null | undefined,
|
||||
formatDecimals: number,
|
||||
quantum?: string
|
||||
): string => {
|
||||
if (!isNumeric(value)) return emptyValue;
|
||||
if (!quantum) return addDecimalsFormatNumber(value, formatDecimals);
|
||||
return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum);
|
||||
};
|
||||
|
||||
export const formatRange = (
|
||||
min: string | number | null | undefined,
|
||||
max: string | number | null | undefined,
|
||||
formatDecimals: number,
|
||||
quantum?: string
|
||||
) => {
|
||||
const minFormatted = formatValue(min, formatDecimals, quantum);
|
||||
const maxFormatted = formatValue(max, formatDecimals, quantum);
|
||||
if (minFormatted !== maxFormatted) {
|
||||
return `${minFormatted} - ${maxFormatted}`;
|
||||
}
|
||||
if (minFormatted !== emptyValue) {
|
||||
return minFormatted;
|
||||
}
|
||||
return maxFormatted;
|
||||
};
|
||||
export interface DealTicketFeeDetailPros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
@@ -143,6 +113,7 @@ export const DealTicketFeeDetails = ({
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
let marginRequiredBestCase: string | undefined = undefined;
|
||||
let marginRequiredWorstCase: string | undefined = undefined;
|
||||
if (marginEstimate) {
|
||||
@@ -232,7 +203,6 @@ export const DealTicketFeeDetails = ({
|
||||
}
|
||||
|
||||
let liquidationPriceEstimate = emptyValue;
|
||||
let liquidationPriceEstimateFormatted;
|
||||
|
||||
if (liquidationEstimate) {
|
||||
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
|
||||
@@ -258,18 +228,10 @@ export const DealTicketFeeDetails = ({
|
||||
liquidationEstimateWorstCaseIncludingSellOrders
|
||||
? liquidationEstimateWorstCaseIncludingBuyOrders
|
||||
: liquidationEstimateWorstCaseIncludingSellOrders;
|
||||
|
||||
// The estimate order query API gives us the liquidation price in formatted by asset decimals.
|
||||
// We need to calculate it with asset decimals, but display it with market decimals precision until the API changes.
|
||||
liquidationPriceEstimate = formatRange(
|
||||
(liquidationEstimateBestCase < liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
(liquidationEstimateBestCase > liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
assetDecimals
|
||||
);
|
||||
liquidationPriceEstimateFormatted = formatRange(
|
||||
(liquidationEstimateBestCase < liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
@@ -279,7 +241,8 @@ export const DealTicketFeeDetails = ({
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
assetDecimals,
|
||||
quantum
|
||||
undefined,
|
||||
market.decimalPlaces
|
||||
);
|
||||
}
|
||||
|
||||
@@ -288,14 +251,16 @@ export const DealTicketFeeDetails = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, assetDecimals)}
|
||||
formattedValue={formatValue(notionalSize, assetDecimals, quantum)}
|
||||
symbol={assetSymbol}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol)}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
@@ -377,8 +342,8 @@ export const DealTicketFeeDetails = ({
|
||||
<DealTicketFeeDetail
|
||||
label={t('Liquidation price estimate')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimateFormatted}
|
||||
symbol={assetSymbol}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
symbol={quoteName}
|
||||
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}
|
||||
/>
|
||||
{partyId && (
|
||||
|
||||
@@ -195,7 +195,7 @@ export const DealTicket = ({
|
||||
}
|
||||
|
||||
const hasNoBalance =
|
||||
!generalAccountBalance || !BigInt(generalAccountBalance);
|
||||
!BigInt(generalAccountBalance) && !BigInt(marginAccountBalance);
|
||||
if (hasNoBalance) {
|
||||
setError('summary', {
|
||||
message: SummaryValidationType.NoCollateral,
|
||||
@@ -219,6 +219,7 @@ export const DealTicket = ({
|
||||
marketState,
|
||||
marketTradingMode,
|
||||
generalAccountBalance,
|
||||
marginAccountBalance,
|
||||
pubKey,
|
||||
setError,
|
||||
clearErrors,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
truncateByChars,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
@@ -25,7 +24,6 @@ export const DepositsTable = forwardRef<
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate={t('No deposits')}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
suppressCellFocus={true}
|
||||
|
||||
@@ -301,6 +301,25 @@ describe('useEnvironment', () => {
|
||||
expect(fetch).toHaveBeenCalledWith(configUrl);
|
||||
});
|
||||
|
||||
it('uses env vars from window._env_ if set', async () => {
|
||||
const url = 'http://foo.bar.com';
|
||||
// @ts-ignore _env_ is declared in app
|
||||
window._env_ = {
|
||||
VEGA_URL: url,
|
||||
};
|
||||
|
||||
const { result } = setup();
|
||||
|
||||
await act(async () => {
|
||||
result.current.initialize();
|
||||
});
|
||||
|
||||
expect(result.current.VEGA_URL).toBe(url);
|
||||
|
||||
// @ts-ignore delete _env_
|
||||
delete window['_env_'];
|
||||
});
|
||||
|
||||
it('sets error if environment is invalid', async () => {
|
||||
const error = console.error;
|
||||
console.error = noop;
|
||||
|
||||
@@ -269,34 +269,96 @@ const testSubscription = (client: Client) => {
|
||||
* here to appease the environment store interface
|
||||
*/
|
||||
function compileEnvVars() {
|
||||
const VEGA_ENV = process.env['NX_VEGA_ENV'] as Networks;
|
||||
const VEGA_ENV = windowOrDefault(
|
||||
'VEGA_ENV',
|
||||
process.env['NX_VEGA_ENV']
|
||||
) as Networks;
|
||||
const env: Environment = {
|
||||
VEGA_URL: process.env['NX_VEGA_URL'],
|
||||
VEGA_URL: windowOrDefault('VEGA_URL', process.env['NX_VEGA_URL']),
|
||||
VEGA_ENV,
|
||||
VEGA_CONFIG_URL: process.env['NX_VEGA_CONFIG_URL'] as string,
|
||||
VEGA_NETWORKS: parseNetworks(process.env['NX_VEGA_NETWORKS']),
|
||||
VEGA_WALLET_URL: process.env['NX_VEGA_WALLET_URL'] as string,
|
||||
HOSTED_WALLET_URL: process.env['NX_HOSTED_WALLET_URL'],
|
||||
ETHERSCAN_URL: getEtherscanUrl(VEGA_ENV, process.env['NX_ETHERSCAN_URL']),
|
||||
VEGA_CONFIG_URL: windowOrDefault(
|
||||
'VEGA_CONFIG_URL',
|
||||
process.env['NX_VEGA_CONFIG_URL'] as string
|
||||
),
|
||||
VEGA_NETWORKS: parseNetworks(
|
||||
windowOrDefault('VEGA_NETWORKS', process.env['NX_VEGA_NETWORKS'])
|
||||
),
|
||||
VEGA_WALLET_URL: windowOrDefault(
|
||||
'VEGA_WALLET_URL',
|
||||
process.env['NX_VEGA_WALLET_URL'] as string
|
||||
),
|
||||
HOSTED_WALLET_URL: windowOrDefault(
|
||||
'HOSTED_WALLET_URL',
|
||||
process.env['NX_HOSTED_WALLET_URL']
|
||||
),
|
||||
ETHERSCAN_URL: getEtherscanUrl(
|
||||
VEGA_ENV,
|
||||
windowOrDefault('ETHERSCAN_URL', process.env['NX_ETHERSCAN_URL'])
|
||||
),
|
||||
ETHEREUM_PROVIDER_URL: getEthereumProviderUrl(
|
||||
VEGA_ENV,
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL']
|
||||
windowOrDefault(
|
||||
'ETHEREUM_PROVIDER_URL',
|
||||
process.env['NX_ETHEREUM_PROVIDER_URL']
|
||||
)
|
||||
),
|
||||
ETH_LOCAL_PROVIDER_URL: process.env['NX_ETH_LOCAL_PROVIDER_URL'],
|
||||
ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'],
|
||||
ORACLE_PROOFS_URL: process.env['NX_ORACLE_PROOFS_URL'],
|
||||
VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'],
|
||||
VEGA_CONSOLE_URL: process.env['NX_VEGA_CONSOLE_URL'],
|
||||
VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
|
||||
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
|
||||
GITHUB_FEEDBACK_URL: process.env['NX_GITHUB_FEEDBACK_URL'],
|
||||
MAINTENANCE_PAGE: parseBoolean(process.env['NX_MAINTENANCE_PAGE']),
|
||||
GIT_BRANCH: process.env['GIT_COMMIT_BRANCH'],
|
||||
GIT_COMMIT_HASH: process.env['GIT_COMMIT_HASH'],
|
||||
GIT_ORIGIN_URL: process.env['GIT_ORIGIN_URL'],
|
||||
ANNOUNCEMENTS_CONFIG_URL: process.env['NX_ANNOUNCEMENTS_CONFIG_URL'],
|
||||
VEGA_INCIDENT_URL: process.env['NX_VEGA_INCIDENT_URL'],
|
||||
APP_VERSION: process.env['NX_APP_VERSION'],
|
||||
ETH_LOCAL_PROVIDER_URL: windowOrDefault(
|
||||
'ETH_LOCAL_PROVIDER_URL',
|
||||
process.env['NX_ETH_LOCAL_PROVIDER_URL']
|
||||
),
|
||||
ETH_WALLET_MNEMONIC: windowOrDefault(
|
||||
'ETH_WALLET_MNEMONIC',
|
||||
process.env['NX_ETH_WALLET_MNEMONIC']
|
||||
),
|
||||
ORACLE_PROOFS_URL: windowOrDefault(
|
||||
'ORACLE_PROOFS_URL',
|
||||
process.env['NX_ORACLE_PROOFS_URL']
|
||||
),
|
||||
VEGA_DOCS_URL: windowOrDefault(
|
||||
'VEGA_DOCS_URL',
|
||||
process.env['NX_VEGA_DOCS_URL']
|
||||
),
|
||||
VEGA_CONSOLE_URL: windowOrDefault(
|
||||
'VEGA_CONSOLE_URL',
|
||||
process.env['NX_VEGA_CONSOLE_URL']
|
||||
),
|
||||
VEGA_EXPLORER_URL: windowOrDefault(
|
||||
'VEGA_EXPLORER_URL',
|
||||
process.env['NX_VEGA_EXPLORER_URL']
|
||||
),
|
||||
VEGA_TOKEN_URL: windowOrDefault(
|
||||
'VEGA_TOKEN_URL',
|
||||
process.env['NX_VEGA_TOKEN_URL']
|
||||
),
|
||||
GITHUB_FEEDBACK_URL: windowOrDefault(
|
||||
'GITHUB_FEEDBACK_URL',
|
||||
process.env['NX_GITHUB_FEEDBACK_URL']
|
||||
),
|
||||
MAINTENANCE_PAGE: parseBoolean(
|
||||
windowOrDefault('MAINTENANCE_PAGE', process.env['NX_MAINTENANCE_PAGE'])
|
||||
),
|
||||
GIT_BRANCH: windowOrDefault(
|
||||
'GIT_COMMIT_BRANCH',
|
||||
process.env['GIT_COMMIT_BRANCH']
|
||||
),
|
||||
GIT_COMMIT_HASH: windowOrDefault(
|
||||
'GIT_COMMIT_HASH',
|
||||
process.env['GIT_COMMIT_HASH']
|
||||
),
|
||||
GIT_ORIGIN_URL: windowOrDefault(
|
||||
'GIT_ORIGIN_URL',
|
||||
process.env['GIT_ORIGIN_URL']
|
||||
),
|
||||
ANNOUNCEMENTS_CONFIG_URL: windowOrDefault(
|
||||
'ANNOUNCEMENTS_CONFIG_URL',
|
||||
process.env['NX_ANNOUNCEMENTS_CONFIG_URL']
|
||||
),
|
||||
VEGA_INCIDENT_URL: windowOrDefault(
|
||||
'VEGA_INCIDENT_URL',
|
||||
process.env['NX_VEGA_INCIDENT_URL']
|
||||
),
|
||||
APP_VERSION: windowOrDefault('APP_VERSION', process.env['NX_APP_VERSION']),
|
||||
SENTRY_DSN: windowOrDefault('SENTRY_DSN', process.env['NX_SENTRY_DSN']),
|
||||
};
|
||||
|
||||
return env;
|
||||
@@ -341,3 +403,14 @@ function getEtherscanUrl(
|
||||
? 'https://etherscan.io'
|
||||
: 'https://sepolia.etherscan.io';
|
||||
}
|
||||
|
||||
export function windowOrDefault(key: string, defaultValue?: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
// @ts-ignore avoid conflic in env
|
||||
if (window._env_ && window._env_[key]) {
|
||||
// @ts-ignore presence has been check above
|
||||
return window._env_[key];
|
||||
}
|
||||
}
|
||||
return defaultValue || undefined;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEnvironment } from './use-environment';
|
||||
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
|
||||
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { isTestEnv } from '@vegaprotocol/utils';
|
||||
|
||||
const POLL_INTERVAL = 1000;
|
||||
const BLOCK_THRESHOLD = 3;
|
||||
@@ -38,7 +39,7 @@ export const useNodeHealth = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
|
||||
if (!isTestEnv() && window.location.hostname !== 'localhost') {
|
||||
startPolling(POLL_INTERVAL);
|
||||
}
|
||||
}, [error, startPolling, stopPolling]);
|
||||
|
||||
@@ -52,6 +52,7 @@ const schemaObject = {
|
||||
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
|
||||
VEGA_INCIDENT_URL: z.optional(z.string()),
|
||||
APP_VERSION: z.optional(z.string()),
|
||||
SENTRY_DSN: z.optional(z.string()),
|
||||
};
|
||||
|
||||
// combine schema above with custom rule to ensure either
|
||||
|
||||
@@ -48,7 +48,7 @@ const mapFillUpdateToFill = (
|
||||
id: marketId,
|
||||
},
|
||||
buyer: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: sellerId, __typename: 'Party' },
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
formatNumberPercentage,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type {
|
||||
VegaValueFormatterParams,
|
||||
TypedDataAgGrid,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { ValueFormatterParams } from 'ag-grid-community';
|
||||
import type {
|
||||
ColDef,
|
||||
ITooltipParams,
|
||||
ValueFormatterParams,
|
||||
} from 'ag-grid-community';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { LiquidityProvisionStatus } from '@vegaprotocol/types';
|
||||
import { LiquidityProvisionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { LiquidityProvisionData } from './liquidity-data-provider';
|
||||
|
||||
@@ -35,21 +37,152 @@ export interface LiquidityTableProps
|
||||
symbol?: string;
|
||||
assetDecimalPlaces?: number;
|
||||
stakeToCcyVolume: string | null;
|
||||
quantum?: string | number;
|
||||
}
|
||||
|
||||
export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
({ symbol = '', assetDecimalPlaces, stakeToCcyVolume, ...props }, ref) => {
|
||||
const assetDecimalsFormatter = ({ value }: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0, 5)}`;
|
||||
};
|
||||
const stakeToCcyVolumeFormatter = ({ value }: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0, 5)}`;
|
||||
};
|
||||
(
|
||||
{ symbol = '', assetDecimalPlaces, stakeToCcyVolume, quantum, ...props },
|
||||
ref
|
||||
) => {
|
||||
const colDefs = useMemo(() => {
|
||||
const assetDecimalsFormatter = ({ value }: ITooltipParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`;
|
||||
};
|
||||
|
||||
const assetDecimalsQuantumFormatter = ({
|
||||
value,
|
||||
}: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
)}`;
|
||||
};
|
||||
|
||||
const stakeToCcyVolumeFormatter = ({ value }: ITooltipParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`;
|
||||
};
|
||||
|
||||
const stakeToCcyVolumeQuantumFormatter = ({
|
||||
value,
|
||||
}: ValueFormatterParams) => {
|
||||
if (!value) return '-';
|
||||
const newValue = new BigNumber(value)
|
||||
.times(Number(stakeToCcyVolume) || 1)
|
||||
.toString();
|
||||
return `${addDecimalsFormatNumberQuantum(
|
||||
newValue,
|
||||
assetDecimalPlaces ?? 0,
|
||||
quantum ?? 0
|
||||
)}`;
|
||||
};
|
||||
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
headerName: t('Party'),
|
||||
field: 'party.id',
|
||||
headerTooltip: t(
|
||||
'The public key of the party making this commitment.'
|
||||
),
|
||||
},
|
||||
{
|
||||
headerName: t(`Commitment (${symbol})`),
|
||||
field: 'commitmentAmount',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The amount committed to the market by this liquidity provider.'
|
||||
),
|
||||
valueFormatter: assetDecimalsQuantumFormatter,
|
||||
tooltipValueGetter: assetDecimalsFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t(`Share`),
|
||||
field: 'equityLikeShare',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
||||
),
|
||||
valueFormatter: percentageFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Proposed fee'),
|
||||
headerTooltip: t(
|
||||
'The fee percentage (per trade) proposed by each liquidity provider.'
|
||||
),
|
||||
field: 'fee',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: percentageFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Market valuation at entry'),
|
||||
field: 'averageEntryValuation',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
||||
),
|
||||
minWidth: 160,
|
||||
valueFormatter: assetDecimalsQuantumFormatter,
|
||||
tooltipValueGetter: assetDecimalsFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Obligation'),
|
||||
field: 'commitmentAmount',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
||||
),
|
||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Supplied'),
|
||||
field: 'balance',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
||||
),
|
||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Status'),
|
||||
headerTooltip: t('The current status of this liquidity provision.'),
|
||||
field: 'status',
|
||||
valueFormatter: ({ value }) => {
|
||||
if (!value) return value;
|
||||
return LiquidityProvisionStatusMapping[
|
||||
value as LiquidityProvisionStatus
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Created'),
|
||||
headerTooltip: t(
|
||||
'The date and time this liquidity provision was created.'
|
||||
),
|
||||
field: 'createdAt',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: dateValueFormatter,
|
||||
},
|
||||
{
|
||||
headerName: t('Updated'),
|
||||
headerTooltip: t(
|
||||
'The date and time this liquidity provision was last updated.'
|
||||
),
|
||||
field: 'updatedAt',
|
||||
type: 'rightAligned',
|
||||
valueFormatter: dateValueFormatter,
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
@@ -66,99 +199,8 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
}}
|
||||
storeKey="liquidityProvisionTable"
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Party')}
|
||||
field="party.id"
|
||||
headerTooltip={t(
|
||||
'The public key of the party making this commitment.'
|
||||
)}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t(`Commitment (${symbol})`)}
|
||||
field="commitmentAmount"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The amount committed to the market by this liquidity provider.'
|
||||
)}
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Share')}
|
||||
field="equityLikeShare"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
||||
)}
|
||||
valueFormatter={percentageFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Proposed fee')}
|
||||
headerTooltip={t(
|
||||
'The fee percentage (per trade) proposed by each liquidity provider.'
|
||||
)}
|
||||
field="fee"
|
||||
type="rightAligned"
|
||||
valueFormatter={percentageFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Market valuation at entry')}
|
||||
field="averageEntryValuation"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
||||
)}
|
||||
minWidth={160}
|
||||
valueFormatter={assetDecimalsFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Obligation')}
|
||||
field="commitmentAmount"
|
||||
type="rightAligned"
|
||||
headerTooltip={t(
|
||||
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_siskas network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
||||
)}
|
||||
valueFormatter={stakeToCcyVolumeFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Supplied')}
|
||||
headerTooltip={t(
|
||||
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
||||
)}
|
||||
field="balance"
|
||||
type="rightAligned"
|
||||
valueFormatter={stakeToCcyVolumeFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Status')}
|
||||
headerTooltip={t('The current status of this liquidity provision.')}
|
||||
field="status"
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LiquidityProvisionData, 'status'>) => {
|
||||
if (!value) return value;
|
||||
return LiquidityProvisionStatusMapping[value];
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Created')}
|
||||
headerTooltip={t(
|
||||
'The date and time this liquidity provision was created.'
|
||||
)}
|
||||
field="createdAt"
|
||||
type="rightAligned"
|
||||
valueFormatter={dateValueFormatter}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Updated')}
|
||||
headerTooltip={t(
|
||||
'The last time this liquidity provision was updated.'
|
||||
)}
|
||||
field="updatedAt"
|
||||
type="rightAligned"
|
||||
valueFormatter={dateValueFormatter}
|
||||
/>
|
||||
</AgGrid>
|
||||
columnDefs={colDefs}
|
||||
></AgGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -47,7 +46,10 @@ const Row = ({
|
||||
if (asPercentage) {
|
||||
return formatNumberPercentage(new BigNumber(value).times(100));
|
||||
}
|
||||
return `${formatNumber(Number(value))} ${assetSymbol}`;
|
||||
if (assetSymbol) {
|
||||
return `${Number(value).toLocaleString()} ${assetSymbol}`;
|
||||
}
|
||||
return Number(value).toLocaleString();
|
||||
};
|
||||
|
||||
const formattedValue = getFormattedValue(value);
|
||||
|
||||
@@ -94,7 +94,7 @@ export const marketInfoQuery = (
|
||||
},
|
||||
lpPriceRange: '0.02',
|
||||
liquidityMonitoringParameters: {
|
||||
triggeringRatio: '0',
|
||||
triggeringRatio: '0.7',
|
||||
targetStakeParameters: {
|
||||
timeWindow: 3600,
|
||||
scalingFactor: 10,
|
||||
|
||||
@@ -76,9 +76,7 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.bestBidPrice'>) => {
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestBidPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.bestBidPrice, data.decimalPlaces).toNumber();
|
||||
@@ -102,12 +100,7 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<
|
||||
MarketMaybeWithData,
|
||||
'data.bestOfferPrice'
|
||||
>) => {
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.bestOfferPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
@@ -134,9 +127,7 @@ export const useColumnDefs = ({ onMarketClick }: Props) => {
|
||||
type: 'rightAligned',
|
||||
cellRenderer: 'PriceFlashCell',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({
|
||||
data,
|
||||
}: VegaValueGetterParams<MarketMaybeWithData, 'data.markPrice'>) => {
|
||||
valueGetter: ({ data }: VegaValueGetterParams<MarketMaybeWithData>) => {
|
||||
return data?.data?.markPrice === undefined
|
||||
? undefined
|
||||
: toBigNum(data?.data?.markPrice, data.decimalPlaces).toNumber();
|
||||
|
||||
@@ -2,14 +2,13 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef } from 'react';
|
||||
import { memo, forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
SetFilter,
|
||||
@@ -24,11 +23,13 @@ import type {
|
||||
TypedDataAgGrid,
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderActionsDropdown } from '../order-actions-dropdown';
|
||||
import { Filter } from '../order-list-manager';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
|
||||
export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
||||
marketId?: string;
|
||||
@@ -54,49 +55,40 @@ export const OrderListTable = memo<
|
||||
: filter === undefined || filter === Filter.Open
|
||||
? true
|
||||
: false;
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.code"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
field="size"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
cellClassRules={{
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: Order }) =>
|
||||
data?.side === Schema.Side.SIDE_SELL,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(data.size, data.market.positionDecimalPlaces ?? 0)
|
||||
.multipliedBy(data.side === Schema.Side.SIDE_SELL ? -1 : 1)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'size'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value)) {
|
||||
if (!data?.market || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
@@ -107,33 +99,33 @@ export const OrderListTable = memo<
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
value,
|
||||
data.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="type"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
}}
|
||||
cellRenderer="OrderTypeCell"
|
||||
cellRendererParams={{
|
||||
},
|
||||
cellRenderer: 'OrderTypeCell',
|
||||
cellRendererParams: {
|
||||
onClick: onOrderTypeClick,
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="status"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderStatusMapping,
|
||||
readonly: filter !== undefined,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'status'>) => {
|
||||
@@ -145,8 +137,8 @@ export const OrderListTable = memo<
|
||||
}`;
|
||||
}
|
||||
return value ? Schema.OrderStatusMapping[value] : '';
|
||||
}}
|
||||
cellRenderer={({
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
@@ -156,45 +148,51 @@ export const OrderListTable = memo<
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
)}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Filled')}
|
||||
field="remaining"
|
||||
cellClass="font-mono text-right"
|
||||
type="rightAligned"
|
||||
valueFormatter={({
|
||||
),
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Filled'),
|
||||
field: 'remaining',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||
return data?.size && data.market
|
||||
? toBigNum(
|
||||
(BigInt(data.size) - BigInt(data.remaining)).toString(),
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
).toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>): string => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(value) || !isNumeric(data.size)) {
|
||||
return '-';
|
||||
}
|
||||
const dps = data.market.positionDecimalPlaces;
|
||||
const size = new BigNumber(data.size);
|
||||
const remaining = new BigNumber(value);
|
||||
const fills = size.minus(remaining);
|
||||
const { positionDecimalPlaces } = data.market;
|
||||
const filled = BigInt(data.size) - BigInt(data.remaining);
|
||||
return `${addDecimalsFormatNumber(
|
||||
fills.toString(),
|
||||
dps
|
||||
)}/${addDecimalsFormatNumber(size.toString(), dps)}`;
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="price"
|
||||
type="rightAligned"
|
||||
cellClass="font-mono text-right"
|
||||
valueFormatter={({
|
||||
filled.toString(),
|
||||
positionDecimalPlaces
|
||||
)}/${addDecimalsFormatNumber(data.size, positionDecimalPlaces)}`;
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
@@ -204,16 +202,16 @@ export const OrderListTable = memo<
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="timeInForce"
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
}}
|
||||
valueFormatter={({
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<Order, 'timeInForce'>) => {
|
||||
@@ -235,13 +233,13 @@ export const OrderListTable = memo<
|
||||
}${data?.reduceOnly ? t('. Reduce only') : ''}`;
|
||||
|
||||
return label;
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="createdAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
filter: DateRangeFilter,
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
||||
return (
|
||||
@@ -249,12 +247,12 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : value}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
cellRenderer={({
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
cellRenderer: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'updatedAt'>) => {
|
||||
@@ -266,15 +264,15 @@ export const OrderListTable = memo<
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
<AgGridColumn
|
||||
colId="amend"
|
||||
{...COL_DEFS.actions}
|
||||
minWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
maxWidth={showAllActions ? 120 : COL_DEFS.actions.minWidth}
|
||||
cellRenderer={({ data }: { data?: Order }) => {
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
colId: 'amend',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: Order }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
@@ -298,9 +296,37 @@ export const OrderListTable = memo<
|
||||
<OrderActionsDropdown id={data?.id} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AgGrid>
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
filter,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
props.isReadOnly,
|
||||
showAllActions,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell, OrderTypeCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useEstimatePositionQuery } from './__generated__/Positions';
|
||||
import { formatRange } from '@vegaprotocol/utils';
|
||||
|
||||
export const LiquidationPrice = ({
|
||||
marketId,
|
||||
openVolume,
|
||||
collateralAvailable,
|
||||
decimalPlaces,
|
||||
formatDecimals,
|
||||
}: {
|
||||
marketId: string;
|
||||
openVolume: string;
|
||||
collateralAvailable: string;
|
||||
decimalPlaces: number;
|
||||
formatDecimals: number;
|
||||
}) => {
|
||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||
variables: {
|
||||
marketId,
|
||||
openVolume,
|
||||
collateralAvailable,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
skip: !openVolume || openVolume === '0',
|
||||
});
|
||||
const data = currentData || previousData;
|
||||
let value = '-';
|
||||
|
||||
if (data) {
|
||||
const bestCase =
|
||||
data.estimatePosition?.liquidation?.bestCase.open_volume_only.replace(
|
||||
/\..*/,
|
||||
''
|
||||
);
|
||||
const worstCase =
|
||||
data.estimatePosition?.liquidation?.worstCase.open_volume_only.replace(
|
||||
/\..*/,
|
||||
''
|
||||
);
|
||||
value =
|
||||
bestCase && worstCase && BigInt(bestCase) < BigInt(worstCase)
|
||||
? formatRange(
|
||||
bestCase,
|
||||
worstCase,
|
||||
decimalPlaces,
|
||||
undefined,
|
||||
formatDecimals,
|
||||
value
|
||||
)
|
||||
: formatRange(
|
||||
worstCase,
|
||||
bestCase,
|
||||
decimalPlaces,
|
||||
undefined,
|
||||
formatDecimals,
|
||||
value
|
||||
);
|
||||
}
|
||||
return <span data-testid="liquidation-price">{value}</span>;
|
||||
};
|
||||
@@ -34,6 +34,7 @@ export interface Position {
|
||||
averageEntryPrice: string;
|
||||
currentLeverage: number | undefined;
|
||||
decimals: number;
|
||||
quantum: string;
|
||||
lossSocializationAmount: string;
|
||||
marginAccountBalance: string;
|
||||
marketDecimalPlaces: number;
|
||||
@@ -73,6 +74,7 @@ export const getMetrics = (
|
||||
decimals,
|
||||
id: assetId,
|
||||
symbol: assetSymbol,
|
||||
quantum,
|
||||
} = market.tradableInstrument.instrument.product.settlementAsset;
|
||||
const generalAccount = accounts?.find(
|
||||
(account) =>
|
||||
@@ -114,6 +116,7 @@ export const getMetrics = (
|
||||
averageEntryPrice: position.averageEntryPrice,
|
||||
currentLeverage: currentLeverage ? currentLeverage.toNumber() : undefined,
|
||||
decimals,
|
||||
quantum,
|
||||
lossSocializationAmount: position.lossSocializationAmount || '0',
|
||||
marginAccountBalance: marginAccount?.balance ?? '0',
|
||||
marketDecimalPlaces,
|
||||
|
||||
@@ -7,6 +7,12 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import { PositionStatus, PositionStatusMapping } from '@vegaprotocol/types';
|
||||
import type { ICellRendererParams } from 'ag-grid-community';
|
||||
|
||||
jest.mock('./liquidation-price', () => ({
|
||||
LiquidationPrice: () => (
|
||||
<span data-testid="liquidation-price">liquidation price</span>
|
||||
),
|
||||
}));
|
||||
|
||||
const singleRow: Position = {
|
||||
partyId: 'partyId',
|
||||
assetId: 'asset-id',
|
||||
@@ -14,6 +20,7 @@ const singleRow: Position = {
|
||||
averageEntryPrice: '133',
|
||||
currentLeverage: 1.1,
|
||||
decimals: 2,
|
||||
quantum: '0.1',
|
||||
lossSocializationAmount: '0',
|
||||
marginAccountBalance: '12345600',
|
||||
marketDecimalPlaces: 1,
|
||||
@@ -48,7 +55,7 @@ it('render correct columns', async () => {
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(11);
|
||||
expect(headers).toHaveLength(12);
|
||||
expect(
|
||||
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
|
||||
).toEqual([
|
||||
@@ -56,6 +63,7 @@ it('render correct columns', async () => {
|
||||
'Notional',
|
||||
'Open volume',
|
||||
'Mark price',
|
||||
'Liquidation price',
|
||||
'Settlement asset',
|
||||
'Entry price',
|
||||
'Leverage',
|
||||
@@ -143,12 +151,20 @@ it('displays mark price', async () => {
|
||||
expect(cells[3].textContent).toEqual('-');
|
||||
});
|
||||
|
||||
it('displays liquidation price', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[4].textContent).toEqual('liquidation price');
|
||||
});
|
||||
|
||||
it('displays leverage', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[6].textContent).toEqual('1.1');
|
||||
expect(cells[7].textContent).toEqual('1.1');
|
||||
});
|
||||
|
||||
it('displays allocated margin', async () => {
|
||||
@@ -156,7 +172,7 @@ it('displays allocated margin', async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[7];
|
||||
const cell = cells[8];
|
||||
expect(cell.textContent).toEqual('123,456.00');
|
||||
});
|
||||
|
||||
@@ -165,7 +181,7 @@ it('displays realised and unrealised PNL', async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[9].textContent).toEqual('4.56');
|
||||
expect(cells[10].textContent).toEqual('4.56');
|
||||
});
|
||||
|
||||
it('displays close button', async () => {
|
||||
@@ -182,7 +198,7 @@ it('displays close button', async () => {
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[11].textContent).toEqual('Close');
|
||||
expect(cells[12].textContent).toEqual('Close');
|
||||
});
|
||||
|
||||
it('do not display close button if openVolume is zero', async () => {
|
||||
@@ -198,7 +214,7 @@ it('do not display close button if openVolume is zero', async () => {
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[11].textContent).toEqual('');
|
||||
expect(cells[12].textContent).toEqual('');
|
||||
});
|
||||
|
||||
describe('PNLCell', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import type { CellRendererSelectorResult } from 'ag-grid-community';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type {
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Position } from './positions-data-providers';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -43,6 +42,7 @@ import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionTableActions } from './position-actions-dropdown';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { LiquidationPrice } from './liquidation-price';
|
||||
|
||||
interface Props extends TypedDataAgGrid<Position> {
|
||||
onClose?: (data: Position) => void;
|
||||
@@ -121,333 +121,344 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
MarketNameCell,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{multipleKeys ? (
|
||||
<AgGridColumn
|
||||
headerName={t('Vega key')}
|
||||
field="partyId"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'partyId'>) =>
|
||||
(data?.partyId &&
|
||||
pubKeys &&
|
||||
pubKeys.find((key) => key.publicKey === data.partyId)?.name) ||
|
||||
data?.partyId
|
||||
}
|
||||
minWidth={190}
|
||||
/>
|
||||
) : null}
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="marketName"
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'marketId', onMarketClick }}
|
||||
minWidth={190}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Notional')}
|
||||
headerTooltip={t('Mark price x open volume.')}
|
||||
field="notional"
|
||||
type="rightAligned"
|
||||
cellClass="font-mono text-right"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'notional'>) => {
|
||||
return !data?.notional
|
||||
? undefined
|
||||
: toBigNum(data.notional, data.marketDecimalPlaces).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'notional'>) => {
|
||||
return !data || !data.notional
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.notional,
|
||||
columnDefs={useMemo<ColDef[]>(() => {
|
||||
const columnDefs: (ColDef | null)[] = [
|
||||
multipleKeys
|
||||
? {
|
||||
headerName: t('Vega key'),
|
||||
field: 'partyId',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) =>
|
||||
(data?.partyId &&
|
||||
pubKeys &&
|
||||
pubKeys.find((key) => key.publicKey === data.partyId)
|
||||
?.name) ||
|
||||
data?.partyId,
|
||||
minWidth: 190,
|
||||
}
|
||||
: null,
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketName',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'marketId', onMarketClick },
|
||||
minWidth: 190,
|
||||
},
|
||||
{
|
||||
headerName: t('Notional'),
|
||||
headerTooltip: t('Mark price x open volume.'),
|
||||
field: 'notional',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data?.notional
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.notional,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'notional'>) => {
|
||||
return !data || !data.notional
|
||||
? '-'
|
||||
: addDecimalsFormatNumber(
|
||||
data.notional,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
headerName: t('Open volume'),
|
||||
field: 'openVolume',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
cellClassRules: signedNumberCssClassRules,
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return data?.openVolume === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.openVolume,
|
||||
data.positionDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'openVolume'>): string => {
|
||||
return data?.openVolume === undefined
|
||||
? ''
|
||||
: volumePrefix(
|
||||
addDecimalsFormatNumber(
|
||||
data.openVolume,
|
||||
data.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
},
|
||||
cellRenderer: OpenVolumeCell,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Mark price'),
|
||||
field: 'markPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: PriceFlashCell,
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data ||
|
||||
!data.markPrice ||
|
||||
data.marketTradingMode ===
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.markPrice,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'markPrice'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data.markPrice ||
|
||||
data.marketTradingMode ===
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.markPrice,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
}}
|
||||
minWidth={80}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Open volume')}
|
||||
field="openVolume"
|
||||
type="rightAligned"
|
||||
cellClass="font-mono text-right"
|
||||
cellClassRules={signedNumberCssClassRules}
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'openVolume'>) => {
|
||||
return data?.openVolume === undefined
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data?.openVolume,
|
||||
data.positionDecimalPlaces
|
||||
).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'openVolume'>):
|
||||
| string
|
||||
| undefined => {
|
||||
return data?.openVolume === undefined
|
||||
? undefined
|
||||
: volumePrefix(
|
||||
addDecimalsFormatNumber(
|
||||
data.openVolume,
|
||||
data.positionDecimalPlaces
|
||||
)
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Liquidation price'),
|
||||
colId: 'liquidationPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: ({ data }: VegaICellRendererParams<Position>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<LiquidationPrice
|
||||
marketId={data.marketId}
|
||||
openVolume={data.openVolume}
|
||||
collateralAvailable={data.totalBalance}
|
||||
decimalPlaces={data.decimals}
|
||||
formatDecimals={data.marketDecimalPlaces}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
cellRenderer={OpenVolumeCell}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Mark price')}
|
||||
field="markPrice"
|
||||
type="rightAligned"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'markPrice'>) => {
|
||||
return !data ||
|
||||
!data.markPrice ||
|
||||
data.marketTradingMode ===
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
? undefined
|
||||
: toBigNum(data.markPrice, data.marketDecimalPlaces).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'markPrice'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
!data.markPrice ||
|
||||
data.marketTradingMode ===
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.markPrice,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Settlement asset')}
|
||||
field="assetSymbol"
|
||||
colId="asset"
|
||||
minWidth={100}
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Position>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(data.assetId, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{data?.assetSymbol}
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Entry price')}
|
||||
field="averageEntryPrice"
|
||||
type="rightAligned"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'averageEntryPrice'>) => {
|
||||
return data?.markPrice === undefined || !data
|
||||
? undefined
|
||||
: toBigNum(
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Settlement asset'),
|
||||
field: 'assetSymbol',
|
||||
colId: 'asset',
|
||||
minWidth: 100,
|
||||
cellRenderer: ({ data }: VegaICellRendererParams<Position>) => {
|
||||
if (!data) return null;
|
||||
return (
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(
|
||||
data.assetId,
|
||||
e.target as HTMLElement
|
||||
);
|
||||
}}
|
||||
>
|
||||
{data?.assetSymbol}
|
||||
</ButtonLink>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Entry price'),
|
||||
field: 'averageEntryPrice',
|
||||
type: 'rightAligned',
|
||||
cellRenderer: PriceFlashCell,
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return data?.markPrice === undefined || !data
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.averageEntryPrice,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
Position,
|
||||
'averageEntryPrice'
|
||||
>): string => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.averageEntryPrice,
|
||||
data.marketDecimalPlaces
|
||||
).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'averageEntryPrice'>):
|
||||
| string
|
||||
| undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.averageEntryPrice,
|
||||
data.marketDecimalPlaces
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
{multipleKeys ? null : (
|
||||
<AgGridColumn
|
||||
headerName={t('Leverage')}
|
||||
field="currentLeverage"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
||||
value === undefined
|
||||
? undefined
|
||||
: formatNumber(value.toString(), 1)
|
||||
}
|
||||
minWidth={100}
|
||||
/>
|
||||
)}
|
||||
{multipleKeys ? null : (
|
||||
<AgGridColumn
|
||||
headerName={t('Margin allocated')}
|
||||
field="marginAccountBalance"
|
||||
type="rightAligned"
|
||||
filter="agNumberColumnFilter"
|
||||
cellRendererSelector={(): CellRendererSelectorResult => {
|
||||
return {
|
||||
component: PriceFlashCell,
|
||||
};
|
||||
}}
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'marginAccountBalance'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.marginAccountBalance, data.decimals).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'marginAccountBalance'>):
|
||||
| string
|
||||
| undefined => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.marginAccountBalance,
|
||||
data.decimals
|
||||
);
|
||||
}}
|
||||
minWidth={100}
|
||||
/>
|
||||
)}
|
||||
<AgGridColumn
|
||||
headerName={t('Realised PNL')}
|
||||
field="realisedPNL"
|
||||
type="rightAligned"
|
||||
cellClassRules={signedNumberCssClassRules}
|
||||
cellClass="font-mono text-right"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'realisedPNL'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.realisedPNL, data.decimals).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'realisedPNL'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
|
||||
}}
|
||||
headerTooltip={t(
|
||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||
)}
|
||||
cellRenderer={PNLCell}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Unrealised PNL')}
|
||||
field="unrealisedPNL"
|
||||
type="rightAligned"
|
||||
cellClassRules={signedNumberCssClassRules}
|
||||
cellClass="font-mono text-right"
|
||||
filter="agNumberColumnFilter"
|
||||
valueGetter={({
|
||||
data,
|
||||
}: VegaValueGetterParams<Position, 'unrealisedPNL'>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.unrealisedPNL, data.decimals).toNumber();
|
||||
}}
|
||||
valueFormatter={({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'unrealisedPNL'>) =>
|
||||
!data
|
||||
? undefined
|
||||
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals)
|
||||
}
|
||||
headerTooltip={t(
|
||||
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
||||
)}
|
||||
cellRenderer={PNLCell}
|
||||
minWidth={100}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Updated')}
|
||||
field="updatedAt"
|
||||
type="rightAligned"
|
||||
filter={DateRangeFilter}
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'updatedAt'>) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
}}
|
||||
minWidth={150}
|
||||
/>
|
||||
{onClose && !isReadOnly ? (
|
||||
<AgGridColumn
|
||||
{...COL_DEFS.actions}
|
||||
cellRenderer={({ data }: VegaICellRendererParams<Position>) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data?.openVolume &&
|
||||
data?.openVolume !== '0' &&
|
||||
data.partyId === pubKey ? (
|
||||
<ButtonLink
|
||||
data-testid="close-position"
|
||||
onClick={() => data && onClose(data)}
|
||||
>
|
||||
{t('Close')}
|
||||
</ButtonLink>
|
||||
) : null}
|
||||
{data?.assetId && (
|
||||
<PositionTableActions assetId={data?.assetId} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
minWidth={90}
|
||||
maxWidth={90}
|
||||
/>
|
||||
) : null}
|
||||
</AgGrid>
|
||||
);
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
multipleKeys
|
||||
? null
|
||||
: {
|
||||
headerName: t('Leverage'),
|
||||
field: 'currentLeverage',
|
||||
type: 'rightAligned',
|
||||
filter: 'agNumberColumnFilter',
|
||||
cellRenderer: PriceFlashCell,
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
|
||||
value === undefined
|
||||
? ''
|
||||
: formatNumber(value.toString(), 1),
|
||||
minWidth: 100,
|
||||
},
|
||||
multipleKeys
|
||||
? null
|
||||
: {
|
||||
headerName: t('Margin allocated'),
|
||||
field: 'marginAccountBalance',
|
||||
type: 'rightAligned',
|
||||
filter: 'agNumberColumnFilter',
|
||||
cellRenderer: PriceFlashCell,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(
|
||||
data.marginAccountBalance,
|
||||
data.decimals
|
||||
).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<
|
||||
Position,
|
||||
'marginAccountBalance'
|
||||
>): string => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
return addDecimalsFormatNumber(
|
||||
data.marginAccountBalance,
|
||||
data.decimals
|
||||
);
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Realised PNL'),
|
||||
field: 'realisedPNL',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: signedNumberCssClassRules,
|
||||
cellClass: 'font-mono text-right',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.realisedPNL, data.decimals).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'realisedPNL'>) => {
|
||||
return !data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
|
||||
},
|
||||
headerTooltip: t(
|
||||
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
|
||||
),
|
||||
cellRenderer: PNLCell,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Unrealised PNL'),
|
||||
field: 'unrealisedPNL',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: signedNumberCssClassRules,
|
||||
cellClass: 'font-mono text-right',
|
||||
filter: 'agNumberColumnFilter',
|
||||
valueGetter: ({ data }: VegaValueGetterParams<Position>) => {
|
||||
return !data
|
||||
? undefined
|
||||
: toBigNum(data.unrealisedPNL, data.decimals).toNumber();
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<Position, 'unrealisedPNL'>) =>
|
||||
!data
|
||||
? ''
|
||||
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals),
|
||||
headerTooltip: t(
|
||||
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
|
||||
),
|
||||
cellRenderer: PNLCell,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
headerName: t('Updated'),
|
||||
field: 'updatedAt',
|
||||
type: 'rightAligned',
|
||||
filter: DateRangeFilter,
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<Position, 'updatedAt'>) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return getDateTimeFormat().format(new Date(value));
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
onClose && !isReadOnly
|
||||
? {
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<Position>) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data?.openVolume &&
|
||||
data?.openVolume !== '0' &&
|
||||
data.partyId === pubKey ? (
|
||||
<ButtonLink
|
||||
data-testid="close-position"
|
||||
onClick={() => data && onClose(data)}
|
||||
>
|
||||
{t('Close')}
|
||||
</ButtonLink>
|
||||
) : null}
|
||||
{data?.assetId && (
|
||||
<PositionTableActions assetId={data?.assetId} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
minWidth: 90,
|
||||
maxWidth: 90,
|
||||
}
|
||||
: null,
|
||||
];
|
||||
return columnDefs.filter<ColDef>(
|
||||
(colDef: ColDef | null): colDef is ColDef => colDef !== null
|
||||
);
|
||||
}, [
|
||||
isReadOnly,
|
||||
multipleKeys,
|
||||
onClose,
|
||||
onMarketClick,
|
||||
openAssetDetailsDialog,
|
||||
pubKey,
|
||||
pubKeys,
|
||||
])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useEffect } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { removePaginationWrapper, isTestEnv } from '@vegaprotocol/utils';
|
||||
import { useProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals';
|
||||
|
||||
export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
@@ -21,7 +21,7 @@ export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
|
||||
if (!isTestEnv() && window.location.hostname !== 'localhost') {
|
||||
startPolling(5000);
|
||||
}
|
||||
}, [error, startPolling, stopPolling]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBlockStatisticsQuery } from './__generated__/BlockStatistics';
|
||||
import sum from 'lodash/sum';
|
||||
import { isTestEnv } from '@vegaprotocol/utils';
|
||||
|
||||
const DEFAULT_POLLS = 10;
|
||||
const INTERVAL = 1000;
|
||||
@@ -20,7 +21,7 @@ const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('Cypress' in window) && window.location.hostname !== 'localhost') {
|
||||
if (!isTestEnv() && window.location.hostname !== 'localhost') {
|
||||
startPolling(INTERVAL);
|
||||
}
|
||||
}, [error, startPolling, stopPolling]);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './use-vote-submit';
|
||||
export * from './__generated__/VoteSubsciption';
|
||||
|
||||
@@ -5,15 +5,16 @@ import { useVoteEvent } from './use-vote-event';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { VoteEventFieldsFragment } from './__generated__/VoteSubsciption';
|
||||
|
||||
export type FinalizedVote = VoteEventFieldsFragment;
|
||||
export type FinalizedVote = VoteEventFieldsFragment & { pubKey: string };
|
||||
|
||||
export const useVoteSubmit = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { send, transaction, setComplete, Dialog } = useVegaTransaction();
|
||||
const waitForVoteEvent = useVoteEvent(transaction);
|
||||
|
||||
const [finalizedVote, setFinalizedVote] =
|
||||
useState<VoteEventFieldsFragment | null>(null);
|
||||
const [finalizedVote, setFinalizedVote] = useState<FinalizedVote | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const submit = useCallback(
|
||||
async (voteValue: VoteValue, proposalId: string | null) => {
|
||||
@@ -33,7 +34,7 @@ export const useVoteSubmit = () => {
|
||||
|
||||
if (res) {
|
||||
waitForVoteEvent(proposalId, pubKey, (v) => {
|
||||
setFinalizedVote(v);
|
||||
setFinalizedVote({ ...v, pubKey });
|
||||
setComplete();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export const tradesUpdateSubscription = (
|
||||
|
||||
const trades: TradeFieldsFragment[] = [
|
||||
{
|
||||
id: 'FFFFBC80005C517A10ACF481F7E6893769471098E696D0CC407F18134044CB16',
|
||||
price: '17116898',
|
||||
size: '24',
|
||||
createdAt: '2022-04-06T16:19:42.692598951Z',
|
||||
id: 'FFFFAD1BF47AA2853E5C375B6B3A62375F62D5B10807583D32EF3119CC455CD1',
|
||||
price: '17106734',
|
||||
size: '18',
|
||||
createdAt: '2022-04-07T17:56:47.997938583Z',
|
||||
aggressor: Side.SIDE_BUY,
|
||||
market: {
|
||||
id: 'market-0',
|
||||
@@ -77,10 +77,10 @@ const trades: TradeFieldsFragment[] = [
|
||||
__typename: 'Trade',
|
||||
},
|
||||
{
|
||||
id: 'FFFFAD1BF47AA2853E5C375B6B3A62375F62D5B10807583D32EF3119CC455CD1',
|
||||
price: '17106734',
|
||||
size: '18',
|
||||
createdAt: '2022-04-07T17:56:47.997938583Z',
|
||||
id: 'FFFFBC80005C517A10ACF481F7E6893769471098E696D0CC407F18134044CB16',
|
||||
price: '17116898',
|
||||
size: '24',
|
||||
createdAt: '2022-04-06T16:19:42.692598951Z',
|
||||
aggressor: Side.SIDE_BUY,
|
||||
market: {
|
||||
id: 'market-0',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/ui-toolkit",
|
||||
"version": "0.12.3"
|
||||
"version": "0.12.5"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { VegaIcon, VegaIconNames } from '../icon';
|
||||
import { Icon } from '../icon';
|
||||
@@ -164,10 +164,9 @@ export const DropdownMenuSeparator = forwardRef<
|
||||
/**
|
||||
* Container element for submenus
|
||||
*/
|
||||
export const DropdownMenuSub = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Sub>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Sub>
|
||||
>(({ ...subProps }) => <DropdownMenuPrimitive.Sub {...subProps} />);
|
||||
export const DropdownMenuSub = (
|
||||
subProps: ComponentProps<typeof DropdownMenuPrimitive.Sub>
|
||||
) => <DropdownMenuPrimitive.Sub {...subProps} />;
|
||||
|
||||
/**
|
||||
* Container within a DropdownMenuSub specifically for the content
|
||||
@@ -201,10 +200,9 @@ export const DropdownMenuSubTrigger = forwardRef<
|
||||
* Portal to ensure menu portions are rendered outwith where they appear in the
|
||||
* DOM.
|
||||
*/
|
||||
export const DropdownMenuPortal = forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Portal>,
|
||||
React.ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||
>(({ ...portalProps }) => <DropdownMenuPrimitive.Portal {...portalProps} />);
|
||||
export const DropdownMenuPortal = (
|
||||
portalProps: ComponentProps<typeof DropdownMenuPrimitive.Portal>
|
||||
) => <DropdownMenuPrimitive.Portal {...portalProps} />;
|
||||
|
||||
/**
|
||||
* Wraps a regular DropdownMenuItem with copy to clip board functionality
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import classNames from 'classnames';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { getIntentBackground, Intent } from '../../utils/intent';
|
||||
@@ -74,13 +77,17 @@ const Level = ({
|
||||
.multipliedBy(100)
|
||||
.toNumber();
|
||||
|
||||
const formattedFee = fee
|
||||
? formatNumberPercentage(new BigNumber(fee).times(100), 2)
|
||||
: '-';
|
||||
|
||||
const tooltipContent = (
|
||||
<>
|
||||
<div className="text-vega-dark-100 dark:text-vega-light-200">
|
||||
<div className="mt-1.5 inline-flex">
|
||||
<Indicator variant={intent} />
|
||||
</div>
|
||||
<span>
|
||||
{fee}% {t('Fee')}
|
||||
{formattedFee} {t('Fee')}
|
||||
</span>
|
||||
<div className="flex flex-col">
|
||||
<span>
|
||||
@@ -88,7 +95,7 @@ const Level = ({
|
||||
{addDecimalsFormatNumber(commitmentAmount, decimals)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -179,7 +186,7 @@ export const HealthBar = ({
|
||||
opacity={opacity}
|
||||
fee={fee}
|
||||
prevLevel={prevLevel}
|
||||
decimals={0}
|
||||
decimals={decimals}
|
||||
intent={intent}
|
||||
key={'healthbar-segment-' + index}
|
||||
/>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import type { ITooltipParams } from 'ag-grid-community';
|
||||
|
||||
const tooltipContentClasses =
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs break-word';
|
||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
||||
export interface TooltipProps {
|
||||
children: React.ReactElement;
|
||||
description?: string | ReactNode;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@vegaprotocol/utils",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ export * from './lib/remove-pagination-wrapper';
|
||||
export * from './lib/time';
|
||||
export * from './lib/validate';
|
||||
export * from './lib/resolve-network-name';
|
||||
export * from './lib/is-test-env';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './date';
|
||||
export * from './number';
|
||||
export * from './range';
|
||||
export * from './size';
|
||||
export * from './strings';
|
||||
|
||||
@@ -29,14 +29,21 @@ describe('number utils', () => {
|
||||
{ v: new BigNumber(123000), d: 3, o: '123.00', q: 0.1 },
|
||||
{ v: new BigNumber(123000), d: 1, o: '12,300.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001000), d: 2, o: '1,230,010.00', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230', q: 100 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 100 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 0.1 },
|
||||
{ v: new BigNumber(123001), d: 2, o: '1,230.01', q: 1 },
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.9123457',
|
||||
o: '12,345,678.91234568',
|
||||
q: '0.00003846',
|
||||
},
|
||||
{
|
||||
v: BigNumber('123456789123456789'),
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
q: '1',
|
||||
},
|
||||
])(
|
||||
'formats with addDecimalsFormatNumberQuantum given number correctly',
|
||||
({ v, d, o, q }) => {
|
||||
@@ -70,80 +77,80 @@ describe('number utils', () => {
|
||||
])('formats given number correctly', ({ v, d, o }) => {
|
||||
expect(formatNumberPercentage(v, d)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
|
||||
{
|
||||
v: new BigNumber(30000),
|
||||
d: undefined,
|
||||
o: ['30,000', '00'],
|
||||
},
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
|
||||
{
|
||||
v: new BigNumber(30000),
|
||||
d: undefined,
|
||||
o: ['30,000', '00'],
|
||||
},
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNumeric', () => {
|
||||
it.each([
|
||||
{ i: null, o: false },
|
||||
{ i: undefined, o: false },
|
||||
{ i: 1, o: true },
|
||||
{ i: '1', o: true },
|
||||
{ i: '-1', o: true },
|
||||
{ i: 0.1, o: true },
|
||||
{ i: '.1', o: true },
|
||||
{ i: '-.1', o: true },
|
||||
{ i: 123, o: true },
|
||||
{ i: -123, o: true },
|
||||
{ i: '123', o: true },
|
||||
{ i: '123.01', o: true },
|
||||
{ i: '-123.01', o: true },
|
||||
{ i: '--123.01', o: false },
|
||||
{ i: '123.', o: false },
|
||||
{ i: '123.1.1', o: false },
|
||||
{ i: BigInt(123), o: true },
|
||||
{ i: BigInt(-1), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: new BigNumber(123.123), o: true },
|
||||
{ i: new BigNumber(123.123).toString(), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: Infinity, o: false },
|
||||
{ i: NaN, o: false },
|
||||
])(
|
||||
'returns correct results',
|
||||
({
|
||||
i,
|
||||
o,
|
||||
}: {
|
||||
i: number | string | undefined | null | BigNumber | bigint;
|
||||
o: boolean;
|
||||
}) => {
|
||||
expect(isNumeric(i)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
||||
describe('isNumeric', () => {
|
||||
it.each([
|
||||
{ i: null, o: false },
|
||||
{ i: undefined, o: false },
|
||||
{ i: 1, o: true },
|
||||
{ i: '1', o: true },
|
||||
{ i: '-1', o: true },
|
||||
{ i: 0.1, o: true },
|
||||
{ i: '.1', o: true },
|
||||
{ i: '-.1', o: true },
|
||||
{ i: 123, o: true },
|
||||
{ i: -123, o: true },
|
||||
{ i: '123', o: true },
|
||||
{ i: '123.01', o: true },
|
||||
{ i: '-123.01', o: true },
|
||||
{ i: '--123.01', o: false },
|
||||
{ i: '123.', o: false },
|
||||
{ i: '123.1.1', o: false },
|
||||
{ i: BigInt(123), o: true },
|
||||
{ i: BigInt(-1), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: new BigNumber(123.123), o: true },
|
||||
{ i: new BigNumber(123.123).toString(), o: true },
|
||||
{ i: new BigNumber(123), o: true },
|
||||
{ i: Infinity, o: false },
|
||||
{ i: NaN, o: false },
|
||||
])(
|
||||
'returns correct results',
|
||||
({
|
||||
i,
|
||||
o,
|
||||
}: {
|
||||
i: number | string | undefined | null | BigNumber | bigint;
|
||||
o: boolean;
|
||||
}) => {
|
||||
expect(isNumeric(i)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('toDecimal', () => {
|
||||
it.each([
|
||||
{ v: 0, o: '1' },
|
||||
{ v: 1, o: '0.1' },
|
||||
{ v: 2, o: '0.01' },
|
||||
{ v: 3, o: '0.001' },
|
||||
{ v: 4, o: '0.0001' },
|
||||
{ v: 5, o: '0.00001' },
|
||||
{ v: 6, o: '0.000001' },
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
describe('toDecimal', () => {
|
||||
it.each([
|
||||
{ v: 0, o: '1' },
|
||||
{ v: 1, o: '0.1' },
|
||||
{ v: 2, o: '0.01' },
|
||||
{ v: 3, o: '0.001' },
|
||||
{ v: 4, o: '0.0001' },
|
||||
{ v: 5, o: '0.00001' },
|
||||
{ v: 6, o: '0.000001' },
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user