Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456635a297 | ||
|
|
489e250b68 | ||
|
|
eb58dcd350 | ||
|
|
c8c0bf2cc0 | ||
|
|
00579fcb1f | ||
|
|
5e13173efb | ||
|
|
3a6c0554cc | ||
|
|
d4b237a6ba | ||
|
|
5e765f3172 | ||
|
|
5ef8c11e6e | ||
|
|
84bae12def | ||
|
|
c5b22f5bfb | ||
|
|
071dcc61a8 | ||
|
|
ce0ccdfebc | ||
|
|
f1524d3fcd | ||
|
|
82e5128ba1 | ||
|
|
2bfc3abd15 | ||
|
|
bd3b557f3d | ||
|
|
c899da52c2 | ||
|
|
b9c4057ce5 | ||
|
|
d02feee5c6 | ||
|
|
6d1ab36a72 | ||
|
|
5690fe91a8 | ||
|
|
1daae6a233 | ||
|
|
cd35a34246 | ||
|
|
77ada964b5 | ||
|
|
d7404bcd76 | ||
|
|
91862701a7 | ||
|
|
0136efd78f | ||
|
|
c3a4954d0d | ||
|
|
528fd96721 | ||
|
|
155923ff8e |
@@ -1,15 +1,14 @@
|
||||
---
|
||||
name: Release
|
||||
about:
|
||||
A template to outline the steps needed to for a successful release of our frontend apps
|
||||
about: A template to outline the steps needed to for a successful release of our frontend apps
|
||||
title: 'Release [add dapp version]-core-[add core version]'
|
||||
labels:
|
||||
labels:
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Tasks
|
||||
|
||||
- [ ] Review [link to core release](xxx)
|
||||
- [ ] Review [link to core release](xxx)
|
||||
- [ ] Tag frontend-monorepo
|
||||
- [ ] Create release and generate release notes
|
||||
- [ ] Run `@smoke` tests
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout frontend mono repo
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
@@ -31,21 +31,25 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ steps.node-version.outputs.npmVersion }}
|
||||
|
||||
# Check SHAs
|
||||
- name: Derive appropriate SHAs for base and head for `nx affected` commands
|
||||
uses: nrwl/nx-set-shas@v2
|
||||
uses: nrwl/nx-set-shas@v3
|
||||
with:
|
||||
main-branch-name: ${{ github.base_ref || github.ref_name }}
|
||||
set-environment-variables-for-job: true
|
||||
main-branch-name: develop
|
||||
|
||||
- name: Restore node_modules from cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: node_modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
# See affected apps
|
||||
- name: See affected apps
|
||||
run: |
|
||||
nx_version=$(cat package.json | grep '"nx"' | cut -d ':' -f 2 | tr -d '",[:space:]')
|
||||
rm package.json yarn.lock
|
||||
yarn add nx@$nx_version
|
||||
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=HEAD --select=projects)"
|
||||
echo -n "Affected projects: $affected"
|
||||
|
||||
projects_e2e=""
|
||||
if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi
|
||||
if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- announcements
|
||||
- ui-toolkit
|
||||
- react-helpers
|
||||
- tailwindcss-config
|
||||
|
||||
@@ -3,6 +3,7 @@ NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -6,3 +6,4 @@ NX_VEGA_ENV=MAINNET
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
@@ -6,6 +6,7 @@ NX_VEGA_ENV=MIRROR
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -9,3 +9,4 @@ NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -11,3 +11,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -5,3 +5,4 @@ NX_VEGA_ENV=STAGNET3
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -5,6 +5,7 @@ NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
|
||||
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
||||
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
|
||||
|
||||
@@ -4,3 +4,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_BLOCK_EXPLORER=
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
|
||||
@@ -3,16 +3,14 @@ import {
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
BackgroundVideo,
|
||||
BreadcrumbsContainer,
|
||||
ButtonLink,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
Link,
|
||||
@@ -37,35 +35,9 @@ const DialogsContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const MainnetSimAd = () => {
|
||||
const [shouldDisplayBanner, setShouldDisplayBanner] = useState<boolean>(true);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => setShouldDisplayBanner(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
|
||||
export const Layout = () => {
|
||||
const isHome = Boolean(useMatch(Routes.HOME));
|
||||
const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -79,7 +51,12 @@ export const Layout = () => {
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<MainnetSimAd />
|
||||
{ANNOUNCEMENTS_CONFIG_URL && (
|
||||
<AnnouncementBanner
|
||||
app="explorer"
|
||||
configUrl={ANNOUNCEMENTS_CONFIG_URL}
|
||||
/>
|
||||
)}
|
||||
<Header />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -56,7 +56,7 @@ describe(
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
// 3001-VOTE-055
|
||||
// 3001-VOTE-050 3001-VOTE-054 3001-VOTE-055 3002-PROP-019
|
||||
it('Newly created raw proposal details - shows proposal title and full description', function () {
|
||||
createRawProposal();
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
|
||||
@@ -128,7 +128,7 @@ context(
|
||||
.and('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-048 3001-VOTE-049
|
||||
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
|
||||
it('Able to fail proposal due to lack of participation', function () {
|
||||
const proposalTitle = 'Add New free form proposal with short enactment';
|
||||
const proposalTx = createFreeFormProposalTxBody();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
waitForSpinner,
|
||||
navigateTo,
|
||||
navigation,
|
||||
closeDialog,
|
||||
} from '../../support/common.functions';
|
||||
import {
|
||||
clickOnValidatorFromList,
|
||||
@@ -41,7 +42,6 @@ const vegaWalletNameElement = '[data-testid="wallet-name"]';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const viewProposalButton = '[data-testid="view-proposal-btn"]';
|
||||
const rawProposalData = '[data-testid="proposal-data"]';
|
||||
const minVoteButton = '[data-testid="min-vote"]';
|
||||
@@ -177,7 +177,7 @@ context(
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
waitForProposalSync();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get(rejectProposalsLink).click();
|
||||
@@ -214,7 +214,7 @@ context(
|
||||
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
// 3002-PROP-009
|
||||
@@ -227,7 +227,7 @@ context(
|
||||
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
it('Unable to create a freeform proposal - when json parent section contains unexpected field', function () {
|
||||
@@ -251,7 +251,7 @@ context(
|
||||
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(rawProposalData)
|
||||
.invoke('val')
|
||||
.should('contain', "i shouldn't be here");
|
||||
@@ -279,7 +279,7 @@ context(
|
||||
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should('have.text', errorMsg);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
// 1005-PROP-009
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
closeDialog,
|
||||
navigateTo,
|
||||
navigation,
|
||||
waitForSpinner,
|
||||
@@ -39,7 +40,6 @@ const maxVoteDeadline = '[data-testid="max-vote"]';
|
||||
const minValidationDeadline = '[data-testid="min-validation"]';
|
||||
const minEnactDeadline = '[data-testid="min-enactment"]';
|
||||
const maxEnactDeadline = '[data-testid="max-enactment"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const inputError = '[data-testid="input-error-text"]';
|
||||
const enactmentDeadlineError =
|
||||
'[data-testid="enactment-before-voting-deadline"]';
|
||||
@@ -48,6 +48,7 @@ const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const proposalTermsSection = 'proposal';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
@@ -68,7 +69,6 @@ context(
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.createMarket();
|
||||
cy.visit('/');
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
@@ -78,6 +78,7 @@ context(
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
ethereumWalletConnect();
|
||||
cy.createMarket();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
@@ -194,7 +195,7 @@ context(
|
||||
'have.text',
|
||||
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(minVoteDeadline).click();
|
||||
cy.get(enactmentDeadlineError).should('not.exist');
|
||||
});
|
||||
@@ -286,7 +287,7 @@ context(
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-092
|
||||
// 3001-VOTE-092 3004-PMAC-001
|
||||
it('Able to submit update market proposal and vote for proposal', function () {
|
||||
vegaWalletFaucetAssetsWithoutCheck(
|
||||
'fUSDC',
|
||||
@@ -347,8 +348,9 @@ context(
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
it('Able to submit new asset proposal using min deadlines', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
goToMakeNewProposal(governanceProposalType.NEW_ASSET);
|
||||
cy.get(newProposalTitle).type('Test new asset proposal');
|
||||
cy.get(newProposalTitle).type(proposalTitle);
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-asset').then((newAssetProposal) => {
|
||||
const newAssetPayload = JSON.stringify(newAssetProposal);
|
||||
@@ -367,7 +369,7 @@ context(
|
||||
cy.contains('Proposal waiting for node vote', proposalTimeout).should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
// cannot submit a proposal with ERC20 address already in use
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
@@ -377,6 +379,17 @@ context(
|
||||
'PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE'
|
||||
);
|
||||
});
|
||||
closeDialog();
|
||||
navigateTo(navigation.proposals);
|
||||
cy.contains(proposalTitle)
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.contains('USDT Coin').should('be.visible');
|
||||
cy.contains('USDT').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Unable to submit new asset proposal with missing/invalid fields', function () {
|
||||
@@ -415,9 +428,15 @@ context(
|
||||
getProposalInformationFromTable('Proposed enactment') // 3001-VOTE-044
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
// 3001-VOTE-030 3001-VOTE-031
|
||||
cy.getByTestId(proposalTermsSection).within(() => {
|
||||
cy.contains('UpdateAsset').should('be.visible');
|
||||
cy.contains('UpdateERC20').should('be.visible');
|
||||
cy.contains('"lifetimeLimit": "10"').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to submit update asset proposal using max deadline', function () {
|
||||
it.only('Able to submit update asset proposal using max deadline', function () {
|
||||
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
|
||||
enterUpdateAssetProposalDetails();
|
||||
cy.get(maxVoteDeadline).click();
|
||||
|
||||
@@ -183,6 +183,7 @@ context(
|
||||
// 1004-ASSO-018
|
||||
// 1004-ASSO-024
|
||||
// 1004-ASSO-023
|
||||
// 1004-ASSO-032
|
||||
|
||||
stakingPageAssociateTokens('2', {
|
||||
type: 'contract',
|
||||
|
||||
@@ -84,3 +84,7 @@ export function verifyEthWalletAssociatedBalance(amount: string) {
|
||||
.parent(txTimeout)
|
||||
.should('contain', amount, txTimeout);
|
||||
}
|
||||
|
||||
export function closeDialog() {
|
||||
cy.getByTestId('dialog-close').click();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { navigateTo, navigation } from './common.functions';
|
||||
import { closeDialog, navigateTo, navigation } from './common.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
|
||||
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
@@ -12,7 +12,6 @@ const voteButtons = '[data-testid="vote-buttons"]';
|
||||
const dialogTitle = '[data-testid="dialog-title"]';
|
||||
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
|
||||
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
@@ -125,7 +124,7 @@ export function voteForProposal(vote: string) {
|
||||
'have.text',
|
||||
'Transaction complete'
|
||||
);
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function waitForProposalSync() {
|
||||
@@ -176,7 +175,7 @@ export function waitForProposalSubmitted() {
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('Proposal submitted', proposalTimeout).should('be.visible');
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function createRawProposal(proposerBalance?: string) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { closeDialog } from './common.functions';
|
||||
import { vegaWalletTeardown } from './wallet-teardown.functions';
|
||||
|
||||
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
|
||||
@@ -18,7 +19,6 @@ const stakeValidatorListTotalStake = 'total-stake';
|
||||
const stakeValidatorListTotalShare = 'total-stake-share';
|
||||
const stakeValidatorListName = '[col-id="validator"]';
|
||||
const vegaKeySelector = '#vega-key-selector';
|
||||
const dialogCloseButton = '[data-testid="dialog-close"]';
|
||||
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
@@ -54,7 +54,7 @@ export function stakingValidatorPageRemoveStake(stake: string) {
|
||||
.and('contain', `Remove ${stake} $VEGA tokens at the end of epoch`)
|
||||
.and('be.visible')
|
||||
.click();
|
||||
cy.get(dialogCloseButton).click();
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
export function stakingPageAssociateTokens(
|
||||
|
||||
@@ -13,6 +13,7 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -16,6 +16,7 @@ NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -10,3 +10,4 @@ NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -11,3 +11,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40:87edc2605e544f888305d7fc4a9141bd@o286262.ingest.sentry.io/5882996
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
|
||||
@@ -9,3 +9,4 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
@@ -6,3 +6,4 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-inter
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet3-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
@@ -11,3 +11,4 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ViewingAsBanner,
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import React from 'react';
|
||||
|
||||
@@ -15,18 +12,16 @@ export interface TemplateSidebarProps {
|
||||
}
|
||||
|
||||
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { VEGA_ENV, ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
return (
|
||||
<>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Wait no longer, SIM3 is here!</span>
|
||||
<ExternalLink href="https://fairground.wtf/sim3">
|
||||
Learn more
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
{ANNOUNCEMENTS_CONFIG_URL && (
|
||||
<AnnouncementBanner
|
||||
app="governance"
|
||||
configUrl={ANNOUNCEMENTS_CONFIG_URL}
|
||||
/>
|
||||
)}
|
||||
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
[
|
||||
{
|
||||
"tranche_id": 56,
|
||||
"tranche_start": "2023-04-20T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-20T00:00:00.000Z",
|
||||
"total_added": "0",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "0",
|
||||
"deposits": [],
|
||||
"withdrawals": [],
|
||||
"users": []
|
||||
},
|
||||
{
|
||||
"tranche_id": 55,
|
||||
"tranche_start": "2023-09-01T00:00:00.000Z",
|
||||
"tranche_end": "2024-03-01T00:00:00.000Z",
|
||||
"total_added": "5000",
|
||||
"total_added": "17500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "5000",
|
||||
"locked_amount": "17500",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12500",
|
||||
"user": "0x52dD21Bd0A8fBD31f4D5002c34b98d0c328791c8",
|
||||
"tx": "0x3d02aec9b4c1ff084ae4518b6a6f4647487c2724afc02c33f7dcd378d606f93f"
|
||||
},
|
||||
{
|
||||
"amount": "5000",
|
||||
"user": "0xbCDBCC3Ff54D659f2CC737bEf49B10D92dF3FDbf",
|
||||
@@ -15,6 +31,21 @@
|
||||
],
|
||||
"withdrawals": [],
|
||||
"users": [
|
||||
{
|
||||
"address": "0x52dD21Bd0A8fBD31f4D5002c34b98d0c328791c8",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12500",
|
||||
"user": "0x52dD21Bd0A8fBD31f4D5002c34b98d0c328791c8",
|
||||
"tranche_id": 55,
|
||||
"tx": "0x3d02aec9b4c1ff084ae4518b6a6f4647487c2724afc02c33f7dcd378d606f93f"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"total_tokens": "12500",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "12500"
|
||||
},
|
||||
{
|
||||
"address": "0xbCDBCC3Ff54D659f2CC737bEf49B10D92dF3FDbf",
|
||||
"deposits": [
|
||||
@@ -530,7 +561,7 @@
|
||||
"tranche_end": "2023-04-06T00:00:00.000Z",
|
||||
"total_added": "14099",
|
||||
"total_removed": "165.10784124668",
|
||||
"locked_amount": "3522.0180029121862637",
|
||||
"locked_amount": "1247.56462776284332054",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "30",
|
||||
@@ -3318,7 +3349,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "86666.297",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "59537.1325290923758345795",
|
||||
"locked_amount": "58349.7031275271807221145",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "86666.297",
|
||||
@@ -3384,7 +3415,7 @@
|
||||
"tranche_end": "2023-06-01T00:00:00.000Z",
|
||||
"total_added": "2500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "875.604300213675125",
|
||||
"locked_amount": "806.91026276963785",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "2500",
|
||||
@@ -3505,7 +3536,7 @@
|
||||
"tranche_end": "2023-09-01T00:00:00.000Z",
|
||||
"total_added": "17500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "14812.6080351751215",
|
||||
"locked_amount": "14336.97649330716525",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12500",
|
||||
@@ -3771,8 +3802,8 @@
|
||||
"tranche_start": "2023-02-01T00:00:00.000Z",
|
||||
"tranche_end": "2023-08-01T00:00:00.000Z",
|
||||
"total_added": "37500",
|
||||
"total_removed": "11535.201330975",
|
||||
"locked_amount": "25844.74994244935625",
|
||||
"total_removed": "11668.32172695",
|
||||
"locked_amount": "24808.64650475752125",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "7500",
|
||||
@@ -3801,6 +3832,11 @@
|
||||
"user": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600",
|
||||
"tx": "0x3f63318adc1797232c2578fa0f5eaccd21318427a3c243490817f778678bb6c6"
|
||||
},
|
||||
{
|
||||
"amount": "133.120395975",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0xf440dd4b160a155a08a3cc6e16706d5e4f9609bddd46c495217c377ef538c76f"
|
||||
},
|
||||
{
|
||||
"amount": "92.587476975",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -3921,6 +3957,12 @@
|
||||
"tranche_id": 34,
|
||||
"tx": "0xe021c767a9587b28a6772b57666d72122e5d869c477596e246a4365ee6b7ce43"
|
||||
},
|
||||
{
|
||||
"amount": "133.120395975",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 34,
|
||||
"tx": "0xf440dd4b160a155a08a3cc6e16706d5e4f9609bddd46c495217c377ef538c76f"
|
||||
},
|
||||
{
|
||||
"amount": "92.587476975",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -4031,8 +4073,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "7500",
|
||||
"withdrawn_tokens": "2299.752052575",
|
||||
"remaining_tokens": "5200.247947425"
|
||||
"withdrawn_tokens": "2432.87244855",
|
||||
"remaining_tokens": "5067.12755145"
|
||||
},
|
||||
{
|
||||
"address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600",
|
||||
@@ -4070,7 +4112,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "129999.45",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "59482.812386908832397903",
|
||||
"locked_amount": "58296.4663652644736979915",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129999.45",
|
||||
@@ -4103,7 +4145,7 @@
|
||||
"tranche_end": "2024-04-01T00:00:00.000Z",
|
||||
"total_added": "54144.7663",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "54144.7663",
|
||||
"locked_amount": "53810.8835054071292707386",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "54144.7663",
|
||||
@@ -4136,7 +4178,7 @@
|
||||
"tranche_end": "2023-09-03T00:00:00.000Z",
|
||||
"total_added": "62600",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "27054.17524733637532",
|
||||
"locked_amount": "26196.482198122780824",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10000",
|
||||
@@ -4329,7 +4371,7 @@
|
||||
"tranche_end": "2023-09-17T00:00:00.000Z",
|
||||
"total_added": "5000",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "2352.6574391171995",
|
||||
"locked_amount": "2284.15160451547415",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "5000",
|
||||
@@ -4540,7 +4582,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "97499.58",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "1569.29949985580425701666",
|
||||
"locked_amount": "405.60456575344483061187",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "97499.58",
|
||||
@@ -4572,8 +4614,8 @@
|
||||
"tranche_start": "2022-02-04T00:00:00.000Z",
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "135173.4239508",
|
||||
"total_removed": "98230.390980249184455396",
|
||||
"locked_amount": "2144.9614880467381205851871136",
|
||||
"total_removed": "133818.54965292963650862",
|
||||
"locked_amount": "554.3914166779487086967342868",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "135173.4239508",
|
||||
@@ -4582,6 +4624,11 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "35588.158672680452053224",
|
||||
"user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2",
|
||||
"tx": "0xb5e4c868526bc95258404cf26ce9d4dc8522e3a6352ebf503e3808023b45c90d"
|
||||
},
|
||||
{
|
||||
"amount": "98230.390980249184455396",
|
||||
"user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2",
|
||||
@@ -4600,6 +4647,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "35588.158672680452053224",
|
||||
"user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2",
|
||||
"tranche_id": 26,
|
||||
"tx": "0xb5e4c868526bc95258404cf26ce9d4dc8522e3a6352ebf503e3808023b45c90d"
|
||||
},
|
||||
{
|
||||
"amount": "98230.390980249184455396",
|
||||
"user": "0xc90eA4d8D214D548221EE3622a8BE1D61f7077A2",
|
||||
@@ -4608,8 +4661,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "135173.4239508",
|
||||
"withdrawn_tokens": "98230.390980249184455396",
|
||||
"remaining_tokens": "36943.032970550815544604"
|
||||
"withdrawn_tokens": "133818.54965292963650862",
|
||||
"remaining_tokens": "1354.87429787036349138"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -4619,7 +4672,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "32499.86",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "660.17719923653009843764",
|
||||
"locked_amount": "170.63083639627856110929",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "32499.86",
|
||||
@@ -4652,7 +4705,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "10833.29",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "214.88127214358664987998",
|
||||
"locked_amount": "55.53868148454478352929",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10833.29",
|
||||
@@ -4685,7 +4738,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "22749.93",
|
||||
"total_removed": "4720.860935375",
|
||||
"locked_amount": "803.27418813808789614012",
|
||||
"locked_amount": "207.61599572971184611278",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "6500",
|
||||
@@ -4836,8 +4889,8 @@
|
||||
"tranche_start": "2022-11-01T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-01T00:00:00.000Z",
|
||||
"total_added": "22500",
|
||||
"total_removed": "6111.900993675",
|
||||
"locked_amount": "4070.38587707182275",
|
||||
"total_removed": "6244.98110415",
|
||||
"locked_amount": "3448.723814456722875",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "7500",
|
||||
@@ -4861,6 +4914,11 @@
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x2ab0e85bffb0894e741265c4efb962d5f09831002daa4acc92ab87f72917bfe3"
|
||||
},
|
||||
{
|
||||
"amount": "133.080110475",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x0e47007cb85268a4e0c3c1143b96e4c98c9880060c4218babcadd0c82f8247a9"
|
||||
},
|
||||
{
|
||||
"amount": "92.593232025",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -5036,6 +5094,12 @@
|
||||
"tranche_id": 33,
|
||||
"tx": "0x2ab0e85bffb0894e741265c4efb962d5f09831002daa4acc92ab87f72917bfe3"
|
||||
},
|
||||
{
|
||||
"amount": "133.080110475",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 33,
|
||||
"tx": "0x0e47007cb85268a4e0c3c1143b96e4c98c9880060c4218babcadd0c82f8247a9"
|
||||
},
|
||||
{
|
||||
"amount": "92.593232025",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -5218,8 +5282,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "7500",
|
||||
"withdrawn_tokens": "6111.900993675",
|
||||
"remaining_tokens": "1388.099006325"
|
||||
"withdrawn_tokens": "6244.98110415",
|
||||
"remaining_tokens": "1255.01889585"
|
||||
},
|
||||
{
|
||||
"address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
|
||||
@@ -5244,7 +5308,7 @@
|
||||
"tranche_end": "2023-06-02T00:00:00.000Z",
|
||||
"total_added": "1939928.38",
|
||||
"total_removed": "928642.9598472029154",
|
||||
"locked_amount": "344106.0535972469579563914",
|
||||
"locked_amount": "317526.7710493525900617104",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1852091.69",
|
||||
@@ -8253,10 +8317,15 @@
|
||||
"tranche_id": 10,
|
||||
"tranche_start": "2021-07-15T23:37:11.000Z",
|
||||
"tranche_end": "2021-07-15T23:37:11.000Z",
|
||||
"total_added": "6359302.299000000000000001",
|
||||
"total_removed": "6313483.280000000000000001",
|
||||
"total_added": "6459302.299000000000000001",
|
||||
"total_removed": "6413483.280000000000000001",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab"
|
||||
},
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@@ -8789,6 +8858,11 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab"
|
||||
},
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@@ -9264,6 +9338,12 @@
|
||||
{
|
||||
"address": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tranche_id": 10,
|
||||
"tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab"
|
||||
},
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@@ -9458,6 +9538,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
"tranche_id": 10,
|
||||
"tx": "0x8d1ae9c167843c9e98404d3db6231aa82dbfb942d41b6b9af46d44c2a5e194ab"
|
||||
},
|
||||
{
|
||||
"amount": "100000",
|
||||
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
|
||||
@@ -9645,8 +9731,8 @@
|
||||
"tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc"
|
||||
}
|
||||
],
|
||||
"total_tokens": "3156651",
|
||||
"withdrawn_tokens": "3156651",
|
||||
"total_tokens": "3256651",
|
||||
"withdrawn_tokens": "3256651",
|
||||
"remaining_tokens": "0"
|
||||
},
|
||||
{
|
||||
@@ -10710,7 +10796,7 @@
|
||||
"tranche_id": 11,
|
||||
"tranche_start": "2021-09-03T00:00:00.000Z",
|
||||
"tranche_end": "2022-09-03T00:00:00.000Z",
|
||||
"total_added": "57188.000000000000000003",
|
||||
"total_added": "57213.000000000000000003",
|
||||
"total_removed": "48157.21518131551",
|
||||
"locked_amount": "0",
|
||||
"deposits": [
|
||||
@@ -10729,6 +10815,11 @@
|
||||
"user": "0xE9F41a0090fcc7eaf626037003AAD44B17098E7C",
|
||||
"tx": "0x971e9bcaace01278dc20ecd83d5de588d15be22c1797f4934cfd2ffb357a95ef"
|
||||
},
|
||||
{
|
||||
"amount": "25",
|
||||
"user": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D",
|
||||
"tx": "0x180cd3aca2d716aa233483a9a52b02e83d9ac88716ca85d998269965c7a01a96"
|
||||
},
|
||||
{
|
||||
"amount": "150",
|
||||
"user": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008",
|
||||
@@ -22517,6 +22608,21 @@
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "472"
|
||||
},
|
||||
{
|
||||
"address": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "25",
|
||||
"user": "0x363D3d06d70761c34D6EfEB25E409A9549E6970D",
|
||||
"tranche_id": 11,
|
||||
"tx": "0x180cd3aca2d716aa233483a9a52b02e83d9ac88716ca85d998269965c7a01a96"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"total_tokens": "25",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "25"
|
||||
},
|
||||
{
|
||||
"address": "0xEb01fF124D71b6C7E6613fd6E0A86c28C733F008",
|
||||
"deposits": [
|
||||
@@ -38935,7 +39041,7 @@
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "3732368.4671",
|
||||
"total_removed": "700133.348465855088393",
|
||||
"locked_amount": "553272.524105026240391145815",
|
||||
"locked_amount": "512429.420752227829460135708",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1998.95815",
|
||||
@@ -40316,8 +40422,8 @@
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "15870102.715470999700000001",
|
||||
"total_removed": "812130.55546472109935452",
|
||||
"locked_amount": "7261556.43262593709654026047091906912327654",
|
||||
"total_removed": "814470.78267520605435452",
|
||||
"locked_amount": "7116729.41051479466949006868895061288618547",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "16249.93",
|
||||
@@ -40831,6 +40937,26 @@
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0xa13046dbc8f011b7647728acbd7b9c6a67fb3869efc0e9e48c9590300345570a"
|
||||
},
|
||||
{
|
||||
"amount": "447.037825785733875",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0xe41c5aea235784efd3ce7c38abcaa73a915e4ad398fbd738c9cf76ee95487de4"
|
||||
},
|
||||
{
|
||||
"amount": "499.227386873675625",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x48e6b25d368a94770abfeffd5b28d7bb8b42269831394aa4b653c6eedd471909"
|
||||
},
|
||||
{
|
||||
"amount": "575.13946745617175",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x83497ea22d041f3b818b20cc112250ea3e38dc24b89a0bc5fee7198d6399a9cc"
|
||||
},
|
||||
{
|
||||
"amount": "818.82253036937375",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0x58bac20de3636c17156dcb64322cc7612c48f553d941bc039c796ef4167eeeb5"
|
||||
},
|
||||
{
|
||||
"amount": "545.4204142199725",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -42831,6 +42957,30 @@
|
||||
"tranche_id": 2,
|
||||
"tx": "0xa13046dbc8f011b7647728acbd7b9c6a67fb3869efc0e9e48c9590300345570a"
|
||||
},
|
||||
{
|
||||
"amount": "447.037825785733875",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 2,
|
||||
"tx": "0xe41c5aea235784efd3ce7c38abcaa73a915e4ad398fbd738c9cf76ee95487de4"
|
||||
},
|
||||
{
|
||||
"amount": "499.227386873675625",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 2,
|
||||
"tx": "0x48e6b25d368a94770abfeffd5b28d7bb8b42269831394aa4b653c6eedd471909"
|
||||
},
|
||||
{
|
||||
"amount": "575.13946745617175",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 2,
|
||||
"tx": "0x83497ea22d041f3b818b20cc112250ea3e38dc24b89a0bc5fee7198d6399a9cc"
|
||||
},
|
||||
{
|
||||
"amount": "818.82253036937375",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tranche_id": 2,
|
||||
"tx": "0x58bac20de3636c17156dcb64322cc7612c48f553d941bc039c796ef4167eeeb5"
|
||||
},
|
||||
{
|
||||
"amount": "545.4204142199725",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -44207,8 +44357,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "259998.8875",
|
||||
"withdrawn_tokens": "140674.575062959993",
|
||||
"remaining_tokens": "119324.312437040007"
|
||||
"withdrawn_tokens": "143014.802273444948",
|
||||
"remaining_tokens": "116984.085226555052"
|
||||
},
|
||||
{
|
||||
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
|
||||
@@ -46553,8 +46703,8 @@
|
||||
"tranche_start": "2021-11-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-05T00:00:00.000Z",
|
||||
"total_added": "14597706.0446472999",
|
||||
"total_removed": "5789694.057941893808930316",
|
||||
"locked_amount": "982377.306834362928705599632004937",
|
||||
"total_removed": "5802489.518043560526965506",
|
||||
"locked_amount": "848673.9247506211806821697552292785",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129284.449",
|
||||
@@ -46783,6 +46933,41 @@
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tx": "0xbe429d608aa735a1bea0099ee9806e2b0a293262199fa9881a3672215e68a6c3"
|
||||
},
|
||||
{
|
||||
"amount": "620.0068681531251345",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xa1dcf42aa5cdb34107d55521fd3b28c8dcea0a6a991ba7ec370f091c2a97a610"
|
||||
},
|
||||
{
|
||||
"amount": "4202.72107310637911484",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tx": "0x55259c6fbd43ea2b1e081dc4964c6785ed4d03bb0cafc49de2f07685b056f487"
|
||||
},
|
||||
{
|
||||
"amount": "691.90098422602867775",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xdf89247c6bdb472dcd750818d83cb70cbbe1ed660320ad1df6b1c94fb49f0161"
|
||||
},
|
||||
{
|
||||
"amount": "797.50404161762011275",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xf7c393e9346520e22750d3065cb66f49e315599b24406aed2b9570b5a25babe0"
|
||||
},
|
||||
{
|
||||
"amount": "5343.35381683275644592",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tx": "0x5a00c2a54f777e2d2ac0f487c6978d4c0edd8a2b146636e45ba94c6988a330f8"
|
||||
},
|
||||
{
|
||||
"amount": "4.83182614679928618",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tx": "0x14811c8d77a39b0331cb2fdc96049b462916877050cc73143c494277304c2496"
|
||||
},
|
||||
{
|
||||
"amount": "1135.14149158400926325",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0xae9047ab72d73da1001c570a12dddc5e82fd523454c1b377928e008da9bdfa85"
|
||||
},
|
||||
{
|
||||
"amount": "755.482067130393772",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@@ -49925,6 +50110,30 @@
|
||||
"tranche_id": 3,
|
||||
"tx": "0x5c9bcf8195dc2bf6d36af74095f45cc5f8289d7239e4205d74f06419b042d648"
|
||||
},
|
||||
{
|
||||
"amount": "620.0068681531251345",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0xa1dcf42aa5cdb34107d55521fd3b28c8dcea0a6a991ba7ec370f091c2a97a610"
|
||||
},
|
||||
{
|
||||
"amount": "691.90098422602867775",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0xdf89247c6bdb472dcd750818d83cb70cbbe1ed660320ad1df6b1c94fb49f0161"
|
||||
},
|
||||
{
|
||||
"amount": "797.50404161762011275",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0xf7c393e9346520e22750d3065cb66f49e315599b24406aed2b9570b5a25babe0"
|
||||
},
|
||||
{
|
||||
"amount": "1135.14149158400926325",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0xae9047ab72d73da1001c570a12dddc5e82fd523454c1b377928e008da9bdfa85"
|
||||
},
|
||||
{
|
||||
"amount": "755.482067130393772",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@@ -52603,8 +52812,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "359123.469575",
|
||||
"withdrawn_tokens": "334458.02309051305859075",
|
||||
"remaining_tokens": "24665.44648448694140925"
|
||||
"withdrawn_tokens": "337702.576476093841779",
|
||||
"remaining_tokens": "21420.893098906158221"
|
||||
},
|
||||
{
|
||||
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
|
||||
@@ -52846,6 +53055,24 @@
|
||||
"tranche_id": 3,
|
||||
"tx": "0xbe429d608aa735a1bea0099ee9806e2b0a293262199fa9881a3672215e68a6c3"
|
||||
},
|
||||
{
|
||||
"amount": "4202.72107310637911484",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tranche_id": 3,
|
||||
"tx": "0x55259c6fbd43ea2b1e081dc4964c6785ed4d03bb0cafc49de2f07685b056f487"
|
||||
},
|
||||
{
|
||||
"amount": "5343.35381683275644592",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tranche_id": 3,
|
||||
"tx": "0x5a00c2a54f777e2d2ac0f487c6978d4c0edd8a2b146636e45ba94c6988a330f8"
|
||||
},
|
||||
{
|
||||
"amount": "4.83182614679928618",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
"tranche_id": 3,
|
||||
"tx": "0x14811c8d77a39b0331cb2fdc96049b462916877050cc73143c494277304c2496"
|
||||
},
|
||||
{
|
||||
"amount": "80421.5336580481619385",
|
||||
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
|
||||
@@ -53178,8 +53405,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "1266324.603486",
|
||||
"withdrawn_tokens": "1179718.62954110669784558",
|
||||
"remaining_tokens": "86605.97394489330215442"
|
||||
"withdrawn_tokens": "1189269.53625719263269252",
|
||||
"remaining_tokens": "77055.06722880736730748"
|
||||
},
|
||||
{
|
||||
"address": "0xC5d9221EB9c28A69859264c0A2Fe0d3272228296",
|
||||
@@ -54008,7 +54235,7 @@
|
||||
"tranche_end": "2023-04-05T00:00:00.000Z",
|
||||
"total_added": "5778205.3912159303",
|
||||
"total_removed": "3381706.364737906998719578",
|
||||
"locked_amount": "71239.811758562587976996363562694",
|
||||
"locked_amount": "18412.7968659549219832486449580318",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "552496.6455",
|
||||
@@ -56135,8 +56362,8 @@
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "472355.6199999996",
|
||||
"total_removed": "38870.3290450237949",
|
||||
"locked_amount": "87669.194085020840174271210350088",
|
||||
"total_removed": "40152.1324861581949",
|
||||
"locked_amount": "81197.370889637811460062778488072",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "3000",
|
||||
@@ -62770,6 +62997,51 @@
|
||||
"user": "0xdc040917f80aC350460e55467e4B6c52Fc5e220E",
|
||||
"tx": "0xd1ded90c18e54cae27c885ec52af8db6260e3eebfd23eb61092de4ad4d20c301"
|
||||
},
|
||||
{
|
||||
"amount": "327.28491882",
|
||||
"user": "0x872502542bbE5115004B85e230dfA7436245eBEd",
|
||||
"tx": "0x40aee0670fcfcf9c5514679f7207a6701cd301ddc2ffa2d97af13b92d5e948ef"
|
||||
},
|
||||
{
|
||||
"amount": "0.001065448",
|
||||
"user": "0x872502542bbE5115004B85e230dfA7436245eBEd",
|
||||
"tx": "0xe3e87e4860455f3d794d59458dee4622fb4727c49290b747ddf791971d2c9066"
|
||||
},
|
||||
{
|
||||
"amount": "327.28796296",
|
||||
"user": "0x47F67fF46d5F8Aad8Cc2D77C6Fd893d71350EebE",
|
||||
"tx": "0x8ceeb54fd69be5d8887aad5bf4d628c274319ee3d9a88db939281108f548623e"
|
||||
},
|
||||
{
|
||||
"amount": "327.288571788",
|
||||
"user": "0xB0473C4FeBd722a6d7eE561329d3134030318132",
|
||||
"tx": "0xf41899ffea2bf68e78f747eb32043d983cf3803bd91ce550a70883f2b46f62b7"
|
||||
},
|
||||
{
|
||||
"amount": "28.223592084",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
"tx": "0x4c003445f6506b21e28176229f41ae8dcb2d3feafda14d76081aa6b46996069b"
|
||||
},
|
||||
{
|
||||
"amount": "26.371004566",
|
||||
"user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b",
|
||||
"tx": "0x93540a3930099c8b54157bb83ae50c8ced993009ae2091888f85704e6f13869c"
|
||||
},
|
||||
{
|
||||
"amount": "164.310191526",
|
||||
"user": "0xfA580dabBa10A5Ef817C7dc26Aaa5fF579D45a9D",
|
||||
"tx": "0xf1df34ff15a8c7238e188632cf69532edb92f0d8203bf3031e5d19338cb5a9ca"
|
||||
},
|
||||
{
|
||||
"amount": "58.7761643844",
|
||||
"user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B",
|
||||
"tx": "0xda5812fda239af9581d2d60c8f4b959e0a8cc5466e5422c5b8fa9df72435d3bc"
|
||||
},
|
||||
{
|
||||
"amount": "22.259969558",
|
||||
"user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585",
|
||||
"tx": "0x14da65fbf5ae2f3a593036daa58b3353ab88fc22666e5514f9160e252ad2fdf9"
|
||||
},
|
||||
{
|
||||
"amount": "154.812094114",
|
||||
"user": "0x27e2254A2A8c9c9D1321E5Fe64cAD88e7A4f0ba7",
|
||||
@@ -82016,6 +82288,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "58.7761643844",
|
||||
"user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B",
|
||||
"tranche_id": 5,
|
||||
"tx": "0xda5812fda239af9581d2d60c8f4b959e0a8cc5466e5422c5b8fa9df72435d3bc"
|
||||
},
|
||||
{
|
||||
"amount": "20.679657534",
|
||||
"user": "0xED71B9A9b5633e9d31A0986693658CBbf23c3c1B",
|
||||
@@ -82030,8 +82308,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "180",
|
||||
"withdrawn_tokens": "89.6315011398",
|
||||
"remaining_tokens": "90.3684988602"
|
||||
"withdrawn_tokens": "148.4076655242",
|
||||
"remaining_tokens": "31.5923344758"
|
||||
},
|
||||
{
|
||||
"address": "0x68927b7A4A360f5B615e104BA2cA2E264a563A33",
|
||||
@@ -82745,10 +83023,17 @@
|
||||
"tx": "0xc8541da6a57f410b6faba47a5e5184bae700193b7bd042914fffc562114d92f5"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "164.310191526",
|
||||
"user": "0xfA580dabBa10A5Ef817C7dc26Aaa5fF579D45a9D",
|
||||
"tranche_id": 5,
|
||||
"tx": "0xf1df34ff15a8c7238e188632cf69532edb92f0d8203bf3031e5d19338cb5a9ca"
|
||||
}
|
||||
],
|
||||
"total_tokens": "200",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "200"
|
||||
"withdrawn_tokens": "164.310191526",
|
||||
"remaining_tokens": "35.689808474"
|
||||
},
|
||||
{
|
||||
"address": "0x40c9DF6b867cA219c0Ab4C2E390E8B919Be52e7e",
|
||||
@@ -84305,6 +84590,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "22.259969558",
|
||||
"user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x14da65fbf5ae2f3a593036daa58b3353ab88fc22666e5514f9160e252ad2fdf9"
|
||||
},
|
||||
{
|
||||
"amount": "17.060806698",
|
||||
"user": "0x28FC83947F02f59Cb36b40f97f2D32BBC5D00585",
|
||||
@@ -84337,8 +84628,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "200",
|
||||
"withdrawn_tokens": "142.641089548",
|
||||
"remaining_tokens": "57.358910452"
|
||||
"withdrawn_tokens": "164.901059106",
|
||||
"remaining_tokens": "35.098940894"
|
||||
},
|
||||
{
|
||||
"address": "0xBef628af8547cE5264835F32487055a7e3dF393D",
|
||||
@@ -84866,6 +85157,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "26.371004566",
|
||||
"user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x93540a3930099c8b54157bb83ae50c8ced993009ae2091888f85704e6f13869c"
|
||||
},
|
||||
{
|
||||
"amount": "57.900076104",
|
||||
"user": "0x96d882E908C06cD697Ea07266Fb8D14ae129A50b",
|
||||
@@ -84886,8 +85183,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "200",
|
||||
"withdrawn_tokens": "137.635305682",
|
||||
"remaining_tokens": "62.364694318"
|
||||
"withdrawn_tokens": "164.006310248",
|
||||
"remaining_tokens": "35.993689752"
|
||||
},
|
||||
{
|
||||
"address": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
@@ -84900,6 +85197,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "28.223592084",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x4c003445f6506b21e28176229f41ae8dcb2d3feafda14d76081aa6b46996069b"
|
||||
},
|
||||
{
|
||||
"amount": "41.084474888",
|
||||
"user": "0xD27929d68ac0E5fd5C919A5eb5968C1D06D3Fb83",
|
||||
@@ -84962,8 +85265,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "299.769393708",
|
||||
"remaining_tokens": "100.230606292"
|
||||
"withdrawn_tokens": "327.992985792",
|
||||
"remaining_tokens": "72.007014208"
|
||||
},
|
||||
{
|
||||
"address": "0xF5037DDA4A660d67560200f45380FF8364e35540",
|
||||
@@ -85252,10 +85555,17 @@
|
||||
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "327.288571788",
|
||||
"user": "0xB0473C4FeBd722a6d7eE561329d3134030318132",
|
||||
"tranche_id": 5,
|
||||
"tx": "0xf41899ffea2bf68e78f747eb32043d983cf3803bd91ce550a70883f2b46f62b7"
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "400"
|
||||
"withdrawn_tokens": "327.288571788",
|
||||
"remaining_tokens": "72.711428212"
|
||||
},
|
||||
{
|
||||
"address": "0x8aBD57F15cB5f8BD8949EE90c67f77B680caB645",
|
||||
@@ -85282,10 +85592,17 @@
|
||||
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "327.28796296",
|
||||
"user": "0x47F67fF46d5F8Aad8Cc2D77C6Fd893d71350EebE",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x8ceeb54fd69be5d8887aad5bf4d628c274319ee3d9a88db939281108f548623e"
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "400"
|
||||
"withdrawn_tokens": "327.28796296",
|
||||
"remaining_tokens": "72.71203704"
|
||||
},
|
||||
{
|
||||
"address": "0x872502542bbE5115004B85e230dfA7436245eBEd",
|
||||
@@ -85297,10 +85614,23 @@
|
||||
"tx": "0xe32a466fc780a0fb3fd84a804f622931ebfaf3f428bff0dc6d141270410e75f8"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "327.28491882",
|
||||
"user": "0x872502542bbE5115004B85e230dfA7436245eBEd",
|
||||
"tranche_id": 5,
|
||||
"tx": "0x40aee0670fcfcf9c5514679f7207a6701cd301ddc2ffa2d97af13b92d5e948ef"
|
||||
},
|
||||
{
|
||||
"amount": "0.001065448",
|
||||
"user": "0x872502542bbE5115004B85e230dfA7436245eBEd",
|
||||
"tranche_id": 5,
|
||||
"tx": "0xe3e87e4860455f3d794d59458dee4622fb4727c49290b747ddf791971d2c9066"
|
||||
}
|
||||
],
|
||||
"total_tokens": "400",
|
||||
"withdrawn_tokens": "0",
|
||||
"remaining_tokens": "400"
|
||||
"withdrawn_tokens": "327.285984268",
|
||||
"remaining_tokens": "72.714015732"
|
||||
},
|
||||
{
|
||||
"address": "0x3f8CEEE4f53d8FFBABbA13E3E90334c6702A8440",
|
||||
|
||||
@@ -19,6 +19,7 @@ CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||
CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||
CYPRESS_VEGA_ENV=CUSTOM
|
||||
|
||||
@@ -282,7 +282,6 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
// comment because of bug #2695
|
||||
it('can edit order', function () {
|
||||
cy.getByTestId(ordersTab).click();
|
||||
cy.getByTestId('edit').first().should('be.visible').click();
|
||||
|
||||
@@ -52,6 +52,8 @@ describe('Console - market list - live env', { tags: '@live' }, () => {
|
||||
describe('Console - market info - live env', { tags: '@live' }, () => {
|
||||
before(() => {
|
||||
cy.visit('/');
|
||||
cy.contains('Loading market data...').should('not.exist');
|
||||
cy.getByTestId('link').should('be.visible');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.getByTestId(marketInfoBtn).click();
|
||||
});
|
||||
@@ -68,17 +70,20 @@ describe('Console - market info - live env', { tags: '@live' }, () => {
|
||||
'Risk model',
|
||||
'Risk parameters',
|
||||
'Risk factors',
|
||||
'Price monitoring trigger 1',
|
||||
'Price monitoring bounds 1',
|
||||
'Liquidity monitoring parameters',
|
||||
'Liquidity',
|
||||
'Liquidity price range',
|
||||
'Oracle',
|
||||
'Proposal',
|
||||
];
|
||||
|
||||
it('market info titles are displayed', () => {
|
||||
cy.get('.text-lg').each((element, index) => {
|
||||
cy.wrap(element).should('have.text', titles[index]);
|
||||
});
|
||||
cy.getByTestId('split-view-view')
|
||||
.find('.text-lg')
|
||||
.each((element, index) => {
|
||||
cy.wrap(element).should('have.text', titles[index]);
|
||||
});
|
||||
});
|
||||
|
||||
it('market info subtitles are displayed', () => {
|
||||
@@ -90,6 +95,16 @@ describe('Console - market info - live env', { tags: '@live' }, () => {
|
||||
cy.wrap(element).should('have.text', subtitles[index]);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders correctly liquidity in trading tab', () => {
|
||||
cy.getByTestId('Liquidity').click();
|
||||
cy.contains('Loading').should('not.exist');
|
||||
cy.contains('Something went wrong').should('not.exist');
|
||||
cy.contains('Application error').should('not.exist');
|
||||
cy.getByTestId('tab-liquidity').within(() => {
|
||||
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Console - market summary - live env', { tags: '@live' }, () => {
|
||||
@@ -205,6 +220,8 @@ describe('Console - markets table - live env', { tags: '@live' }, () => {
|
||||
});
|
||||
|
||||
function openMarketDropDown() {
|
||||
cy.contains('Loading...').should('not.exist');
|
||||
cy.getByTestId('link').should('be.visible');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.getByTestId('popover-trigger').click();
|
||||
cy.contains('Loading market data...').should('not.exist');
|
||||
|
||||
@@ -75,8 +75,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(3, 'Quote Name', 'BTC');
|
||||
});
|
||||
|
||||
// need to check why data are not visible
|
||||
it.skip('settlement asset displayed', () => {
|
||||
it('settlement asset displayed', () => {
|
||||
cy.getByTestId(marketTitle).contains('Settlement asset').click();
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
|
||||
@@ -181,7 +180,15 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
'termination.BTC.value'
|
||||
);
|
||||
|
||||
// check that links to github for oracle proofs are shown
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId('oracle-proof-links')
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://github.com/vegaprotocol/well-known');
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId('oracle-spec-links')
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/oracles');
|
||||
|
||||
@@ -84,6 +84,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -98,6 +100,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order);
|
||||
@@ -112,6 +116,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -126,6 +132,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GFN,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '50000',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -143,6 +151,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
size: '100',
|
||||
price: '1.00',
|
||||
expiresAt: expiresAt.toISOString().substring(0, 16),
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
|
||||
createOrder(order);
|
||||
@@ -150,6 +160,8 @@ describe('must submit order', { tags: '@smoke' }, () => {
|
||||
price: '100000',
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -182,6 +194,8 @@ describe(
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -196,6 +210,8 @@ describe(
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '50000',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -210,12 +226,16 @@ describe(
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '1.00',
|
||||
expiresAt: displayTomorrow(),
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
});
|
||||
@@ -251,6 +271,8 @@ describe(
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -265,6 +287,8 @@ describe(
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '50000',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -281,6 +305,8 @@ describe(
|
||||
size: '100',
|
||||
price: '1.00',
|
||||
expiresAt: displayTomorrow(),
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
@@ -320,6 +346,8 @@ describe(
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
@@ -335,6 +363,8 @@ describe(
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
price: '50000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, { price: '5000000000' });
|
||||
@@ -350,6 +380,8 @@ describe(
|
||||
size: '100',
|
||||
price: '1.00',
|
||||
expiresAt: displayTomorrow(),
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
|
||||
@@ -35,6 +35,8 @@ type MarketPageMockData = {
|
||||
trigger?: Schema.AuctionTrigger;
|
||||
};
|
||||
|
||||
const ORACLE_PUBKEY = Cypress.env('ORACLE_PUBKEY');
|
||||
|
||||
const marketDataOverride = (
|
||||
data: MarketPageMockData
|
||||
): PartialDeep<MarketDataQuery> => ({
|
||||
@@ -96,7 +98,54 @@ const mockTradingPage = (
|
||||
aliasGQLQuery(req, 'Margins', marginsQuery());
|
||||
aliasGQLQuery(req, 'Assets', assetsQuery());
|
||||
aliasGQLQuery(req, 'Asset', assetQuery());
|
||||
aliasGQLQuery(req, 'MarketInfo', marketInfoQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'MarketInfo',
|
||||
marketInfoQuery({
|
||||
market: {
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
dataSourceSpecForSettlementData: {
|
||||
data: {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: ORACLE_PUBKEY,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
data: {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: ORACLE_PUBKEY,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(req, 'Trades', tradesQuery());
|
||||
aliasGQLQuery(req, 'Chart', chartQuery());
|
||||
aliasGQLQuery(req, 'Candles', candlesQuery());
|
||||
@@ -127,6 +176,40 @@ export const addMockTradingPage = () => {
|
||||
cy.mockGQL((req) => {
|
||||
mockTradingPage(req, state, tradingMode, trigger);
|
||||
});
|
||||
|
||||
// Prevent request to github, return some dummy content
|
||||
cy.intercept(
|
||||
'GET',
|
||||
/^https:\/\/raw.githubusercontent.com\/vegaprotocol\/well-known/,
|
||||
{
|
||||
body: [
|
||||
{
|
||||
name: 'Another oracle',
|
||||
url: 'https://zombo.com',
|
||||
description_markdown:
|
||||
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||
oracle: {
|
||||
status: 'GOOD',
|
||||
status_reason: '',
|
||||
first_verified: '2022-01-01T00:00:00.000Z',
|
||||
last_verified: '2022-12-31T00:00:00.000Z',
|
||||
type: 'public_key',
|
||||
public_key: ORACLE_PUBKEY,
|
||||
},
|
||||
proofs: [
|
||||
{
|
||||
format: 'signed_message',
|
||||
available: true,
|
||||
type: 'public_key',
|
||||
public_key: ORACLE_PUBKEY,
|
||||
message: 'SOMEHEX',
|
||||
},
|
||||
],
|
||||
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/public_key-${ORACLE_PUBKEY}.toml`,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +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_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||
@@ -10,3 +11,4 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
@@ -11,6 +11,7 @@ NX_VEGA_URL=http://localhost:3028/query
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
@@ -10,4 +10,5 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
@@ -9,4 +9,5 @@ NX_VEGA_TOKEN_URL=https://token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
@@ -10,4 +10,5 @@ NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
@@ -10,4 +10,5 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
@@ -10,4 +10,5 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
@@ -10,4 +10,5 @@ NX_VEGA_TOKEN_URL=https://validator-testnet.governance.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
getId,
|
||||
liquidityProvisionsDataProvider,
|
||||
LiquidityTable,
|
||||
lpAggregatedDataProvider,
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
Indicator,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
|
||||
|
||||
@@ -37,6 +38,13 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
const enum LiquidityTabs {
|
||||
Active = 'active',
|
||||
Inactive = 'inactive',
|
||||
MyLiquidityProvision = 'myLP',
|
||||
}
|
||||
|
||||
export const Liquidity = () => {
|
||||
const params = useParams();
|
||||
@@ -48,10 +56,11 @@ const useReloadLiquidityData = (marketId: string | undefined) => {
|
||||
const { reload } = useDataProvider({
|
||||
dataProvider: liquidityProvisionsDataProvider,
|
||||
variables: { marketId: marketId || '' },
|
||||
update: () => true,
|
||||
skip: !marketId,
|
||||
});
|
||||
useEffect(() => {
|
||||
const interval = setInterval(reload, 10000);
|
||||
const interval = setInterval(reload, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [reload]);
|
||||
};
|
||||
@@ -126,47 +135,9 @@ export const LiquidityContainer = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const LiquidityViewContainer = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId: string | undefined;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
const { data: market } = useMarket(marketId);
|
||||
const { data: marketData } = useStaticMarketData(marketId);
|
||||
|
||||
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
|
||||
|
||||
// To be removed when liquidityProvision subscriptions are working
|
||||
useReloadLiquidityData(marketId);
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: LiquidityProvisionData[] | null }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
if (dataRef.current?.length) {
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const {
|
||||
data: liquidityProviders,
|
||||
loading,
|
||||
error,
|
||||
} = useDataProvider({
|
||||
dataProvider: lpAggregatedDataProvider,
|
||||
update,
|
||||
variables: { marketId: marketId || '' },
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const targetStake = marketData?.targetStake;
|
||||
const suppliedStake = marketData?.suppliedStake;
|
||||
const assetDecimalPlaces =
|
||||
@@ -178,44 +149,8 @@ export const LiquidityViewContainer = ({
|
||||
NetworkParams.market_liquidity_stakeToCcyVolume,
|
||||
NetworkParams.market_liquidity_targetstake_triggering_ratio,
|
||||
]);
|
||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||
const triggeringRatio =
|
||||
params.market_liquidity_targetstake_triggering_ratio || '1';
|
||||
const myLpEdges = useMemo(
|
||||
() => liquidityProviders?.filter((e) => e.party.id === pubKey),
|
||||
[liquidityProviders, pubKey]
|
||||
);
|
||||
const activeEdges = useMemo(
|
||||
() =>
|
||||
liquidityProviders?.filter(
|
||||
(e) => e.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE
|
||||
),
|
||||
[liquidityProviders]
|
||||
);
|
||||
const inactiveEdges = useMemo(
|
||||
() =>
|
||||
liquidityProviders?.filter(
|
||||
(e) => e.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE
|
||||
),
|
||||
[liquidityProviders]
|
||||
);
|
||||
|
||||
const enum LiquidityTabs {
|
||||
Active = 'active',
|
||||
Inactive = 'inactive',
|
||||
MyLiquidityProvision = 'myLP',
|
||||
}
|
||||
|
||||
const getActiveDefaultId = () => {
|
||||
if (myLpEdges && myLpEdges.length > 0) {
|
||||
return LiquidityTabs.MyLiquidityProvision;
|
||||
}
|
||||
if (activeEdges?.length) return LiquidityTabs.Active;
|
||||
else if (inactiveEdges && inactiveEdges.length > 0) {
|
||||
return LiquidityTabs.Inactive;
|
||||
}
|
||||
return LiquidityTabs.Active;
|
||||
};
|
||||
|
||||
const { percentage, status } = useCheckLiquidityStatus({
|
||||
suppliedStake: suppliedStake || 0,
|
||||
@@ -223,66 +158,197 @@ export const LiquidityViewContainer = ({
|
||||
triggeringRatio,
|
||||
});
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
market?.tradableInstrument.instrument.name &&
|
||||
market?.tradableInstrument.instrument.code &&
|
||||
marketId && (
|
||||
<HeaderTitle
|
||||
primaryContent={`${market.tradableInstrument.instrument.code} ${t(
|
||||
'liquidity provision'
|
||||
)}`}
|
||||
secondaryContent={
|
||||
<Link to={Links[Routes.MARKET](marketId)}>
|
||||
<UiToolkitLink>{t('Go to trading')}</UiToolkitLink>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<HeaderStat
|
||||
heading={t('Target stake')}
|
||||
description={tooltipMapping['targetStake']}
|
||||
>
|
||||
<div>
|
||||
{targetStake
|
||||
? `${addDecimalsFormatNumber(
|
||||
targetStake,
|
||||
assetDecimalPlaces ?? 0
|
||||
)} ${symbol}`
|
||||
: '-'}
|
||||
</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat
|
||||
heading={t('Supplied stake')}
|
||||
description={tooltipMapping['suppliedStake']}
|
||||
>
|
||||
<div>
|
||||
{suppliedStake
|
||||
? `${addDecimalsFormatNumber(
|
||||
suppliedStake,
|
||||
assetDecimalPlaces ?? 0
|
||||
)} ${symbol}`
|
||||
: '-'}
|
||||
</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Liquidity supplied')} testId="liquidity-supplied">
|
||||
<Indicator variant={status} />
|
||||
|
||||
{formatNumberPercentage(percentage, 2)}
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Market ID')}>
|
||||
<div className="break-word">{marketId}</div>
|
||||
</HeaderStat>
|
||||
</Header>
|
||||
);
|
||||
});
|
||||
LiquidityViewHeader.displayName = 'LiquidityViewHeader';
|
||||
|
||||
const filterLiquidities = (
|
||||
tab: string,
|
||||
liquidities?: LiquidityProvisionData[] | null,
|
||||
pubKey?: string | null
|
||||
) => {
|
||||
switch (tab) {
|
||||
case LiquidityTabs.MyLiquidityProvision:
|
||||
return pubKey
|
||||
? (liquidities || []).filter((e) => e.party.id === pubKey)
|
||||
: [];
|
||||
break;
|
||||
case LiquidityTabs.Active:
|
||||
return (liquidities || []).filter(
|
||||
(e) => e.status === Schema.LiquidityProvisionStatus.STATUS_ACTIVE
|
||||
);
|
||||
case LiquidityTabs.Inactive:
|
||||
return (liquidities || []).filter(
|
||||
(e) => e.status !== Schema.LiquidityProvisionStatus.STATUS_ACTIVE
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const LiquidityViewContainer = ({
|
||||
marketId,
|
||||
}: {
|
||||
marketId: string | undefined;
|
||||
}) => {
|
||||
const [tab, setTab] = useState('');
|
||||
const { pubKey } = useVegaWallet();
|
||||
const [liquidityProviders, setLiquidityProviders] = useState<
|
||||
LiquidityProvisionData[] | null
|
||||
>();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data: market } = useMarket(marketId);
|
||||
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
|
||||
|
||||
// To be removed when liquidityProvision subscriptions are working
|
||||
useReloadLiquidityData(marketId);
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: LiquidityProvisionData[] | null }) => {
|
||||
if (!dataRef.current) {
|
||||
setLiquidityProviders(data);
|
||||
dataRef.current = data;
|
||||
}
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
const updateRows: LiquidityProvisionData[] = [];
|
||||
const addRows: LiquidityProvisionData[] = [];
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
dataRef.current = data;
|
||||
gridRef.current.api.refreshInfiniteCache();
|
||||
} else {
|
||||
const filteredData = filterLiquidities(tab, data, pubKey as string);
|
||||
filteredData?.forEach((d) => {
|
||||
const rowNode = gridRef.current?.api?.getRowNode(getId(d));
|
||||
if (rowNode) {
|
||||
if (!isEqual(rowNode.data, d)) {
|
||||
updateRows.push(d);
|
||||
}
|
||||
} else {
|
||||
addRows.push(d);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api?.applyTransaction({
|
||||
update: updateRows,
|
||||
add: addRows,
|
||||
addIndex: 0,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[gridRef, tab, pubKey]
|
||||
);
|
||||
|
||||
const { loading, error } = useDataProvider({
|
||||
dataProvider: lpAggregatedDataProvider,
|
||||
update,
|
||||
variables: { marketId: marketId || '' },
|
||||
skip: !marketId,
|
||||
});
|
||||
const assetDecimalPlaces =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
|
||||
const symbol =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.market_liquidity_stakeToCcyVolume,
|
||||
NetworkParams.market_liquidity_targetstake_triggering_ratio,
|
||||
]);
|
||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||
const myLpEdges = useMemo(
|
||||
() =>
|
||||
filterLiquidities(
|
||||
LiquidityTabs.MyLiquidityProvision,
|
||||
liquidityProviders,
|
||||
pubKey
|
||||
),
|
||||
[liquidityProviders, pubKey]
|
||||
);
|
||||
const activeEdges = useMemo(
|
||||
() => filterLiquidities(LiquidityTabs.Active, liquidityProviders),
|
||||
[liquidityProviders]
|
||||
);
|
||||
const inactiveEdges = useMemo(
|
||||
() => filterLiquidities(LiquidityTabs.Inactive, liquidityProviders),
|
||||
[liquidityProviders]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab) {
|
||||
return;
|
||||
}
|
||||
let initialTab = LiquidityTabs.Active;
|
||||
if (myLpEdges.length > 0) {
|
||||
initialTab = LiquidityTabs.MyLiquidityProvision;
|
||||
}
|
||||
if (activeEdges?.length) {
|
||||
initialTab = LiquidityTabs.Active;
|
||||
} else if (inactiveEdges.length > 0) {
|
||||
initialTab = LiquidityTabs.Inactive;
|
||||
}
|
||||
setTab(initialTab);
|
||||
}, [tab, myLpEdges?.length, activeEdges?.length, inactiveEdges?.length]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={liquidityProviders}>
|
||||
<div className="h-full grid grid-rows-[min-content_1fr]">
|
||||
<Header
|
||||
title={
|
||||
market?.tradableInstrument.instrument.name &&
|
||||
market?.tradableInstrument.instrument.code &&
|
||||
marketId && (
|
||||
<HeaderTitle
|
||||
primaryContent={`${
|
||||
market.tradableInstrument.instrument.code
|
||||
} ${t('liquidity provision')}`}
|
||||
secondaryContent={
|
||||
<Link to={Links[Routes.MARKET](marketId)}>
|
||||
<UiToolkitLink>{t('Go to trading')}</UiToolkitLink>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<HeaderStat
|
||||
heading={t('Target stake')}
|
||||
description={tooltipMapping['targetStake']}
|
||||
>
|
||||
<div>
|
||||
{targetStake
|
||||
? `${addDecimalsFormatNumber(
|
||||
targetStake,
|
||||
assetDecimalPlaces ?? 0
|
||||
)} ${symbol}`
|
||||
: '-'}
|
||||
</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat
|
||||
heading={t('Supplied stake')}
|
||||
description={tooltipMapping['suppliedStake']}
|
||||
>
|
||||
<div>
|
||||
{suppliedStake
|
||||
? `${addDecimalsFormatNumber(
|
||||
suppliedStake,
|
||||
assetDecimalPlaces ?? 0
|
||||
)} ${symbol}`
|
||||
: '-'}
|
||||
</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat
|
||||
heading={t('Liquidity supplied')}
|
||||
testId="liquidity-supplied"
|
||||
>
|
||||
<Indicator variant={status} />
|
||||
|
||||
{formatNumberPercentage(percentage, 2)}
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Market ID')}>
|
||||
<div className="break-word">{marketId}</div>
|
||||
</HeaderStat>
|
||||
</Header>
|
||||
<Tabs defaultValue={getActiveDefaultId()}>
|
||||
<LiquidityViewHeader marketId={marketId} />
|
||||
<Tabs value={tab} onValueChange={setTab}>
|
||||
<Tab
|
||||
id={LiquidityTabs.MyLiquidityProvision}
|
||||
name={t('My liquidity provision')}
|
||||
@@ -309,19 +375,17 @@ export const LiquidityViewContainer = ({
|
||||
/>
|
||||
)}
|
||||
</Tab>
|
||||
{
|
||||
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
|
||||
{inactiveEdges && (
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={inactiveEdges}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
/>
|
||||
)}
|
||||
</Tab>
|
||||
}
|
||||
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
|
||||
{inactiveEdges && (
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
rowData={inactiveEdges}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
/>
|
||||
)}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</AsyncRenderer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||
import { TradeGrid, TradePanels } from './trade-grid';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||
return markPrice && decimalPlaces
|
||||
@@ -66,14 +67,7 @@ export const MarketPage = () => {
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(id: string) => {
|
||||
if (id && id !== marketId) {
|
||||
navigate(Links[Routes.MARKET](id));
|
||||
}
|
||||
},
|
||||
[marketId, navigate]
|
||||
);
|
||||
const onSelect = useMarketClickHandler();
|
||||
|
||||
const { data, error, loading } = useDataProvider({
|
||||
dataProvider: marketProvider,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { LayoutPriority } from 'allotment';
|
||||
import classNames from 'classnames';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import type { ReactNode, ComponentProps } from 'react';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
@@ -27,9 +27,9 @@ import { TradeMarketHeader } from './trade-market-header';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { LiquidityContainer } from '../liquidity/liquidity';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -66,7 +66,7 @@ type TradingView = keyof typeof TradingViews;
|
||||
|
||||
interface TradeGridProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string) => void;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
@@ -78,15 +78,7 @@ interface BottomPanelProps {
|
||||
const MarketBottomPanel = memo(
|
||||
({ marketId, pinnedAsset }: BottomPanelProps) => {
|
||||
const { screenSize } = useScreenDimensions();
|
||||
const navigate = useNavigate();
|
||||
const onMarketClick = useCallback(
|
||||
(marketId: string) => {
|
||||
navigate(Links[Routes.MARKET](marketId), {
|
||||
replace: true,
|
||||
});
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
|
||||
return 'xxxl' === screenSize ? (
|
||||
<ResizableGrid proportionalLayout minSize={200}>
|
||||
@@ -189,7 +181,7 @@ const MainGrid = memo(
|
||||
pinnedAsset,
|
||||
}: {
|
||||
marketId: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
@@ -230,12 +222,7 @@ const MainGrid = memo(
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="info" name={t('Info')}>
|
||||
<TradingViews.Info
|
||||
marketId={marketId}
|
||||
onSelect={(id: string) => {
|
||||
onSelect?.(id);
|
||||
}}
|
||||
/>
|
||||
<TradingViews.Info marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -304,7 +291,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
|
||||
|
||||
interface TradePanelsProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string) => void;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
@@ -320,7 +307,7 @@ export const TradePanels = ({
|
||||
const renderView = () => {
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
onSelect: (marketId: string) => void;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
|
||||
@@ -22,7 +22,7 @@ import { MarketState as State } from '@vegaprotocol/types';
|
||||
|
||||
interface TradeMarketHeaderProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string) => void;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const TradeMarketHeader = ({
|
||||
@@ -91,7 +91,6 @@ export const TradeMarketHeader = ({
|
||||
</HeaderStat>
|
||||
<HeaderStatMarketTradingMode
|
||||
marketId={market?.id}
|
||||
onSelect={onSelect}
|
||||
initialTradingMode={market?.tradingMode}
|
||||
/>
|
||||
<MarketState market={market} />
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { MarketsContainer } from '@vegaprotocol/market-list';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const Markets = () => {
|
||||
const navigate = useNavigate();
|
||||
const handleOnSelect = useCallback(
|
||||
(marketId: string) => {
|
||||
navigate(Links[Routes.MARKET](marketId));
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const handleOnSelect = useMarketClickHandler();
|
||||
return <MarketsContainer onSelect={handleOnSelect} />;
|
||||
};
|
||||
|
||||
@@ -19,25 +19,18 @@ import { usePageTitleStore } from '../../stores';
|
||||
import { LedgerContainer } from '@vegaprotocol/ledger';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const Portfolio = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
updateTitle(titlefy([t('Portfolio')]));
|
||||
}, [updateTitle]);
|
||||
|
||||
const onMarketClick = (marketId: string) => {
|
||||
navigate(Links[Routes.MARKET](marketId), {
|
||||
replace: true,
|
||||
});
|
||||
};
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
|
||||
const wrapperClasses = 'h-full max-h-full flex flex-col';
|
||||
return (
|
||||
|
||||
@@ -1,39 +1,16 @@
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import React from 'react';
|
||||
import { AnnouncementBanner } from '@vegaprotocol/announcements';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const Banner = () => {
|
||||
const { update, shouldDisplayAnnouncementBanner } = useGlobalStore(
|
||||
(store) => ({
|
||||
update: store.update,
|
||||
shouldDisplayAnnouncementBanner: store.shouldDisplayAnnouncementBanner,
|
||||
})
|
||||
);
|
||||
const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayAnnouncementBanner) {
|
||||
if (!ANNOUNCEMENTS_CONFIG_URL) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => update({ shouldDisplayAnnouncementBanner: false })}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
<AnnouncementBanner app="console" configUrl={ANNOUNCEMENTS_CONFIG_URL} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const getTradingModeLabel = (
|
||||
|
||||
interface HeaderStatMarketTradingModeProps {
|
||||
marketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect?: (marketId: string, metaKey?: boolean) => void;
|
||||
initialTradingMode?: Schema.MarketTradingMode;
|
||||
initialTrigger?: Schema.AuctionTrigger;
|
||||
}
|
||||
@@ -66,7 +66,9 @@ export const MarketTradingMode = ({
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
description={<TradingModeTooltip marketId={marketId} skip={!inView} />}
|
||||
description={
|
||||
<TradingModeTooltip marketId={marketId} skip={!inView} skipGrid />
|
||||
}
|
||||
>
|
||||
<span ref={ref}>
|
||||
{getTradingModeLabel(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RefObject } from 'react';
|
||||
import type { RefObject, MouseEvent } from 'react';
|
||||
import { FeesCell } from '@vegaprotocol/market-info';
|
||||
import {
|
||||
calcCandleHigh,
|
||||
@@ -157,14 +157,14 @@ export const columnHeaders: Column[] = [
|
||||
];
|
||||
|
||||
export type OnCellClickHandler = (
|
||||
e: React.MouseEvent,
|
||||
e: MouseEvent,
|
||||
kind: ColumnKind,
|
||||
value: string
|
||||
) => void;
|
||||
|
||||
export const columns = (
|
||||
market: MarketMaybeWithDataAndCandles,
|
||||
onSelect: (id: string) => void,
|
||||
onSelect: (id: string, metaKey?: boolean) => void,
|
||||
onCellClick: OnCellClickHandler,
|
||||
inViewRoot?: RefObject<HTMLElement>
|
||||
) => {
|
||||
@@ -174,14 +174,7 @@ export const columns = (
|
||||
const candleLow = market.candles && calcCandleLow(market.candles);
|
||||
const candleHigh = market.candles && calcCandleHigh(market.candles);
|
||||
const candleVolume = market.candles && calcCandleVolume(market.candles);
|
||||
const handleKeyPress = (
|
||||
event: React.KeyboardEvent<HTMLAnchorElement>,
|
||||
id: string
|
||||
) => {
|
||||
if (event.key === 'Enter' && onSelect) {
|
||||
return onSelect(id);
|
||||
}
|
||||
};
|
||||
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
kind: ColumnKind.Market,
|
||||
@@ -189,10 +182,10 @@ export const columns = (
|
||||
<Link
|
||||
to={Links[Routes.MARKET](market.id)}
|
||||
data-testid={`market-link-${market.id}`}
|
||||
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onSelect(market.id);
|
||||
e.stopPropagation();
|
||||
onSelect(market.id, e.metaKey);
|
||||
}}
|
||||
>
|
||||
<UILink>{market.tradableInstrument.instrument.code}</UILink>
|
||||
@@ -352,7 +345,7 @@ export const columns = (
|
||||
|
||||
export const columnsPositionMarkets = (
|
||||
market: MarketMaybeWithDataAndCandles,
|
||||
onSelect: (id: string) => void,
|
||||
onSelect: (id: string, metaKey?: boolean) => void,
|
||||
inViewRoot?: RefObject<HTMLElement>,
|
||||
openVolume?: string,
|
||||
onCellClick?: OnCellClickHandler
|
||||
@@ -362,14 +355,6 @@ export const columnsPositionMarkets = (
|
||||
.filter((c: string | undefined): c is CandleClose => !isNil(c));
|
||||
const candleLow = market.candles && calcCandleLow(market.candles);
|
||||
const candleHigh = market.candles && calcCandleHigh(market.candles);
|
||||
const handleKeyPress = (
|
||||
event: React.KeyboardEvent<HTMLSpanElement>,
|
||||
id: string
|
||||
) => {
|
||||
if (event.key === 'Enter' && onSelect) {
|
||||
return onSelect(id);
|
||||
}
|
||||
};
|
||||
const candleVolume = market.candles && calcCandleVolume(market.candles);
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
@@ -378,10 +363,10 @@ export const columnsPositionMarkets = (
|
||||
<Link
|
||||
to={Links[Routes.MARKET](market.id)}
|
||||
data-testid={`market-link-${market.id}`}
|
||||
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onSelect(market.id);
|
||||
e.stopPropagation();
|
||||
onSelect(market.id, e.metaKey);
|
||||
}}
|
||||
>
|
||||
<UILink>{market.tradableInstrument.instrument.code}</UILink>
|
||||
|
||||
@@ -37,14 +37,14 @@ export const SelectMarketTableRow = ({
|
||||
}: {
|
||||
detailed?: boolean;
|
||||
columns: Column[];
|
||||
onSelect: (id: string) => void;
|
||||
onSelect: (id: string, metaKey?: boolean) => void;
|
||||
marketId: string;
|
||||
}) => {
|
||||
return (
|
||||
<tr
|
||||
className={`hover:bg-neutral-200 dark:hover:bg-neutral-700 cursor-pointer relative h-[34px]`}
|
||||
onClick={() => {
|
||||
onSelect(marketId);
|
||||
onClick={(ev) => {
|
||||
onSelect(marketId, ev.metaKey);
|
||||
}}
|
||||
data-testid={`market-link-${marketId}`}
|
||||
>
|
||||
|
||||
@@ -178,6 +178,6 @@ describe('SelectMarket', () => {
|
||||
expect(screen.getByText('25.00%')).toBeTruthy(); // price change
|
||||
expect(container).toHaveTextContent(/1,000/); // volume
|
||||
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
|
||||
expect(onSelect).toHaveBeenCalledWith('1');
|
||||
expect(onSelect).toHaveBeenCalledWith('1', false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ export const SelectAllMarketsTableBody = ({
|
||||
markets?: MarketMaybeWithDataAndCandles[] | null;
|
||||
positions?: PositionFieldsFragment[];
|
||||
title?: string;
|
||||
onSelect: (id: string) => void;
|
||||
onSelect: (id: string, metaKey?: boolean) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
headers?: Column[];
|
||||
tableColumns?: (
|
||||
@@ -95,7 +95,7 @@ export const SelectMarketPopover = ({
|
||||
}: {
|
||||
marketCode: string;
|
||||
marketName: string;
|
||||
onSelect: (id: string) => void;
|
||||
onSelect: (id: string, metaKey?: boolean) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -116,8 +116,8 @@ export const SelectMarketPopover = ({
|
||||
skip: !pubKey,
|
||||
});
|
||||
const onSelectMarket = useCallback(
|
||||
(marketId: string) => {
|
||||
onSelect(marketId);
|
||||
(marketId: string, metaKey?: boolean) => {
|
||||
onSelect(marketId, metaKey);
|
||||
setOpen(false);
|
||||
},
|
||||
[onSelect]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useCallback } from 'react';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const useMarketClickHandler = (replace = false) => {
|
||||
const navigate = useNavigate();
|
||||
const { marketId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const isMarketPage = pathname.match(/^\/markets\/(.+)/);
|
||||
return useCallback(
|
||||
(selectedId: string, metaKey?: boolean) => {
|
||||
const link = Links[Routes.MARKET](selectedId);
|
||||
if (metaKey) {
|
||||
window.open(`/#${link}`, '_blank');
|
||||
} else if (selectedId !== marketId || !isMarketPage) {
|
||||
navigate(link, { replace });
|
||||
}
|
||||
},
|
||||
[navigate, marketId, replace, isMarketPage]
|
||||
);
|
||||
};
|
||||
@@ -474,15 +474,17 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
}
|
||||
|
||||
if (tx.order && tx.order.rejectionReason) {
|
||||
const rejectionReason = getRejectionReason(tx.order) || ' ';
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Order rejected')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Your order has been rejected because: %s',
|
||||
getRejectionReason(tx.order) || ''
|
||||
)}
|
||||
</p>
|
||||
{rejectionReason ? (
|
||||
<p>
|
||||
{t('Your order has been rejected because: %s', [rejectionReason])}
|
||||
</p>
|
||||
) : (
|
||||
<p>{t('Your order has been rejected.')}</p>
|
||||
)}
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
<ExternalLink
|
||||
@@ -576,10 +578,9 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
walletNoConnectionCodes.includes(tx.error.code);
|
||||
if (orderRejection) {
|
||||
label = t('Order rejected');
|
||||
errorMessage = t(
|
||||
'Your order has been rejected because: %s',
|
||||
orderRejection
|
||||
);
|
||||
errorMessage = t('Your order has been rejected because: %s', [
|
||||
orderRejection,
|
||||
]);
|
||||
}
|
||||
if (walletError) {
|
||||
label = t('Wallet disconnected');
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Head from 'next/head';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -23,14 +25,12 @@ import {
|
||||
import './styles.css';
|
||||
import { useGlobalStore, usePageTitleStore } from '../stores';
|
||||
import { Footer } from '../components/footer';
|
||||
import { useMemo, useState } from 'react';
|
||||
import DialogsContainer from './dialogs-container';
|
||||
import ToastsManager from './toasts-manager';
|
||||
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { Banner } from '../components/banner';
|
||||
import classNames from 'classnames';
|
||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
|
||||
@@ -57,7 +57,7 @@ const Title = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TransactionsHandler = () => {
|
||||
const InitializeHandlers = () => {
|
||||
useVegaTransactionManager();
|
||||
useVegaTransactionUpdater();
|
||||
useEthTransactionManager();
|
||||
@@ -93,7 +93,7 @@ function AppBody({ Component }: AppProps) {
|
||||
</div>
|
||||
<DialogsContainer />
|
||||
<ToastsManager />
|
||||
<TransactionsHandler />
|
||||
<InitializeHandlers />
|
||||
<MaybeConnectEagerly />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ interface GlobalStore {
|
||||
marketId: string | null;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
shouldDisplayWelcomeDialog: boolean;
|
||||
shouldDisplayAnnouncementBanner: boolean;
|
||||
}
|
||||
|
||||
interface PageTitleStore {
|
||||
@@ -19,7 +18,6 @@ export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
nodeSwitcherDialog: false,
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
shouldDisplayWelcomeDialog: false,
|
||||
shouldDisplayAnnouncementBanner: true,
|
||||
update: (newState) => {
|
||||
set(
|
||||
produce((state: GlobalStore) => {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["@nrwl/next/babel"],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
...rootMain,
|
||||
|
||||
core: { ...rootMain.core, builder: 'webpack5' },
|
||||
|
||||
stories: [
|
||||
...rootMain.stories,
|
||||
'../src/lib/**/*.stories.mdx',
|
||||
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
|
||||
],
|
||||
addons: [
|
||||
...rootMain.addons,
|
||||
'@nrwl/react/plugins/storybook',
|
||||
'storybook-addon-themes',
|
||||
],
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// apply any global webpack configs that might have been specified in .storybook/main.js
|
||||
if (rootMain.webpackFinal) {
|
||||
config = await rootMain.webpackFinal(config, { configType });
|
||||
}
|
||||
|
||||
// add your own webpack tweaks if needed
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
export * from '../../ui-toolkit/.storybook/preview';
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["../src/**/*", "*.js"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# announcements
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Creating announcements
|
||||
|
||||
The announcements config lives in the [announcements repo](https://github.com/vegaprotocol/announcements). To create app/env specific announcements, check out the readme in the repo!
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test announcements` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'announcements',
|
||||
preset: '../../jest.preset.js',
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/announcements',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "@vegaprotocol/announcements",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/announcements/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/accounts",
|
||||
"tsConfig": "libs/announcements/tsconfig.lib.json",
|
||||
"project": "libs/announcements/package.json",
|
||||
"entryFile": "libs/announcements/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/announcements/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/announcements/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/libs/announcements"],
|
||||
"options": {
|
||||
"jestConfig": "libs/announcements/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
"executor": "@nrwl/storybook:storybook",
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"port": 4400,
|
||||
"config": {
|
||||
"configFolder": "libs/announcements/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-storybook": {
|
||||
"executor": "@nrwl/storybook:build",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"outputPath": "dist/storybook/announcements",
|
||||
"config": {
|
||||
"configFolder": "libs/announcements/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
"executor": "@nrwl/workspace:run-commands",
|
||||
"outputs": [],
|
||||
"options": {
|
||||
"command": "yarn tsc --project ./libs/announcements/tsconfig.spec.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './lib';
|
||||
@@ -0,0 +1,207 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { addDays } from 'date-fns';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import { AnnouncementBanner } from './announcements';
|
||||
|
||||
const MOCK_URL = 'http://somewhere.com/config.json';
|
||||
const MOCK_ANNOUNCEMENT = {
|
||||
text: 'Attention everyone!',
|
||||
url: 'http://click.me/',
|
||||
urlText: 'Read more',
|
||||
};
|
||||
|
||||
describe('Announcements', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
jest.useRealTimers();
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
it('does not display the banner when fetching announcements fails', async () => {
|
||||
fetchMock.get(MOCK_URL, 500);
|
||||
const { container } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not display the banner when there are no announcements', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
website: [],
|
||||
});
|
||||
const { container } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the correct announcement', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
text: 'Console announcement',
|
||||
},
|
||||
],
|
||||
governance: [
|
||||
{
|
||||
text: 'Governance announcement',
|
||||
},
|
||||
],
|
||||
explorer: [
|
||||
{
|
||||
text: 'Explorer announcement',
|
||||
},
|
||||
],
|
||||
wallet: [
|
||||
{
|
||||
text: 'Wallet announcement',
|
||||
},
|
||||
],
|
||||
website: [
|
||||
{
|
||||
text: 'Website announcement',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { rerender, findByText } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
expect(await findByText('Console announcement')).toBeVisible();
|
||||
|
||||
rerender(<AnnouncementBanner app="governance" configUrl={MOCK_URL} />);
|
||||
expect(await findByText('Governance announcement')).toBeVisible();
|
||||
|
||||
rerender(<AnnouncementBanner app="explorer" configUrl={MOCK_URL} />);
|
||||
expect(await findByText('Explorer announcement')).toBeVisible();
|
||||
|
||||
rerender(<AnnouncementBanner app="wallet" configUrl={MOCK_URL} />);
|
||||
expect(await findByText('Wallet announcement')).toBeVisible();
|
||||
|
||||
rerender(<AnnouncementBanner app="website" configUrl={MOCK_URL} />);
|
||||
expect(await findByText('Website announcement')).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows the announcement link', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [MOCK_ANNOUNCEMENT],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
website: [],
|
||||
});
|
||||
|
||||
const { findByText, findByRole } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
const text = await findByText(MOCK_ANNOUNCEMENT.text);
|
||||
const link = (await findByRole('link')) as HTMLAnchorElement;
|
||||
const linkText = await findByText(MOCK_ANNOUNCEMENT.urlText);
|
||||
|
||||
expect(text).toBeVisible();
|
||||
expect(link).toBeVisible();
|
||||
expect(link.href).toBe(MOCK_ANNOUNCEMENT.url);
|
||||
expect(linkText).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows the first announcement', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
text: 'First text',
|
||||
},
|
||||
{
|
||||
text: 'Second text',
|
||||
},
|
||||
],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
website: [],
|
||||
});
|
||||
|
||||
const { queryByText } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('First text')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show expired announcements', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
text: 'Expired text',
|
||||
timing: {
|
||||
to: new Date(0).toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Live text',
|
||||
},
|
||||
],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
website: [],
|
||||
});
|
||||
|
||||
const { queryByText } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Live text')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('hides announcements after they expire', async () => {
|
||||
const now = new Date();
|
||||
const tomorrow = addDays(now, 1);
|
||||
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
text: 'Live text',
|
||||
timing: {
|
||||
to: tomorrow.toISOString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
website: [],
|
||||
});
|
||||
|
||||
const { queryByText } = render(
|
||||
<AnnouncementBanner app="console" configUrl={MOCK_URL} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Live text')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Live text')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { AnnouncementBanner } from './announcements';
|
||||
|
||||
export default {
|
||||
component: AnnouncementBanner,
|
||||
title: 'AnnouncementBanner',
|
||||
} as Meta;
|
||||
|
||||
const MOCK_URL = 'http://somewhere.com/config.json';
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
text: 'Console announcement',
|
||||
url: 'http://vega.xyz',
|
||||
urlText: 'Read more',
|
||||
},
|
||||
],
|
||||
governance: [
|
||||
{
|
||||
text: 'Governance announcement',
|
||||
url: 'http://vega.xyz',
|
||||
urlText: 'Read more',
|
||||
},
|
||||
],
|
||||
explorer: [
|
||||
{
|
||||
text: 'Explorer announcement',
|
||||
url: 'http://vega.xyz',
|
||||
urlText: 'Read more',
|
||||
},
|
||||
],
|
||||
wallet: [
|
||||
{
|
||||
text: 'Wallet announcement',
|
||||
url: 'http://vega.xyz',
|
||||
urlText: 'Read more',
|
||||
},
|
||||
],
|
||||
website: [
|
||||
{
|
||||
text: 'Website announcement',
|
||||
url: 'http://vega.xyz',
|
||||
urlText: 'Read more',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<AnnouncementBanner app={args.app} configUrl={args.configUrl} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
configUrl: MOCK_URL,
|
||||
app: 'console',
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { AppNameType, Announcement } from './schema';
|
||||
import { useAnnouncement } from './hooks/use-announcement';
|
||||
import {
|
||||
Icon,
|
||||
AnnouncementBanner as Banner,
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export type AnnouncementBannerProps = {
|
||||
app: AppNameType;
|
||||
configUrl: string;
|
||||
};
|
||||
|
||||
// run only if below the allowed maximum delay ~24.8 days (https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value)
|
||||
const MAX_DELAY = 2147483648;
|
||||
|
||||
const doesStartInTheFuture = (now: Date, data: Announcement) => {
|
||||
return data.timing?.from
|
||||
? now < data.timing.from &&
|
||||
data.timing.from.valueOf() - now.valueOf() < MAX_DELAY
|
||||
: false;
|
||||
};
|
||||
|
||||
const doesEndInTheFuture = (now: Date, data: Announcement) => {
|
||||
return data.timing?.to
|
||||
? now < data.timing.to &&
|
||||
data.timing.to.valueOf() - now.valueOf() < MAX_DELAY
|
||||
: false;
|
||||
};
|
||||
|
||||
export const AnnouncementBanner = ({
|
||||
app,
|
||||
configUrl,
|
||||
}: AnnouncementBannerProps) => {
|
||||
const [isVisible, setVisible] = useState(false);
|
||||
const { data, reload } = useAnnouncement(app, configUrl);
|
||||
|
||||
useEffect(() => {
|
||||
const now = new Date();
|
||||
let stampFrom: NodeJS.Timeout;
|
||||
let stampTo: NodeJS.Timeout;
|
||||
|
||||
if (data) {
|
||||
const startsInTheFuture = doesStartInTheFuture(now, data);
|
||||
const endsInTheFuture = doesEndInTheFuture(now, data);
|
||||
|
||||
if (!startsInTheFuture) {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
if (data.timing?.from && startsInTheFuture) {
|
||||
stampFrom = setTimeout(() => {
|
||||
setVisible(true);
|
||||
}, data.timing.from.valueOf() - now.valueOf());
|
||||
}
|
||||
|
||||
if (data.timing?.to && endsInTheFuture) {
|
||||
stampTo = setTimeout(() => {
|
||||
setVisible(false);
|
||||
reload();
|
||||
}, data.timing.to.valueOf() - now.valueOf());
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(stampFrom);
|
||||
clearTimeout(stampTo);
|
||||
};
|
||||
}, [data, reload, setVisible]);
|
||||
|
||||
if (!data || !isVisible) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Banner className="relative px-10">
|
||||
<button
|
||||
className="absolute left-0 top-0 w-10 h-full flex items-center justify-center"
|
||||
data-testid="app-announcement-close"
|
||||
onClick={() => setVisible(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div
|
||||
data-testid="app-announcement"
|
||||
className="relative font-alpha flex gap-2 justify-center calt uppercase text-center text-lg text-white"
|
||||
>
|
||||
<span className="pr-4">{data.text}</span>
|
||||
{data.urlText && data.url && (
|
||||
<ExternalLink href={data.url}>{data.urlText}</ExternalLink>
|
||||
)}
|
||||
</div>
|
||||
</Banner>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useAnnouncement } from './use-announcement';
|
||||
|
||||
const MOCK_URL = 'http://somewhere.com/config.json';
|
||||
const MOCK_ANNOUNCEMENT = {
|
||||
text: 'Attention everyone!',
|
||||
url: 'http://click.me/',
|
||||
urlText: 'Read more',
|
||||
};
|
||||
|
||||
describe('Use announcement hook', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
it('loads the data', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [MOCK_ANNOUNCEMENT],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
});
|
||||
const { result } = renderHook(() => useAnnouncement('console', MOCK_URL));
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.data).toEqual(null);
|
||||
expect(result.current.error).toBe(null);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.data).toEqual(MOCK_ANNOUNCEMENT);
|
||||
expect(result.current.error).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when cannot load data', async () => {
|
||||
fetchMock.get(MOCK_URL, 404);
|
||||
const { result } = renderHook(() => useAnnouncement('console', MOCK_URL));
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.data).toEqual(null);
|
||||
expect(result.current.error).toBe(null);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.data).toBe(null);
|
||||
expect(result.current.error).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an error when data is malformed', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
some: 'json',
|
||||
});
|
||||
const { result } = renderHook(() => useAnnouncement('console', MOCK_URL));
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.data).toEqual(null);
|
||||
expect(result.current.error).toBe(null);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.data).toBe(null);
|
||||
expect(result.current.error).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('filters out expired announcements', async () => {
|
||||
fetchMock.get(MOCK_URL, {
|
||||
console: [
|
||||
{
|
||||
...MOCK_ANNOUNCEMENT,
|
||||
timing: {
|
||||
to: new Date(0).toISOString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
governance: [],
|
||||
explorer: [],
|
||||
wallet: [],
|
||||
});
|
||||
const { result } = renderHook(() => useAnnouncement('console', MOCK_URL));
|
||||
expect(result.current.loading).toBe(true);
|
||||
expect(result.current.data).toEqual(null);
|
||||
expect(result.current.error).toBe(null);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.data).toBe(null);
|
||||
expect(result.current.error).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { AppNameType, Announcement } from '../schema';
|
||||
import { AnnouncementsSchema } from '../schema';
|
||||
|
||||
const getData = async (name: AppNameType, url: string) => {
|
||||
const now = new Date();
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
const parsed = AnnouncementsSchema.parse(data);
|
||||
|
||||
const list = parsed[name] || [];
|
||||
|
||||
const announcements = list
|
||||
.filter((item) => {
|
||||
// Don't add expired items
|
||||
return !item.timing?.to || (item.timing.to && now < item.timing.to);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const fromA = a.timing?.from || new Date(0);
|
||||
const fromB = b.timing?.from || new Date(0);
|
||||
// Sort by from date
|
||||
return fromA.valueOf() - fromB.valueOf();
|
||||
});
|
||||
|
||||
return announcements[0] || null;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loading: boolean;
|
||||
data: null | Announcement;
|
||||
error: null | string;
|
||||
};
|
||||
|
||||
export const useAnnouncement = (name: AppNameType, url: string) => {
|
||||
const [state, setState] = useState<State>({
|
||||
loading: true,
|
||||
data: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const fetchData = useCallback(() => {
|
||||
let mounted = true;
|
||||
|
||||
getData(name, url)
|
||||
.then((data) => {
|
||||
if (mounted) {
|
||||
setState({
|
||||
loading: false,
|
||||
data,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (mounted) {
|
||||
setState({
|
||||
loading: false,
|
||||
data: null,
|
||||
error: `${err}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [name, url, setState]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
reload: fetchData,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './announcements';
|
||||
export * from './hooks/use-announcement';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const AppName = z.enum([
|
||||
'console',
|
||||
'governance',
|
||||
'explorer',
|
||||
'wallet',
|
||||
'website',
|
||||
]);
|
||||
|
||||
export type AppNameType = z.infer<typeof AppName>;
|
||||
|
||||
export const AnnouncementSchema = z.object({
|
||||
text: z.string(),
|
||||
url: z.string().url().optional(),
|
||||
urlText: z.string().optional(),
|
||||
timing: z
|
||||
.object({
|
||||
from: z.coerce.date().optional(),
|
||||
to: z.coerce.date().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type Announcement = z.infer<typeof AnnouncementSchema>;
|
||||
|
||||
export const AnnouncementsSchema = z.record(
|
||||
AppName,
|
||||
z.array(AnnouncementSchema)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/next/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.jsx",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.d.ts",
|
||||
"../react-helpers/src/lib/grid-cells/summary-row.spec.ts",
|
||||
"jest.config.ts"
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export * from './lib/cells/price-change-cell';
|
||||
export * from './lib/cells/price-flash-cell';
|
||||
export * from './lib/cells/vol-cell';
|
||||
export * from './lib/cells/centered-grid-cell';
|
||||
export * from './lib/cells/market-name-cell';
|
||||
|
||||
export * from './lib/filters/date-range-filter';
|
||||
export * from './lib/filters/set-filter';
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import get from 'lodash/get';
|
||||
|
||||
interface MarketNameCellProps {
|
||||
value?: string;
|
||||
data?: { id?: string; marketId?: string; market?: { id: string } };
|
||||
idPath?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const MarketNameCell = ({
|
||||
value,
|
||||
data,
|
||||
idPath,
|
||||
onMarketClick,
|
||||
}: MarketNameCellProps) => {
|
||||
const id = data ? get(data, idPath ?? 'id', 'all') : '';
|
||||
const handleOnClick = useCallback(
|
||||
(ev: MouseEvent<HTMLButtonElement>) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (onMarketClick) {
|
||||
onMarketClick(id, ev.metaKey);
|
||||
}
|
||||
},
|
||||
[id, onMarketClick]
|
||||
);
|
||||
if (!data) return null;
|
||||
return (
|
||||
<button onClick={handleOnClick} tabIndex={0}>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -107,6 +107,50 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values reduceOnly and postOnly', () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
size: '0.1',
|
||||
price: '300.22',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
persist: true,
|
||||
reduceOnly: true,
|
||||
postOnly: false,
|
||||
};
|
||||
|
||||
useOrderStore.setState({
|
||||
orders: {
|
||||
[expectedOrder.marketId]: expectedOrder,
|
||||
},
|
||||
});
|
||||
|
||||
render(generateJsx());
|
||||
|
||||
// Assert correct defaults are used from store
|
||||
expect(
|
||||
screen
|
||||
.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
|
||||
.querySelector('input')
|
||||
).toBeChecked();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
|
||||
).toBeChecked();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
|
||||
).not.toBeChecked();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
expectedOrder.size
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
expectedOrder.timeInForce
|
||||
);
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||
expectedOrder.price
|
||||
);
|
||||
});
|
||||
|
||||
it('handles TIF select box dependent on order type', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { memo, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { memo, useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
Checkbox,
|
||||
ExternalLink,
|
||||
InputError,
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
TinyScroll,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
@@ -146,6 +148,16 @@ export const DealTicket = ({
|
||||
clearErrors,
|
||||
]);
|
||||
|
||||
const disablePostOnlyCheckbox = useMemo(() => {
|
||||
const disabled = order
|
||||
? [
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
].includes(order.timeInForce)
|
||||
: true;
|
||||
return disabled;
|
||||
}, [order]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(order: OrderSubmission) => {
|
||||
const now = new Date().getTime();
|
||||
@@ -239,7 +251,7 @@ export const DealTicket = ({
|
||||
value={order.timeInForce}
|
||||
orderType={order.type}
|
||||
onSelect={(timeInForce) => {
|
||||
update({ timeInForce });
|
||||
update({ timeInForce, postOnly: false, reduceOnly: false });
|
||||
// Set tif value for the given order type, so that when switching
|
||||
// types we know the last used TIF for the given order type
|
||||
setLastTIF((curr) => ({
|
||||
@@ -276,6 +288,65 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<Controller
|
||||
name="postOnly"
|
||||
control={control}
|
||||
render={() => (
|
||||
<Checkbox
|
||||
name="post-only"
|
||||
checked={order.postOnly}
|
||||
disabled={disablePostOnlyCheckbox}
|
||||
onCheckedChange={() => {
|
||||
update({ postOnly: !order.postOnly, reduceOnly: false });
|
||||
}}
|
||||
label={
|
||||
<Tooltip
|
||||
description={
|
||||
<span>
|
||||
{disablePostOnlyCheckbox
|
||||
? t(
|
||||
'"Post only" can not be used on "Fill or Kill" or "Immediate or Cancel" orders.'
|
||||
)
|
||||
: t(
|
||||
'"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.'
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span className="text-xs">{t('Post only')}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="reduceOnly"
|
||||
control={control}
|
||||
render={() => (
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={order.reduceOnly}
|
||||
onCheckedChange={() => {
|
||||
update({ postOnly: false, reduceOnly: !order.reduceOnly });
|
||||
}}
|
||||
label={
|
||||
<Tooltip
|
||||
description={
|
||||
<span>
|
||||
{t(
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.'
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span className="text-xs">{t('Reduce only')}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<SummaryMessage
|
||||
errorMessage={errors.summary?.message}
|
||||
asset={asset}
|
||||
|
||||
@@ -96,16 +96,6 @@ export const TimeInForceSelector = ({
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
// setPreviousTimeInForce({
|
||||
// ...previousTimeInForce,
|
||||
// [orderType]: e.target.value,
|
||||
// });
|
||||
|
||||
// if (previousOrderType !== orderType) {
|
||||
// setPreviousOrderType(orderType);
|
||||
// const prev = previousTimeInForce[orderType as OrderType];
|
||||
// onSelect(prev);
|
||||
// }
|
||||
onSelect(e.target.value as Schema.OrderTimeInForce);
|
||||
}}
|
||||
className="w-full"
|
||||
|
||||
@@ -26,7 +26,7 @@ export const compileGridData = (
|
||||
| 'targetStake'
|
||||
| 'trigger'
|
||||
> | null,
|
||||
onSelect?: (id: string) => void
|
||||
onSelect?: (id: string, metaKey?: boolean) => void
|
||||
): { label: ReactNode; value?: ReactNode }[] => {
|
||||
const grid: SimpleGridProps['grid'] = [];
|
||||
const isLiquidityMonitoringAuction =
|
||||
@@ -78,7 +78,7 @@ export const compileGridData = (
|
||||
label: (
|
||||
<Link
|
||||
to={`/liquidity/${market.id}`}
|
||||
onClick={() => onSelect && onSelect(market.id)}
|
||||
onClick={(ev) => onSelect && onSelect(market.id, ev.metaKey)}
|
||||
>
|
||||
<UILink>{t('Current liquidity')}</UILink>
|
||||
</Link>
|
||||
|
||||
@@ -12,14 +12,16 @@ import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
|
||||
|
||||
type TradingModeTooltipProps = {
|
||||
marketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
onSelect?: (marketId: string, metaKey?: boolean) => void;
|
||||
skip?: boolean;
|
||||
skipGrid?: boolean;
|
||||
};
|
||||
|
||||
export const TradingModeTooltip = ({
|
||||
marketId,
|
||||
onSelect,
|
||||
skip,
|
||||
skipGrid,
|
||||
}: TradingModeTooltipProps) => {
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const { data: market } = useMarket(marketId);
|
||||
@@ -42,7 +44,7 @@ export const TradingModeTooltip = ({
|
||||
);
|
||||
|
||||
const compiledGrid =
|
||||
onSelect && compileGridData(market, marketData, onSelect);
|
||||
!skipGrid && compileGridData(market, marketData, onSelect);
|
||||
|
||||
switch (marketTradingMode) {
|
||||
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
|
||||
@@ -103,6 +105,7 @@ export const TradingModeTooltip = ({
|
||||
{VEGA_DOCS_URL && (
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).AUCTION_TYPE_OPENING}
|
||||
className="ml-1"
|
||||
>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
@@ -129,6 +132,7 @@ export const TradingModeTooltip = ({
|
||||
createDocsLinks(VEGA_DOCS_URL)
|
||||
.AUCTION_TYPE_LIQUIDITY_MONITORING
|
||||
}
|
||||
className="ml-1"
|
||||
>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
@@ -153,6 +157,7 @@ export const TradingModeTooltip = ({
|
||||
createDocsLinks(VEGA_DOCS_URL)
|
||||
.AUCTION_TYPE_LIQUIDITY_MONITORING
|
||||
}
|
||||
className="ml-1"
|
||||
>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
@@ -175,6 +180,7 @@ export const TradingModeTooltip = ({
|
||||
createDocsLinks(VEGA_DOCS_URL)
|
||||
.AUCTION_TYPE_PRICE_MONITORING
|
||||
}
|
||||
className="ml-1"
|
||||
>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import 'jest-canvas-mock';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { defaultFallbackInView } from 'react-intersection-observer';
|
||||
|
||||
defaultFallbackInView(true);
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -277,6 +277,7 @@ function compileEnvVars() {
|
||||
),
|
||||
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_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
|
||||
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
|
||||
@@ -285,6 +286,7 @@ function compileEnvVars() {
|
||||
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'],
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const schemaObject = {
|
||||
GIT_COMMIT_HASH: z.optional(z.string()),
|
||||
GIT_ORIGIN_URL: z.optional(z.string()),
|
||||
GITHUB_FEEDBACK_URL: z.optional(z.string()),
|
||||
ORACLE_PROOFS_URL: z.optional(z.string().url()),
|
||||
VEGA_ENV: z.nativeEnum(Networks),
|
||||
VEGA_EXPLORER_URL: z.optional(z.string()),
|
||||
VEGA_TOKEN_URL: z.optional(z.string()),
|
||||
@@ -50,6 +51,7 @@ const schemaObject = {
|
||||
MAINTENANCE_PAGE: z.optional(z.boolean()),
|
||||
ETH_LOCAL_PROVIDER_URL: z.optional(z.string()),
|
||||
ETH_WALLET_MNEMONIC: z.optional(z.string()),
|
||||
ANNOUNCEMENTS_CONFIG_URL: z.optional(z.string()),
|
||||
VEGA_INCIDENT_URL: z.optional(z.string()),
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export const FillsContainer = ({
|
||||
onMarketClick,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||
interface FillsManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const FillsManager = ({
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { Trade } from './fills-data-provider';
|
||||
|
||||
import { FillsTable, getFeesBreakdown } from './fills-table';
|
||||
import { generateFill } from './test-helpers';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
describe('FillsTable', () => {
|
||||
let defaultFill: PartialDeep<Trade>;
|
||||
@@ -36,7 +37,11 @@ describe('FillsTable', () => {
|
||||
|
||||
it('correct columns are rendered', async () => {
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId="party-id" rowData={[generateFill()]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
@@ -67,7 +72,11 @@ describe('FillsTable', () => {
|
||||
liquidityFee: '2',
|
||||
},
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId={partyId} rowData={[{ ...buyerFill }]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
buyerFill.market?.tradableInstrument.instrument.name || '',
|
||||
@@ -100,7 +109,11 @@ describe('FillsTable', () => {
|
||||
liquidityFee: '1',
|
||||
},
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[buyerFill]} />);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId={partyId} rowData={[buyerFill]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const expectedValues = [
|
||||
@@ -129,7 +142,9 @@ describe('FillsTable', () => {
|
||||
aggressor: Schema.Side.SIDE_SELL,
|
||||
});
|
||||
const { rerender } = render(
|
||||
<FillsTable partyId={partyId} rowData={[takerFill]} />
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId={partyId} rowData={[takerFill]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(
|
||||
screen
|
||||
@@ -143,7 +158,11 @@ describe('FillsTable', () => {
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_BUY,
|
||||
});
|
||||
rerender(<FillsTable partyId={partyId} rowData={[makerFill]} />);
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId={partyId} rowData={[makerFill]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen
|
||||
@@ -160,7 +179,11 @@ describe('FillsTable', () => {
|
||||
},
|
||||
aggressor: Schema.Side.SIDE_SELL,
|
||||
});
|
||||
render(<FillsTable partyId={partyId} rowData={[takerFill]} />);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<FillsTable partyId={partyId} rowData={[takerFill]} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const feeCell = screen
|
||||
.getAllByRole('gridcell')
|
||||
|
||||
@@ -14,16 +14,13 @@ import {
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AgGridDynamic as AgGrid,
|
||||
positiveClassNames,
|
||||
negativeClassNames,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { VegaValueFormatterParams } from '@vegaprotocol/datagrid';
|
||||
import { forwardRef } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
@@ -34,7 +31,7 @@ const MAKER = 'MAKER';
|
||||
|
||||
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
partyId: string;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
};
|
||||
|
||||
export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
@@ -48,30 +45,14 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
tooltipHideDelay={2000}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
>
|
||||
<AgGridColumn
|
||||
headerName={t('Market')}
|
||||
field="market.tradableInstrument.instrument.name"
|
||||
cellRenderer={({
|
||||
value,
|
||||
data,
|
||||
}: VegaICellRendererParams<
|
||||
Trade,
|
||||
'market.tradableInstrument.instrument.name'
|
||||
>) =>
|
||||
onMarketClick ? (
|
||||
<Link
|
||||
onClick={() =>
|
||||
data?.market?.id && onMarketClick(data?.market?.id)
|
||||
}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
) : (
|
||||
value
|
||||
)
|
||||
}
|
||||
cellRenderer="MarketNameCell"
|
||||
cellRendererParams={{ idPath: 'market.id', onMarketClick }}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Size')}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user