Compare commits

..
250 changed files with 1577 additions and 5661 deletions
+10 -26
View File
@@ -5,12 +5,12 @@ on:
branches:
- release/*
- develop
- main
pull_request:
types:
- opened
- ready_for_review
- reopened
- edited
- synchronize
jobs:
node-modules:
@@ -44,6 +44,13 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
lint-format:
timeout-minutes: 20
needs: node-modules
@@ -179,33 +186,10 @@ jobs:
# with:
# github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
check-e2e-needed:
runs-on: ubuntu-latest
needs: build-sources
name: '(CI) check if e2e needed'
outputs:
run-tests: ${{ steps.check-test.outputs.e2e-needed }}
steps:
- name: Check branch
id: check-test
run: |
if [[ "${{ github.base_ref }}" == "develop" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "main" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/"* ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
else
echo "e2e-needed=false" >> $GITHUB_OUTPUT
fi
- name: Print result
run: |
echo "e2e-needed: ${{ steps.check-test.outputs.e2e-needed }}"
cypress:
needs: [build-sources, check-e2e-needed]
needs: build-sources
name: '(CI) cypress'
if: ${{ needs.check-e2e-needed.outputs.run-tests == 'true' }}
# if: ${{ needs.build-sources.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
+7 -41
View File
@@ -9,37 +9,23 @@ on:
github-sha:
required: true
type: string
workflow_dispatch:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
jobs:
run-tests:
name: run-tests
runs-on: 8-cores
timeout-minutes: 20
runs-on: console-test
timeout-minutes: 40
steps:
#----------------------------------------------
# check-out frontend-monorepo
#----------------------------------------------
- name: Checkout frontend-monorepo
- name: Checkout console test repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha || github.sha }}
ref: ${{ inputs.github-sha }}
#----------------------------------------------
# cache node modules
#----------------------------------------------
- name: setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: yarn
- name: Cache node modules
id: cache
uses: actions/cache@v3
@@ -82,18 +68,7 @@ jobs:
uses: actions/checkout@v3
with:
repository: vegaprotocol/console-test
ref: ${{ inputs.console-test-branch }}
path: './console-test'
- name: Load console test envs
id: console-test-env
uses: falti/dotenv-action@v1.0.4
with:
path: './console-test/.env.${{ inputs.console-test-branch }}'
export-variables: true
keys-case: upper
log-variables: true
#----------------------------------------------
# install dependencies
#----------------------------------------------
@@ -121,7 +96,8 @@ jobs:
#----------------------------------------------
- name: Install vega binaries
working-directory: ./console-test
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
if: steps.vega_binaries_cache.outputs.cache-hit != 'true'
run: poetry run python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
#----------------------------------------------
# install playwright
#----------------------------------------------
@@ -133,7 +109,7 @@ jobs:
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
run: poetry run pytest -v -s --numprocesses 2 --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
@@ -148,13 +124,3 @@ jobs:
name: playwright-trace
path: ./traces/
retention-days: 15
#----------------------------------------------
# ----- upload logs -----
#----------------------------------------------
- name: Upload worker logs
uses: actions/upload-artifact@v3
if: always()
with:
name: worker-logs
path: ./logs/
retention-days: 15
+1 -24
View File
@@ -13,35 +13,13 @@ on:
type: string
jobs:
runner-choice:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.step.outputs.runner }}
steps:
- name: Check branch
id: step
run: |
if [[ "${{ github.base_ref }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "develop" && "${{ github.ref_name }}" == "main" ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/mainnet"* ]]; then
echo "runner=mainnet-compatible-runner" >> $GITHUB_OUTPUT
else
echo "runner=self-hosted-runner" >> $GITHUB_OUTPUT
fi
- name: Print runner
run: echo ${{ steps.step.outputs.runner }}
e2e:
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
needs: runner-choice
runs-on: ${{ needs.runner-choice.outputs.runner }}
runs-on: self-hosted-runner
timeout-minutes: 120
steps:
# Checks if skip cache was requested
@@ -85,7 +63,6 @@ jobs:
- name: Run Vegacapsule network and Vega wallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
timeout-minutes: 10
######
## Run some tests
+11 -11
View File
@@ -2,12 +2,7 @@
name: Verify PR title
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
workflow_call:
jobs:
lint_pr:
@@ -16,16 +11,21 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
- name: Install dependencies
run: |
rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
+6 -12
View File
@@ -30,18 +30,12 @@ jobs:
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
echo IS_MAIN_IMAGE=false >> $GITHUB_ENV
- name: Is dev image
if: ${{ github.ref_name == 'develop' && github.event_name == 'push' && matrix.app == 'trading' }}
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
- name: Is main image
if: ${{ github.ref_name == 'main' && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_MAIN_IMAGE=true >> $GITHUB_ENV
- name: Is PR
if: ${{ github.event_name == 'pull_request' }}
run: |
@@ -63,7 +57,7 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' && github.ref_name != 'main'}}
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }}
run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
@@ -87,7 +81,7 @@ jobs:
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -185,7 +179,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' || env.IS_MAIN_IMAGE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -195,7 +189,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
@@ -222,7 +216,7 @@ jobs:
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || env.IS_MAIN_IMAGE == 'true' && 'main' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3
@@ -87,6 +87,7 @@ context('Market page', { tags: '@regression' }, function () {
// Liquidity
cy.validate_element_from_table('Target Stake', '0.00 fUSDC');
cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC');
cy.validate_element_from_table('Market Value Proxy', '0.00 fUSDC');
// Liquidity price range
cy.validate_element_from_table(
'Liquidity Price Range',
@@ -94,6 +95,7 @@ context('Market page', { tags: '@regression' }, function () {
);
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href')
.and(
@@ -142,8 +144,11 @@ context('Market page', { tags: '@regression' }, function () {
.as('successorMarketId');
cy.contains('Token test market').click();
cy.getByTestId(marketHeaders).should('have.text', 'Token test market');
cy.validate_proposal_change_type('Triggering Ratio', 'Added');
cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_proposal_change_type('Time Window', 'Added');
cy.validate_element_from_table('Time Window', '3,600');
cy.validate_proposal_change_type('Scaling Factor', 'Added');
cy.validate_element_from_table('Scaling Factor', '10');
cy.getByTestId(successionLineItem)
@@ -129,12 +129,6 @@ function getSuccessorTxBody(parentMarketId) {
parentMarketId: parentMarketId,
insurancePoolFraction: '0.75',
},
liquiditySlaParameters: {
priceRange: '0.95',
commitmentMinTimeFraction: '0.5',
performanceHysteresisEpochs: 2,
slaCompetitionFactor: '0.75',
},
},
},
closingTimestamp,
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -1,8 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import {
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
PriceMonitoringBoundsInfoPanel,
SuccessionLineInfoPanel,
getDataSourceSpecForSettlementData,
@@ -96,10 +94,6 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity SLA protocol')}</h2>
<LiquiditySLAParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} />
{showTwoOracles ? (
@@ -4,7 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="flex items-center justify-center h-full pt-2 uppercase">
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
@@ -49,37 +49,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
}
... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration {
signers {
signer {
+3 -34
View File
@@ -5,19 +5,19 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
export const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
@@ -72,37 +72,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
}
... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
abi
args
method
requiredConfirmations
address
normalisers {
name
expression
}
trigger {
trigger {
... on EthTimeTrigger {
initial
every
until
}
}
}
filters {
key {
name
type
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
... on DataSourceSpecConfiguration {
signers {
signer {
@@ -8,9 +8,7 @@ import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading, error } = useExplorerOracleSpecsQuery({
errorPolicy: 'ignore',
});
const { data, loading, error } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
-2
View File
@@ -21,8 +21,6 @@ NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
NX_SUCCESSOR_MARKETS=true
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
-2
View File
@@ -4,5 +4,3 @@ NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
-2
View File
@@ -4,5 +4,3 @@ NX_VEGA_URL=https://api.vega.community/graphql
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
-2
View File
@@ -4,5 +4,3 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
@@ -0,0 +1,21 @@
{
"rationale": {
"title": "Add USDT Coin (USDT)",
"description": "Proposal to add USDT Coin (USDT) as an asset"
},
"terms": {
"newAsset": {
"changes": {
"name": "USDT Coin",
"symbol": "USDT",
"decimals": "18",
"quantum": "1",
"erc20": {
"contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084"
}
}
},
"closingTimestamp": 1662374250,
"enactmentTimestamp": 1662460650
}
}
@@ -1,138 +0,0 @@
{
"rationale": {
"description": "## Summary\n\nThis proposal requests to list BTC PERPS Incentive as a market with USD-P as a settlement asset on the Vega Network as discussed in: https://community.vega.xyz/.\n\n## Rationale\n\n- BTC is the largest Crypto asset with the highest volume and Marketcap.\n- Given the price, 1 decimal places will be used for price due to the number of valid digits in asset price. \n- Position decimal places will be set to 4 considering the value per contract\n- USDT is chosen as settlement asset due to its stability.",
"title": "perpetual market proposal"
},
"terms": {
"closingTimestamp": 0,
"enactmentTimestamp": 0,
"newMarket": {
"changes": {
"instrument": {
"name": "Token test market",
"code": "TEST.24h",
"perpetual": {
"clampLowerBound": "0",
"clampUpperBound": "0",
"interestRate": "0",
"marginFundingFactor": "0.1",
"settlementAsset": "73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0",
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"ethOracle": {
"address": "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43",
"abi": "[{\"inputs\":[],\"name\":\"latestAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]",
"method": "latestAnswer",
"normalisers": [
{
"name": "btc.price",
"expression": "$[0]"
}
],
"requiredConfirmations": 3,
"trigger": {
"timeTrigger": {
"every": 30
}
},
"filters": [
{
"key": {
"name": "btc.price",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": 8
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForSettlementSchedule": {
"internal": {
"timeTrigger": {
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "0"
}
],
"triggers": [
{
"every": 1800
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "btc.price",
"settlementScheduleProperty": "vegaprotocol.builtin.timetrigger"
}
}
},
"metadata": [
"base:BTC",
"quote:USD-P",
"class:fx/crypto",
"quarterly",
"sector:defi",
"enactment:2023-06-15T14:00:00Z",
"settlement:2023-09-30T08:00:00Z"
],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "3600",
"probability": "0.9999",
"auctionExtension": "120"
},
{
"horizon": "14400",
"probability": "0.9999",
"auctionExtension": "180"
},
{
"horizon": "43200",
"probability": "0.9999",
"auctionExtension": "300"
}
]
},
"liquidityMonitoringParameters": {
"targetStakeParameters": {
"timeWindow": "3600",
"scalingFactor": 1
},
"triggeringRatio": "0.7",
"auctionExtension": "1"
},
"liquiditySlaParameters": {
"priceRange": "0.05",
"commitmentMinTimeFraction": "0.95",
"performanceHysteresisEpochs": 1,
"slaCompetitionFactor": "0.95"
},
"logNormal": {
"riskAversionParameter": 0.000001,
"tau": 0.0001140771161,
"params": {
"sigma": 1.5
}
},
"decimalPlaces": "1",
"positionDecimalPlaces": "4",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0.0"
}
}
}
}
@@ -1,16 +0,0 @@
{
"rationale": {
"title": "Market resume test",
"description": "E2E test for market resume proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "b33bb4157e12355db22e41f277ddd0c10104dec29a4d6960bbcb96d186c40cbd",
"updateType": "MARKET_STATE_UPDATE_TYPE_RESUME"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -1,16 +0,0 @@
{
"rationale": {
"title": "Market suspended test",
"description": "E2E test for market suspended proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "",
"updateType": "MARKET_STATE_UPDATE_TYPE_SUSPEND"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -1,17 +0,0 @@
{
"rationale": {
"title": "Market terminate test",
"description": "E2E test for market terminate proposal"
},
"terms": {
"updateMarketState": {
"changes": {
"marketId": "",
"updateType": "MARKET_STATE_UPDATE_TYPE_TERMINATE",
"price": "100"
}
},
"closingTimestamp": 0,
"enactmentTimestamp": 0
}
}
@@ -0,0 +1,85 @@
{
"instrument": {
"code": "Token.24h",
"future": {
"quoteName": "fBTC",
"dataSourceSpecForSettlementData": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForTradingTermination": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
"value": "1648684800000000000"
}
]
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "prices.BTC.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.3
}
}
}
@@ -9,7 +9,6 @@ import {
createTenDigitUnixTimeStampForSpecifiedDays,
generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays,
getProposalDetailsValue,
getProposalFromTitle,
getProposalInformationFromTable,
goToMakeNewProposal,
@@ -51,7 +50,6 @@ const openProposals = 'open-proposals';
const viewProposalButton = 'view-proposal-btn';
const proposalTermsToggle = 'proposal-json-toggle';
const marketDataToggle = 'proposal-market-data-toggle';
const marketProposalType = 'proposal-type';
describe(
'Governance flow for proposal details',
@@ -60,17 +58,6 @@ describe(
before('connect wallets and set approval limit', function () {
cy.visit('/');
ethereumWalletConnect();
cy.createMarket();
navigateTo(navigation.proposals);
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(viewProposalButton).click();
});
});
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
});
beforeEach('visit proposals tab', function () {
@@ -309,6 +296,9 @@ describe(
});
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
@@ -317,9 +307,14 @@ describe(
cy.getByTestId(viewProposalButton).click();
});
});
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
getProposalInformationFromTable('ID')
.invoke('text')
.as('parentMarketId')
.then(() => {
cy.VegaWalletSubmitProposal(
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
});
navigateTo(navigation.proposals);
cy.reload();
getProposalFromTitle('Test successor market proposal details').within(
@@ -377,147 +372,19 @@ describe(
});
// 3003-PMAN-011
cy.contains('Parent Market ID').realHover();
cy.get('.underline').contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.contains('Insurance Pool Fraction').realMouseUp().realHover();
cy.get('.underline')
.contains('Insurance Pool Fraction')
.realMouseUp()
.realHover();
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
it('Able to see perpetual market', function () {
const proposalPath =
'src/fixtures/proposals/new-market-perpetual-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('perpetual market proposal').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'New market - perpetual'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketDataToggle).click();
getProposalDetailsValue('Product Type').should(
'contain.text',
'Perpetual'
);
cy.getByTestId(marketProposalType).should(
'have.text',
'New market - perpetual'
);
// Liquidity SLA protocols
getProposalDetailsValue('Performance Hysteresis Epochs').should(
'contain.text',
'1'
);
getProposalDetailsValue('SLA Competition Factor').should(
'contain.text',
'95.00%'
);
getProposalDetailsValue('Epoch Length').should('contain.text', '5s');
getProposalDetailsValue('Non Performance Bond Penalty Max').should(
'contain.text',
'0.05'
);
getProposalDetailsValue('Stake To CCY Volume').should(
'contain.text',
'0.3'
);
getProposalDetailsValue(
'Minimum Probability Of Trading LP Orders'
).should('contain.text', '1e-8');
});
it('Able to see suspended market proposal', function () {
const proposalPath = 'src/fixtures/proposals/suspend-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market suspended test').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'Suspend market'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Suspend market');
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
});
});
it('Able to see resume market proposal', function () {
const proposalPath = 'src/fixtures/proposals/resume-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market resume test').within(() => {
cy.getByTestId(marketProposalType).should('have.text', 'Resume market');
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Resume market');
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
});
});
it('Able to see terminate market proposal', function () {
const proposalPath = 'src/fixtures/proposals/terminate-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
});
getProposalFromTitle('Market terminate test').within(() => {
cy.getByTestId(marketProposalType).should(
'have.text',
'Terminate market'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should(
'have.text',
'Terminate market'
);
cy.getByTestId(marketDataToggle).click();
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
.and('eq', this.parentMarketId);
getProposalDetailsValue('Termination Price').should(
'contain.text',
'0.001 fUSDC'
);
});
});
}
);
@@ -65,10 +65,7 @@ context(
cy.getByTestId(viewProposalButton).click();
});
});
cy.getByTestId('proposal-type').should(
'have.text',
'New market - future'
);
cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(votesTable).within(() => {
cy.contains('Voting has ended.').should('be.visible');
@@ -89,12 +89,11 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
it('Newly created proposals list - shows title and portion of summary', function () {
const proposalPath = 'src/fixtures/proposals/new-market-raw.json';
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
const proposalTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
submitUniqueRawProposal({
proposalBody: proposalPath,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
enactmentTimestamp: proposalTimestamp,
closingTimestamp: proposalTimestamp,
}); // 3001-VOTE-052
// 3001-VOTE-008
// 3001-VOTE-034
@@ -54,7 +54,6 @@ export function submitUniqueRawProposal(proposalFields: {
proposalBody?: string;
proposalTitle?: string;
proposalDescription?: string;
updateMarketId?: string;
closingTimestamp?: number;
enactmentTimestamp?: number;
submit?: boolean;
@@ -72,10 +71,6 @@ export function submitUniqueRawProposal(proposalFields: {
if (proposalFields.proposalDescription) {
rawProposal.rationale.description = proposalFields.proposalDescription;
}
if (proposalFields.updateMarketId) {
rawProposal.terms.updateMarketState.changes.marketId =
proposalFields.updateMarketId;
}
if (proposalFields.closingTimestamp) {
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
} else if (
@@ -237,25 +232,21 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
return filepath;
}
export function getProposalDetailsValue(RowName: string) {
return cy
.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first();
}
export function validateProposalDetailsDiff(
RowName: string,
changeType: proposalChangeType,
newValue: string,
oldValue?: string
) {
getProposalDetailsValue(RowName).within(() => {
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
cy.contains(RowName)
.parentsUntil(proposalInformationTableRows)
.parent()
.first()
.within(() => {
cy.contains(changeType).should('be.visible');
cy.contains(newValue).should('be.visible');
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
});
}
function getFormattedTime() {
@@ -108,7 +108,7 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
liquiditySlaParameters: {
priceRange: '0.5',
commitmentMinTimeFraction: '0.1',
performanceHysteresisEpochs: 2,
performanceHysteresisEpochs: 0,
slaCompetitionFactor: '0.1',
},
quadraticSlippageFactor: '0',
@@ -243,7 +243,7 @@ export function createSuccessorMarketProposalTxBody(
liquiditySlaParameters: {
priceRange: '0.5',
commitmentMinTimeFraction: '0.1',
performanceHysteresisEpochs: 2,
performanceHysteresisEpochs: 0,
slaCompetitionFactor: '0.1',
},
instrument: {
-4
View File
@@ -8,7 +8,6 @@ NX_FAIRGROUND=false
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https://governance.stagnet1.vega.rocks","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz"}'
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_WALLET_URL=http://localhost:1789
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
@@ -33,6 +32,3 @@ LC_ALL="en_US.UTF-8"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_METAMASK_SNAPS=true
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
NX_GOVERNANCE_TRANSFERS=false
-2
View File
@@ -33,5 +33,3 @@ CYPRESS_FAIRGROUND=false
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_METAMASK_SNAPS=false
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
-2
View File
@@ -25,5 +25,3 @@ NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/m
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_METAMASK_SNAPS=true
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
+1 -3
View File
@@ -17,12 +17,10 @@ NX_VEGA_REST_URL=https://api.vega.community/api/v2/
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_METAMASK_SNAPS=false
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
-2
View File
@@ -23,5 +23,3 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_METAMASK_SNAPS=false
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
-4
View File
@@ -10,7 +10,6 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
@@ -21,6 +20,3 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_METAMASK_SNAPS=true
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
NX_GOVERNANCE_TRANSFERS=true
-3
View File
@@ -15,7 +15,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
@@ -26,5 +25,3 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_METAMASK_SNAPS=true
NX_PRODUCT_PERPETUALS=true
NX_UPDATE_MARKET_STATE=true
-2
View File
@@ -22,5 +22,3 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
NX_METAMASK_SNAPS=false
NX_PRODUCT_PERPETUALS=false
NX_UPDATE_MARKET_STATE=false
+1 -1
View File
@@ -308,7 +308,7 @@ const AppContainer = () => {
<Router>
<ScrollToTop />
<AppStateProvider>
<div className="min-h-full text-white grid">
<div className="min-h-full text-white">
<NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={
+1 -17
View File
@@ -613,9 +613,6 @@
"proposalDetails": "Proposal details",
"marketSpecification": "Market specification",
"viewMarketJson": "View market JSON",
"marketId": "Market ID",
"marketName": "Market name",
"marketCode": "Market code",
"proposalDescription": "Description",
"currentlySetTo": "Currently expected to ",
"currently": "currently",
@@ -708,17 +705,10 @@
"parameter": "parameter",
"NewMarketProposal": "New market proposal",
"UpdateMarketProposal": "Update market proposal",
"UpdateMarketStateProposal": "Update market state proposal",
"MarketChange": "Market change",
"MarketStateChange": "Market state change",
"MarketDetails": "Market details",
"NewAssetProposal": "New asset proposal",
"UpdateAssetProposal": "Update asset proposal",
"NewFreeformProposal": "New freeform proposal",
"NewRawProposal": "New proposal",
"MARKET_STATE_UPDATE_TYPE_RESUME": "Resume market",
"MARKET_STATE_UPDATE_TYPE_SUSPEND": "Suspend market",
"MARKET_STATE_UPDATE_TYPE_TERMINATE": "Terminate market",
"MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a proposal",
"MinProposalVoteRequirements": "You must have at least {{value}} VEGA associated to vote on this proposal",
"totalSupply": "Total Supply",
@@ -727,11 +717,7 @@
"ProposalDocsPrefix": "For guidance on how to make proposals, see",
"NetworkParameter": "Network parameter",
"NewMarket": "New market",
"NewMarketPerpetualProduct": "New market - perpetual",
"NewMarketFutureProduct": "New market - future",
"NewMarketSpotProduct": "New market - spot",
"UpdateMarket": "Update market",
"UpdateMarketState": "Update market state",
"NewAsset": "New asset",
"UpdateAsset": "Update asset",
"AssetID": "Asset ID",
@@ -888,7 +874,5 @@
"HowToPropose": "How to make a proposal",
"HowToProposeRawStep1": "1. Sense check your proposal with the community on the forum:",
"HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:",
"HowToProposeRawStep3": "3. Submit on-chain below",
"proposalTransferDetails": "New governance transfer details",
"proposalCancelTransferDetails": "Cancel governance transfer details"
"HowToProposeRawStep3": "3. Submit on-chain below"
}
+7 -16
View File
@@ -12,7 +12,7 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import Routes from '../routes';
import { ExternalLinks, FLAGS } from '@vegaprotocol/environment';
import { ExternalLinks } from '@vegaprotocol/environment';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
@@ -23,6 +23,7 @@ import {
import { Heading, SubHeading } from '../../components/heading';
import * as Schema from '@vegaprotocol/types';
import type { RouteChildProps } from '..';
import type { ProposalFieldsFragment } from '../proposals/proposals/__generated__/Proposals';
import type { NodesFragmentFragment } from '../staking/home/__generated__/Nodes';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
@@ -31,7 +32,6 @@ import {
orderByUpgradeBlockHeight,
} from '../proposals/components/proposals-list/proposals-list';
import { BigNumber } from '../../lib/bignumber';
import type { ProposalQuery } from '../proposals/proposal/__generated__/Proposal';
const nodesToShow = 6;
@@ -39,7 +39,7 @@ const HomeProposals = ({
proposals,
protocolUpgradeProposals,
}: {
proposals: ProposalQuery['proposal'][];
proposals: ProposalFieldsFragment[];
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
}) => {
const { t } = useTranslation();
@@ -60,12 +60,9 @@ const HomeProposals = ({
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
))}
{proposals.map(
(proposal) =>
proposal?.id && (
<ProposalsListItem key={proposal.id} proposal={proposal} />
)
)}
{proposals.map((proposal) => (
<ProposalsListItem key={proposal.id} proposal={proposal} />
))}
</ul>
<div className="mt-6">
@@ -185,10 +182,6 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
},
});
const {
@@ -213,9 +206,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
const proposals = useMemo(
() =>
proposalsData
? getNotRejectedProposals(
removePaginationWrapper(proposalsData.proposalsConnection?.edges)
)
? getNotRejectedProposals(proposalsData.proposalsConnection)
: [],
[proposalsData]
);
@@ -3,28 +3,24 @@ import { Lozenge, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
import {
useCancelTransferProposalDetails,
useNewTransferProposalDetails,
useSuccessorMarketProposalDetails,
} from '@vegaprotocol/proposals';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import Routes from '../../../routes';
import { Link } from 'react-router-dom';
import type { VoteState } from '../vote-details/use-user-vote';
import { VoteBreakdown } from '../vote-breakdown';
import { GovernanceTransferKindMapping } from '@vegaprotocol/types';
export const ProposalHeader = ({
proposal,
isListItem = true,
voteState,
}: {
proposal: ProposalQuery['proposal'];
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
isListItem?: boolean;
voteState?: VoteState | null;
}) => {
@@ -41,10 +37,7 @@ export const ProposalHeader = ({
switch (change?.__typename) {
case 'NewMarket': {
proposalType =
FLAGS.PRODUCT_PERPETUALS && change?.instrument?.product?.__typename
? `NewMarket${change?.instrument?.product?.__typename}`
: 'NewMarket';
proposalType = 'NewMarket';
fallbackTitle = t('NewMarketProposal');
details = (
<>
@@ -68,31 +61,12 @@ export const ProposalHeader = ({
);
break;
}
case 'UpdateMarketState': {
proposalType =
FLAGS.UPDATE_MARKET_STATE && change?.updateType
? t(change.updateType)
: 'UpdateMarketState';
fallbackTitle = t('UpdateMarketStateProposal');
details = (
<span>
{FLAGS.UPDATE_MARKET_STATE &&
change?.market?.id &&
change.updateType ? (
<>
{t(change.updateType)}: {truncateMiddle(change.market.id)}
</>
) : null}
</span>
);
break;
}
case 'UpdateMarket': {
proposalType = 'UpdateMarket';
fallbackTitle = t('UpdateMarketProposal');
details = (
<>
<span>{t('MarketChange')}:</span>{' '}
<span>{t('Market change')}:</span>{' '}
<span>{truncateMiddle(change.marketId)}</span>
</>
);
@@ -152,20 +126,6 @@ export const ProposalHeader = ({
);
break;
}
case 'NewTransfer':
proposalType = 'NewTransfer';
fallbackTitle = t('NewTransferProposal');
details = FLAGS.GOVERNANCE_TRANSFERS ? (
<NewTransferSummary proposalId={proposal?.id} />
) : null;
break;
case 'CancelTransfer':
proposalType = 'CancelTransfer';
fallbackTitle = t('CancelTransferProposal');
details = FLAGS.GOVERNANCE_TRANSFERS ? (
<CancelTransferSummary proposalId={proposal?.id} />
) : null;
break;
}
return (
@@ -243,36 +203,3 @@ const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
</span>
) : null;
};
const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => {
const { t } = useTranslation();
const details = useNewTransferProposalDetails(proposalId);
if (!details) return null;
return (
<span>
{GovernanceTransferKindMapping[details.kind.__typename]}{' '}
{t('transfer from')} <Lozenge>{truncateMiddle(details.source)}</Lozenge>{' '}
{t('to')} <Lozenge>{truncateMiddle(details.destination)}</Lozenge>
</span>
);
};
const CancelTransferSummary = ({
proposalId,
}: {
proposalId?: string | null;
}) => {
const { t } = useTranslation();
const details = useCancelTransferProposalDetails(proposalId);
if (!details) return null;
return (
<span>
{t('Cancel transfer: ')}{' '}
<Lozenge>{truncateMiddle(details.transferId)}</Lozenge>
</span>
);
};
@@ -5,8 +5,6 @@ import {
InstrumentInfoPanel,
KeyDetailsInfoPanel,
LiquidityMonitoringParametersInfoPanel,
LiquidityPriceRangeInfoPanel,
LiquiditySLAParametersInfoPanel,
MetadataInfoPanel,
OracleInfoPanel,
PriceMonitoringBoundsInfoPanel,
@@ -272,21 +270,6 @@ export const ProposalMarketData = ({
market={marketData}
parentMarket={parentMarketData}
/>
<h2 className={marketDataHeaderStyles}>
{t('Liquidity price range')}
</h2>
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
<h2 className={marketDataHeaderStyles}>
{t('Liquidity SLA protocol')}
</h2>
<LiquiditySLAParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
</div>
</>
)}
@@ -1,2 +0,0 @@
export * from './proposal-transfer-details';
export * from './proposal-cancel-transfer-details';
@@ -1,37 +0,0 @@
import { useTranslation } from 'react-i18next';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import { useCancelTransferProposalDetails } from '@vegaprotocol/proposals';
import {
KeyValueTable,
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
export const ProposalCancelTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const details = useCancelTransferProposalDetails(proposal?.id);
if (!details) {
return null;
}
return (
<>
<SubHeading title={t('proposalCancelTransferDetails')} />
<RoundedWrapper paddingBottom={true}>
<KeyValueTable data-testid="proposal-cancel-transfer-details-table">
<KeyValueTableRow noBorder={true}>
{t('transferId')}
{details.transferId}
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
</>
);
};
@@ -1,145 +0,0 @@
import { useState } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import { useTranslation } from 'react-i18next';
import {
KeyValueTable,
KeyValueTableRow,
RoundedWrapper,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { useNewTransferProposalDetails } from '@vegaprotocol/proposals';
import {
AccountTypeMapping,
DescriptionGovernanceTransferTypeMapping,
GovernanceTransferKindMapping,
GovernanceTransferTypeMapping,
} from '@vegaprotocol/types';
import {
addDecimalsFormatNumberQuantum,
formatDateWithLocalTimezone,
} from '@vegaprotocol/utils';
export const ProposalTransferDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const [show, setShow] = useState(false);
const details = useNewTransferProposalDetails(proposal?.id);
if (!details) {
return null;
}
return (
<>
<CollapsibleToggle
toggleState={show}
setToggleState={setShow}
dataTestId="proposal-transfer-details"
>
<SubHeading title={t('proposalTransferDetails')} />
</CollapsibleToggle>
{show && (
<RoundedWrapper paddingBottom={true}>
<KeyValueTable data-testid="proposal-transfer-details-table">
{/* The source account */}
<KeyValueTableRow>
{t('Source')}
{details.source}
</KeyValueTableRow>
{/* The type of source account */}
<KeyValueTableRow>
{t('Source Type')}
{AccountTypeMapping[details.sourceType]}
</KeyValueTableRow>
{/* The destination account */}
<KeyValueTableRow>
{t('Destination')}
{details.destination}
</KeyValueTableRow>
{/* The type of destination account */}
<KeyValueTableRow>
{t('Destination Type')}
{AccountTypeMapping[details.destinationType]}
</KeyValueTableRow>
{/* The asset to transfer */}
<KeyValueTableRow>
{t('Asset')}
{details.asset.symbol}
</KeyValueTableRow>
{/*The fraction of the balance to be transfer */}
<KeyValueTableRow>
{t('Fraction Of Balance')}
{`${Number(details.fraction_of_balance) * 100}%`}
</KeyValueTableRow>
{/* The maximum amount to be transferred */}
<KeyValueTableRow>
{t('Amount')}
{addDecimalsFormatNumberQuantum(
details.amount,
details.asset.decimals,
details.asset.quantum
)}
</KeyValueTableRow>
{/* The type of the governance transfer */}
<KeyValueTableRow>
{t('Transfer Type')}
<Tooltip
description={
DescriptionGovernanceTransferTypeMapping[details.transferType]
}
>
<span>
{GovernanceTransferTypeMapping[details.transferType]}
</span>
</Tooltip>
</KeyValueTableRow>
{/* The type of governance transfer being made, i.e. a one-off or recurring trans */}
<KeyValueTableRow>
{t('Kind')}
{GovernanceTransferKindMapping[details.kind.__typename]}
</KeyValueTableRow>
{details.kind.__typename === 'OneOffGovernanceTransfer' &&
details.kind.deliverOn && (
<KeyValueTableRow noBorder={true}>
{t('Deliver On')}
{formatDateWithLocalTimezone(
new Date(details.kind.deliverOn)
)}
</KeyValueTableRow>
)}
{details.kind.__typename === 'RecurringGovernanceTransfer' && (
<>
<KeyValueTableRow noBorder={!details.kind.endEpoch}>
{t('Start On')}
<span>{details.kind.startEpoch}</span>
</KeyValueTableRow>
{details.kind.endEpoch && (
<KeyValueTableRow noBorder={true}>
{t('End on')}
{details.kind.endEpoch}
</KeyValueTableRow>
)}
</>
)}
</KeyValueTable>
</RoundedWrapper>
)}
</>
);
};
@@ -1 +0,0 @@
export * from './proposal-update-market-state';
@@ -1,123 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ProposalUpdateMarketState } from './proposal-update-market-state';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { MarketUpdateType } from '@vegaprotocol/types';
describe('<ProposalUpdateMarketState />', () => {
const suspendProposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarketState',
market: {
id: '1',
decimalPlaces: 0,
tradableInstrument: {
instrument: {
name: 'suspendProposal Name',
code: 'suspendProposal Code',
product: {
__typename: 'Future',
quoteName: 'USD',
},
},
},
},
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_SUSPEND,
},
},
});
const resumeProposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarketState',
market: {
id: '1',
decimalPlaces: 0,
tradableInstrument: {
instrument: {
name: 'resumeProposal Name',
code: 'resumeProposal Code',
product: {
__typename: 'Future',
quoteName: 'USD',
},
},
},
},
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_RESUME,
},
},
});
const terminateProposal = generateProposal({
terms: {
change: {
__typename: 'UpdateMarketState',
market: {
id: '1',
decimalPlaces: 0,
tradableInstrument: {
instrument: {
name: 'terminateProposal Name',
code: 'terminateProposal Code',
product: {
__typename: 'Future',
quoteName: 'USD',
},
},
},
},
updateType: MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '123',
},
},
});
it('should render nothing if proposal is null', () => {
render(<ProposalUpdateMarketState proposal={null} />);
expect(screen.queryByTestId('proposal-update-market-state')).toBeNull();
});
it('should toggle details when CollapsibleToggle is clicked', () => {
render(<ProposalUpdateMarketState proposal={suspendProposal} />);
expect(
screen.queryByTestId('proposal-update-market-state-table')
).toBeNull();
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
expect(
screen.getByTestId('proposal-update-market-state-table')
).toBeInTheDocument();
});
it('should display suspend market information when showDetails is true', () => {
render(<ProposalUpdateMarketState proposal={suspendProposal} />);
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
expect(screen.getByText('suspendProposal Name')).toBeInTheDocument();
expect(screen.getByText('suspendProposal Code')).toBeInTheDocument();
});
it('should display resume market information when showDetails is true', () => {
render(<ProposalUpdateMarketState proposal={resumeProposal} />);
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
expect(screen.getByText('resumeProposal Name')).toBeInTheDocument();
expect(screen.getByText('resumeProposal Code')).toBeInTheDocument();
});
it('should display terminate market information when showDetails is true', () => {
render(<ProposalUpdateMarketState proposal={terminateProposal} />);
fireEvent.click(screen.getByTestId('proposal-market-data-toggle'));
expect(screen.getByText('terminateProposal Name')).toBeInTheDocument();
expect(screen.getByText('terminateProposal Code')).toBeInTheDocument();
expect(screen.getByText('123 USD')).toBeInTheDocument();
});
});
@@ -1,84 +0,0 @@
import { useTranslation } from 'react-i18next';
import {
KeyValueTable,
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { Row } from '@vegaprotocol/markets';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { useState } from 'react';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
interface ProposalUpdateMarketStateProps {
proposal: ProposalQuery['proposal'];
}
export const ProposalUpdateMarketState = ({
proposal,
}: ProposalUpdateMarketStateProps) => {
const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false);
let market;
let isTerminate = false;
if (!proposal) {
return null;
}
if (proposal?.terms.change.__typename === 'UpdateMarketState') {
market = proposal?.terms?.change?.market;
isTerminate =
proposal?.terms?.change?.updateType ===
'MARKET_STATE_UPDATE_TYPE_TERMINATE';
}
return (
<section className="relative" data-testid="proposal-update-market-state">
<CollapsibleToggle
toggleState={showDetails}
setToggleState={setShowDetails}
dataTestId="proposal-market-data-toggle"
>
<SubHeading title={t('MarketDetails')} />
</CollapsibleToggle>
{showDetails && (
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
{proposal?.terms.change.__typename === 'UpdateMarketState' && (
<KeyValueTable data-testid="proposal-update-market-state-table">
<KeyValueTableRow>
{t('marketId')}
{market?.id}
</KeyValueTableRow>
<KeyValueTableRow>
{t('marketName')}
{market?.tradableInstrument?.instrument?.name}
</KeyValueTableRow>
<KeyValueTableRow noBorder={!isTerminate}>
{t('marketCode')}
{market?.tradableInstrument?.instrument?.code}
</KeyValueTableRow>
{isTerminate && (
<Row
field="termination-price"
value={proposal?.terms?.change?.price}
assetSymbol={
market?.tradableInstrument?.instrument?.product
?.__typename === 'Future' ||
market?.tradableInstrument?.instrument?.product
?.__typename === 'Perpetual'
? market?.tradableInstrument?.instrument?.product
?.quoteName
: undefined
}
decimalPlaces={market?.decimalPlaces}
/>
)}
</KeyValueTable>
)}
</RoundedWrapper>
)}
</section>
);
};
@@ -10,26 +10,21 @@ import { UserVote } from '../vote-details';
import { ListAsset } from '../list-asset';
import Routes from '../../../routes';
import { ProposalMarketData } from '../proposal-market-data';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MarketInfo } from '@vegaprotocol/markets';
import type { AssetQuery } from '@vegaprotocol/assets';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalMarketChanges } from '../proposal-market-changes';
import { ProposalUpdateMarketState } from '../proposal-update-market-state';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
import { useVoteSubmit } from '@vegaprotocol/proposals';
import { useUserVote } from '../vote-details/use-user-vote';
import {
ProposalCancelTransferDetails,
ProposalTransferDetails,
} from '../proposal-transfer';
import { FLAGS } from '@vegaprotocol/environment';
export interface ProposalProps {
proposal: ProposalQuery['proposal'];
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
marketData?: MarketInfo | null;
newMarketData?: MarketInfo | null;
parentMarketData?: MarketInfo | null;
assetData?: AssetQuery | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -44,7 +39,7 @@ export const Proposal = ({
proposal,
networkParams,
restData,
marketData,
newMarketData,
parentMarketData,
assetData,
originalMarketProposalRestData,
@@ -79,15 +74,14 @@ export const Proposal = ({
if (networkParams) {
switch (proposal.terms.change.__typename) {
case 'UpdateMarket':
case 'UpdateMarketState':
minVoterBalance =
networkParams.governance_proposal_updateMarket_minVoterBalance;
break;
case 'NewMarket':
minVoterBalance =
networkParams.governance_proposal_market_minVoterBalance;
break;
case 'UpdateMarket':
minVoterBalance =
networkParams.governance_proposal_updateMarket_minVoterBalance;
break;
case 'NewAsset':
minVoterBalance =
networkParams.governance_proposal_asset_minVoterBalance;
@@ -104,37 +98,9 @@ export const Proposal = ({
minVoterBalance =
networkParams.governance_proposal_freeform_minVoterBalance;
break;
case 'NewTransfer':
// TODO: check minVoterBalance for 'NewTransfer'
minVoterBalance =
networkParams.governance_proposal_freeform_minVoterBalance;
break;
case 'CancelTransfer':
// TODO: check minVoterBalance for 'CancelTransfer'
minVoterBalance =
networkParams.governance_proposal_freeform_minVoterBalance;
}
}
// Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on.
const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && (
<>
{proposal.terms.change.__typename === 'NewTransfer' && (
/** Governance New Transfer Details */
<div className="mb-4">
<ProposalTransferDetails proposal={proposal} />
</div>
)}
{proposal.terms.change.__typename === 'CancelTransfer' && (
/** Governance Cancel Transfer Details */
<div className="mb-4">
<ProposalCancelTransferDetails proposal={proposal} />
</div>
)}
</>
);
return (
<section data-testid="proposal">
<div className="flex items-center gap-1 mb-6">
@@ -179,21 +145,15 @@ export const Proposal = ({
<ProposalDescription description={proposal.rationale.description} />
</div>
{marketData && (
{newMarketData && (
<div className="mb-4">
<ProposalMarketData
marketData={marketData}
marketData={newMarketData}
parentMarketData={parentMarketData ? parentMarketData : undefined}
/>
</div>
)}
{proposal.terms.change.__typename === 'UpdateMarketState' && (
<div className="mb-4">
<ProposalUpdateMarketState proposal={proposal} />
</div>
)}
{proposal.terms.change.__typename === 'UpdateMarket' && (
<div className="mb-4">
<ProposalMarketChanges
@@ -220,8 +180,6 @@ export const Proposal = ({
</div>
)}
{governanceTransferDetails}
<div className="mb-10">
<RoundedWrapper paddingBottom={true}>
<UserVote
@@ -2,10 +2,11 @@ import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { useUserVote } from '../vote-details/use-user-vote';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface ProposalsListItemProps {
proposal?: ProposalQuery['proposal'] | null;
proposal?: ProposalFieldsFragment | ProposalQuery['proposal'] | null;
}
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
@@ -16,14 +16,14 @@ import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/propos
import { ExternalLinks } from '@vegaprotocol/environment';
interface ProposalsListProps {
proposals: Array<ProposalQuery['proposal']>;
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
lastBlockHeight?: string;
}
interface SortedProposalsProps {
open: ProposalQuery['proposal'][];
closed: ProposalQuery['proposal'][];
open: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
closed: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
}
interface SortedProtocolUpgradeProposalsProps {
@@ -31,7 +31,7 @@ interface SortedProtocolUpgradeProposalsProps {
closed: ProtocolUpgradeProposalFieldsFragment[];
}
export const orderByDate = (arr: ProposalQuery['proposal'][]) =>
export const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
arr,
[
@@ -92,12 +92,12 @@ export const ProposalsList = ({
return {
open:
initialSorting.open.length > 0
? orderByDate(initialSorting.open as ProposalQuery['proposal'][])
? orderByDate(initialSorting.open as ProposalFieldsFragment[])
: [],
closed:
initialSorting.closed.length > 0
? orderByDate(
initialSorting.closed as ProposalQuery['proposal'][]
initialSorting.closed as ProposalFieldsFragment[]
).reverse()
: [],
};
@@ -3,17 +3,20 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface ProposalsListProps {
proposals: ProposalQuery['proposal'][];
proposals: Array<ProposalQuery['proposal'] | ProposalFieldsFragment>;
}
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const filterPredicate = (p: ProposalQuery['proposal']) =>
const filterPredicate = (
p: ProposalFieldsFragment | ProposalQuery['proposal']
) =>
p?.id?.includes(filterString) ||
p?.party?.id?.toString().includes(filterString);
@@ -28,20 +28,17 @@ export const useProposalNetworkParams = ({
NetworkParams.governance_proposal_freeform_requiredParticipation,
]);
const fallback = {
requiredMajority: new BigNumber(1),
requiredMajorityLP: new BigNumber(0),
requiredParticipation: new BigNumber(1),
requiredParticipationLP: new BigNumber(0),
};
if (!params) {
return fallback;
return {
requiredMajority: new BigNumber(1),
requiredMajorityLP: new BigNumber(0),
requiredParticipation: new BigNumber(1),
requiredParticipationLP: new BigNumber(0),
};
}
switch (proposal?.terms.change.__typename) {
case 'UpdateMarket':
case 'UpdateMarketState':
return {
requiredMajority:
params.governance_proposal_updateMarket_requiredMajority,
@@ -92,6 +89,6 @@ export const useProposalNetworkParams = ({
),
};
default:
return fallback;
throw new Error('Unknown proposal type');
}
};
@@ -1,53 +1,4 @@
fragment NewMarketProductField on Proposal {
terms {
change {
... on NewMarket {
instrument {
product {
__typename
}
}
}
}
}
}
fragment UpdateMarketState on Proposal {
terms {
change {
... on UpdateMarketState {
updateType
market {
decimalPlaces
id
tradableInstrument {
instrument {
product {
__typename
... on Future {
quoteName
}
... on Perpetual {
quoteName
}
}
name
code
}
}
}
updateType
price
}
}
}
}
query Proposal(
$proposalId: ID!
$includeNewMarketProductField: Boolean!
$includeUpdateMarketState: Boolean!
) {
query Proposal($proposalId: ID!) {
proposal(id: $proposalId) {
id
rationale {
@@ -62,8 +13,6 @@ query Proposal(
id
}
errorDetails
...NewMarketProductField @include(if: $includeNewMarketProductField)
...UpdateMarketState @include(if: $includeUpdateMarketState)
terms {
closingDatetime
enactmentDatetime
File diff suppressed because one or more lines are too long
@@ -6,11 +6,6 @@ import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { ProposalDocument } from './__generated__/Proposal';
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn(() => ({ data: [], loading: false })),
}));
jest.mock('../components/proposal', () => ({
Proposal: () => <div data-testid="proposal" />,
}));
@@ -49,9 +44,7 @@ const renderComponent = (
);
};
// These tests are broken due to schema changes. NewMarket.futureProduct -> NewMarket.product union
// eslint-disable-next-line jest/no-disabled-tests
describe.skip('Proposal container', () => {
describe('Proposal container', () => {
it('Renders not found if the proposal is not found', async () => {
render(renderComponent(null, 'foo'));
await waitFor(() => {
@@ -53,11 +53,7 @@ export const ProposalContainer = () => {
const { data, loading, error, refetch } = useProposalQuery({
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
proposalId: params.proposalId || '',
includeNewMarketProductField: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketState: !!FLAGS.UPDATE_MARKET_STATE,
},
variables: { proposalId: params.proposalId || '' },
skip: !params.proposalId,
});
@@ -95,9 +91,9 @@ export const ProposalContainer = () => {
);
const {
data: marketData,
loading: marketLoading,
error: marketError,
data: newMarketData,
loading: newMarketLoading,
error: newMarketError,
} = useDataProvider({
dataProvider: marketInfoProvider,
skipUpdates: true,
@@ -113,9 +109,9 @@ export const ProposalContainer = () => {
error: parentMarketIdError,
} = useParentMarketIdQuery({
variables: {
marketId: marketData?.id || '',
marketId: newMarketData?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !marketData?.id,
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !newMarketData?.id,
});
const {
@@ -195,7 +191,7 @@ export const ProposalContainer = () => {
<AsyncRenderer
loading={
loading ||
marketLoading ||
newMarketLoading ||
assetLoading ||
networkParamsLoading ||
parentMarketIdLoading ||
@@ -210,7 +206,7 @@ export const ProposalContainer = () => {
}
error={
error ||
marketError ||
newMarketError ||
assetError ||
networkParamsError ||
parentMarketIdError ||
@@ -222,7 +218,7 @@ export const ProposalContainer = () => {
data={{
...data,
...networkParams,
...(marketData ? { newMarketData: marketData } : {}),
...(newMarketData ? { newMarketData } : {}),
...(parentMarketData ? { parentMarketData } : {}),
...(assetData ? { assetData } : {}),
...(restData ? { restData } : {}),
@@ -239,7 +235,7 @@ export const ProposalContainer = () => {
proposal={data.proposal}
networkParams={networkParams}
restData={restData}
marketData={marketData}
newMarketData={newMarketData}
parentMarketData={parentMarketData}
assetData={assetData}
originalMarketProposalRestData={originalMarketProposalRestData}
@@ -1,48 +1,3 @@
fragment NewMarketProductFields on Proposal {
terms {
change {
... on NewMarket {
instrument {
product {
__typename
}
}
}
}
}
}
fragment UpdateMarketStates on Proposal {
terms {
change {
... on UpdateMarketState {
updateType
market {
decimalPlaces
id
tradableInstrument {
instrument {
product {
__typename
... on Future {
quoteName
}
... on Perpetual {
quoteName
}
}
name
code
}
}
}
updateType
price
}
}
}
}
fragment ProposalFields on Proposal {
id
rationale {
@@ -124,16 +79,11 @@ fragment ProposalFields on Proposal {
}
}
query Proposals(
$includeNewMarketProductFields: Boolean!
$includeUpdateMarketStates: Boolean!
) {
query Proposals {
proposalsConnection {
edges {
node {
...ProposalFields
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
}
}
}
@@ -3,67 +3,13 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type NewMarketProductFieldsFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
export type UpdateMarketStatesFragment = { __typename?: 'Proposal', terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } };
export type ProposalFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } };
export type ProposalsQueryVariables = Types.Exact<{
includeNewMarketProductFields: Types.Scalars['Boolean'];
includeUpdateMarketStates: Types.Scalars['Boolean'];
}>;
export type ProposalsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null, product?: { __typename: 'FutureProduct' } | { __typename: 'PerpetualProduct' } | { __typename: 'SpotProduct' } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState', updateType: Types.MarketUpdateType, price?: string | null, market: { __typename?: 'Market', decimalPlaces: number, id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, product: { __typename: 'Future', quoteName: string } | { __typename: 'Perpetual', quoteName: string } | { __typename: 'Spot' } } } } } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
export type ProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename?: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, withdrawThreshold: string, lifetimeLimit: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', settlementAsset: { __typename?: 'Asset', symbol: string } } | null } } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset', quantum: string, assetId: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalEquityLikeShareWeight: string } } } } | null> | null } | null };
export const NewMarketProductFieldsFragmentDoc = gql`
fragment NewMarketProductFields on Proposal {
terms {
change {
... on NewMarket {
instrument {
product {
__typename
}
}
}
}
}
}
`;
export const UpdateMarketStatesFragmentDoc = gql`
fragment UpdateMarketStates on Proposal {
terms {
change {
... on UpdateMarketState {
updateType
market {
decimalPlaces
id
tradableInstrument {
instrument {
product {
__typename
... on Future {
quoteName
}
... on Perpetual {
quoteName
}
}
name
code
}
}
}
updateType
price
}
}
}
}
`;
export const ProposalFieldsFragmentDoc = gql`
fragment ProposalFields on Proposal {
id
@@ -147,20 +93,16 @@ export const ProposalFieldsFragmentDoc = gql`
}
`;
export const ProposalsDocument = gql`
query Proposals($includeNewMarketProductFields: Boolean!, $includeUpdateMarketStates: Boolean!) {
query Proposals {
proposalsConnection {
edges {
node {
...ProposalFields
...NewMarketProductFields @include(if: $includeNewMarketProductFields)
...UpdateMarketStates @include(if: $includeUpdateMarketStates)
}
}
}
}
${ProposalFieldsFragmentDoc}
${NewMarketProductFieldsFragmentDoc}
${UpdateMarketStatesFragmentDoc}`;
${ProposalFieldsFragmentDoc}`;
/**
* __useProposalsQuery__
@@ -174,12 +116,10 @@ ${UpdateMarketStatesFragmentDoc}`;
* @example
* const { data, loading, error } = useProposalsQuery({
* variables: {
* includeNewMarketProductFields: // value for 'includeNewMarketProductFields'
* includeUpdateMarketStates: // value for 'includeUpdateMarketStates'
* },
* });
*/
export function useProposalsQuery(baseOptions: Apollo.QueryHookOptions<ProposalsQuery, ProposalsQueryVariables>) {
export function useProposalsQuery(baseOptions?: Apollo.QueryHookOptions<ProposalsQuery, ProposalsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ProposalsQuery, ProposalsQueryVariables>(ProposalsDocument, options);
}
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { SplashLoader } from '../../../components/splash-loader';
import { ProposalsList } from '../components/proposals-list';
import { useProposalsQuery } from './__generated__/Proposals';
import { getNodes, removePaginationWrapper } from '@vegaprotocol/utils';
import { getNodes } from '@vegaprotocol/utils';
import {
ProposalState,
ProtocolUpgradeProposalStatus,
@@ -15,13 +15,14 @@ import type { NodeConnection, NodeEdge } from '@vegaprotocol/utils';
import type { ProposalFieldsFragment } from './__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
export function getNotRejectedProposals(data?: ProposalFieldsFragment[]) {
export function getNotRejectedProposals<T extends ProposalFieldsFragment>(
data?: NodeConnection<NodeEdge<T>> | null
): T[] {
return flow([
(data) =>
data.filter(
(p: ProposalFieldsFragment) => p?.state !== ProposalState.STATE_REJECTED
getNodes<ProposalFieldsFragment>(data, (p) =>
p ? p.state !== ProposalState.STATE_REJECTED : false
),
])(data);
}
@@ -46,10 +47,6 @@ export const ProposalsContainer = () => {
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
},
});
const {
@@ -63,10 +60,7 @@ export const ProposalsContainer = () => {
});
const proposals = useMemo(
() =>
getNotRejectedProposals(
removePaginationWrapper(data?.proposalsConnection?.edges)
),
() => getNotRejectedProposals(data?.proposalsConnection),
[data]
);
@@ -6,11 +6,11 @@ import { SplashLoader } from '../../../components/splash-loader';
import { RejectedProposalsList } from '../components/proposals-list';
import type { ProposalFieldsFragment } from '../proposals/__generated__/Proposals';
import { useProposalsQuery } from '../proposals/__generated__/Proposals';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { NodeConnection, NodeEdge } from '@vegaprotocol/utils';
import { getNodes } from '@vegaprotocol/utils';
import flow from 'lodash/flow';
import orderBy from 'lodash/orderBy';
import { ProposalState } from '@vegaprotocol/types';
import { FLAGS } from '@vegaprotocol/environment';
const orderByDate = (arr: ProposalFieldsFragment[]) =>
orderBy(
@@ -22,11 +22,13 @@ const orderByDate = (arr: ProposalFieldsFragment[]) =>
['desc', 'desc']
);
export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
export function getRejectedProposals<T extends ProposalFieldsFragment>(
data?: NodeConnection<NodeEdge<ProposalFieldsFragment>> | null
): T[] {
return flow([
(data) =>
data.filter(
(p: ProposalFieldsFragment) => p?.state === ProposalState.STATE_REJECTED
getNodes<ProposalFieldsFragment>(data, (p) =>
p ? p?.state === ProposalState.STATE_REJECTED : false
),
orderByDate,
])(data);
@@ -34,21 +36,11 @@ export function getRejectedProposals(data?: ProposalFieldsFragment[] | null) {
export const RejectedProposalsContainer = () => {
const { t } = useTranslation();
const { data, loading, error } = useProposalsQuery({
pollInterval: 5000,
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
variables: {
includeNewMarketProductFields: !!FLAGS.PRODUCT_PERPETUALS,
includeUpdateMarketStates: !!FLAGS.UPDATE_MARKET_STATE,
},
});
const { data, loading, error } = useProposalsQuery();
const proposals = useMemo(
() =>
getRejectedProposals(
removePaginationWrapper(data?.proposalsConnection?.edges)
),
getRejectedProposals<ProposalFieldsFragment>(data?.proposalsConnection),
[data]
);
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useMemo, useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
@@ -86,18 +86,12 @@ export const EpochIndividualRewards = ({
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
if (prevEpochIdRef.current === null) {
prevEpochIdRef.current = epochId;
} else if (epochId !== prevEpochIdRef.current) {
// When the epoch changes, we want to refetch the data to update the current page
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
refetchData();
}
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
}, [epochId, data, refetchData]);
return (
<AsyncRenderer
@@ -3,7 +3,7 @@ import { forwardRef, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -85,17 +85,17 @@ const TopThirdCellRenderer = (
}}
className="grid grid-cols-[60px_1fr] w-full h-full py-4 px-0 text-sm text-white text-center overflow-scroll"
>
<div className="px-3 text-xs text-left">
<div className="text-xs text-left px-3">
{params?.data?.rankingDisplay}
</div>
<div className="px-3 whitespace-normal">
<div className="whitespace-normal px-3">
<div className="mb-4">
<Button
data-testid="show-all-validators"
rightIcon={
<Icon
name="arrow-right"
className="mr-2 align-text-top fill-current"
className="fill-current mr-2 align-text-top"
/>
}
className="inline-flex items-center"
@@ -103,7 +103,7 @@ const TopThirdCellRenderer = (
{t('Reveal top validators')}
</Button>
</div>
<p className="mb-0 font-semibold text-white">
<p className="font-semibold text-white mb-0">
{t(
'Validators with too great a stake share will have the staking rewards for their delegators penalised.'
)}
@@ -1,7 +1,7 @@
import { forwardRef, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -11,8 +11,102 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-0');
});
it('should open usage breakdown dialog when clicked on used', () => {
it('renders accounts', () => {
// 7001-COLL-001
// 7001-COLL-002
// 7001-COLL-003
// 7001-COLL-004
// 7001-COLL-005
// 7001-COLL-006
// 7001-COLL-007
// 1003-TRAN-001
// 7001-COLL-012
const tradingAccountRowId = '[row-id="t-0"]';
cy.getByTestId('Collateral').click();
cy.getByTestId('tab-accounts').should('be.visible');
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="asset.symbol"]')
.should('have.text', 'AST0');
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="used"]')
.should('have.text', '1.01' + '1.00%');
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="available"]')
.should('have.text', '100.00');
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="total"]')
.should('have.text', '101.01');
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="accounts-actions"]')
.should('have.text', '');
cy.getByTestId('tab-accounts')
.get('[col-id="accounts-actions"]')
.find('[data-testid="dropdown-menu"]')
.eq(1)
.click();
cy.getByTestId('deposit').should('be.visible');
cy.getByTestId('withdraw').should('be.visible');
cy.getByTestId('transfer').should('be.visible');
cy.getByTestId('breakdown').should('be.visible');
cy.getByTestId('Collateral').click({ force: true });
});
it('should open asset details dialog when clicked on symbol', () => {
// 7001-COLL-008
// 6501-ASSE-001
// 6501-ASSE-002
// 6501-ASSE-003
// 6501-ASSE-004
// 6501-ASSE-005
// 6501-ASSE-006
// 6501-ASSE-007
// 6501-ASSE-008
// 6501-ASSE-009
// 6501-ASSE-010
// 6501-ASSE-011
// 6501-ASSE-012
// 6501-ASSE-013
const titles = [
'ID',
'Type',
'Name',
'Symbol',
'Decimals',
'Quantum',
'Status',
'Contract address',
'Withdrawal threshold',
'Lifetime limit',
'Infrastructure fee account balance',
'Global reward pool account balance',
'Maker paid fees account balance',
'Maker received fees account balance',
'Liquidity provision fee reward account balance',
'Market proposer reward account balance',
];
cy.get('[col-id="asset.symbol"]').contains('tEURO').click();
cy.get('[data-testid$="_label"]').should('have.length', 16);
cy.get('[data-testid$="_label"]').each((element, index) => {
cy.wrap(element).should('have.text', titles[index]);
});
cy.getByTestId(dialogClose).click();
cy.getByTestId(dialogClose).should('not.exist');
});
it('should open usage breakdown dialog when clicked on used', () => {
// 7001-COLL-009
cy.get('[col-id="used"]').contains('1.01').click();
const headers = ['Market', 'Account type', 'Balance', 'Margin health'];
@@ -0,0 +1,106 @@
import * as Schema from '@vegaprotocol/types';
import { mockConnectWallet } from '@vegaprotocol/cypress';
import {
orderPriceField,
placeOrderBtn,
toggleLimit,
toggleLong,
toggleMarket,
toggleShort,
} from '../support/deal-ticket';
describe('deal ticket basics', { tags: '@smoke' }, () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.clearAllLocalStorage();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('order-connect-wallet').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().next('input').should('be.checked');
cy.getByTestId(toggleLong).click().next('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().next('input').should('be.checked');
cy.getByTestId(toggleMarket).click().next('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).next('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
it('sidebar should be open after reload', () => {
cy.mockTradingPage();
cy.getByTestId('deal-ticket-form').should('be.visible');
cy.getByTestId('Order').click();
cy.getByTestId('deal-ticket-form').should('not.exist');
cy.reload();
cy.getByTestId('deal-ticket-form').should('be.visible');
});
});
describe(
'market states not accepting orders',
{ tags: '@smoke', testIsolation: true },
function () {
//7002-SORD-062
//7002-SORD-063
//7002-SORD-066
const states = [
Schema.MarketState.STATE_REJECTED,
Schema.MarketState.STATE_CANCELLED,
Schema.MarketState.STATE_CLOSED,
Schema.MarketState.STATE_SETTLED,
Schema.MarketState.STATE_TRADING_TERMINATED,
];
states.forEach((marketState) => {
describe(marketState, function () {
beforeEach(function () {
cy.mockTradingPage(marketState);
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/markets/market-0');
});
it('must display that market is not accepting orders', function () {
cy.getByTestId('deal-ticket-error-message-summary').should(
'have.text',
`This market is ${marketState
.split('_')
.pop()
?.toLowerCase()} and not accepting orders`
);
// 7002-SORD-060
cy.getByTestId('place-order').should('be.enabled');
});
});
});
}
);
@@ -0,0 +1,134 @@
import {
orderPriceField,
orderSizeField,
orderTIFDropDown,
placeOrderBtn,
toggleLimit,
toggleMarket,
} from '../support/deal-ticket';
describe('deal ticker order validation', { tags: '@smoke' }, () => {
before(() => {
cy.setVegaWallet();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.get('[data-testid="deal-ticket-form"]').then(($form) => {
if (!$form.length) {
cy.getByTestId('Order').click();
}
});
});
beforeEach(() => {
cy.mockTradingPage();
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
describe('limit order', () => {
before(() => {
cy.getByTestId(toggleLimit).click();
});
it('must see the price unit', function () {
// 7002-SORD-018
cy.getByTestId(orderPriceField).next().should('have.text', 'DAI');
});
it('must see warning when placing an order with expiry date in past', () => {
const expiresAt = new Date(Date.now() - 24 * 60 * 60 * 1000);
const expiresAtInputValue = expiresAt.toISOString().substring(0, 16);
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTT');
cy.log('choosing yesterday');
cy.getByTestId('date-picker-field').type(expiresAtInputValue);
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-expiry').should(
'have.text',
'The expiry date that you have entered appears to be in the past'
);
});
it('must see warning if price has too many digits after decimal place', function () {
// 7002-SORD-059
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-price').should(
'have.text',
'Price accepts up to 5 decimal places'
);
});
});
describe('market order', () => {
before(() => {
cy.getByTestId(toggleMarket).click();
cy.getByTestId(placeOrderBtn).click();
});
it('must not see the price unit', function () {
// 7002-SORD-019
cy.getByTestId(orderPriceField).should('not.exist');
});
it('must warn if order size input has too many digits after the decimal place', function () {
// 7002-SORD-016
cy.getByTestId(orderSizeField).clear().type('1.234');
// 7002-SORD-060
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId('deal-ticket-error-message-size').should(
'have.text',
'Size must be whole numbers for this market'
);
});
it('must warn if order size is set to 0', function () {
cy.getByTestId(orderSizeField).clear().type('0');
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId('deal-ticket-error-message-size').should(
'have.text',
'Size cannot be lower than 1'
);
});
it('must have total margin available', () => {
// 7001-COLL-011
cy.getByTestId('deal-ticket-fee-total-margin-available').within(() => {
cy.get('[data-state="closed"]').should(
'have.text',
'Total margin available100.01 tDAI'
);
});
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
it('must have current margin allocation', () => {
cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => {
cy.get('[data-state="closed"]:first').should(
'have.text',
'Current margin allocation'
);
});
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
it('should open usage breakdown dialog when clicked on current margin allocation', () => {
cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => {
cy.get('button').click();
});
cy.getByTestId('usage-breakdown').should('exist');
cy.getByTestId('dialog-close').click();
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
});
});
@@ -9,7 +9,7 @@ import {
testOrderAmendment,
} from '../support/order-validation';
const orderSymbol = 'instrument-code';
const orderSymbol = 'market.tradableInstrument.instrument.code';
const orderSize = 'size';
const orderType = 'type';
const orderStatus = 'status';
@@ -229,7 +229,10 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
status: Schema.OrderStatus.STATUS_FILLED,
});
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
cy.get(`[col-id="${orderSymbol}"]`).contains('[title="Future"]', 'Futr');
cy.get('[col-id="market.tradableInstrument.instrument.code"]').contains(
'[title="Future"]',
'Futr'
);
});
it('must see a rejected order', () => {
-2
View File
@@ -12,7 +12,6 @@ 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_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
@@ -25,7 +24,6 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
-1
View File
@@ -24,7 +24,6 @@ NX_STOP_ORDERS=false
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
NX_REFERRALS=false
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
-2
View File
@@ -15,7 +15,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
@@ -23,7 +22,6 @@ NX_STOP_ORDERS=true
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
+4 -2
View File
@@ -16,7 +16,10 @@ NX_VEGA_CONSOLE_URL=https://console.vega.xyz
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.21.2-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
@@ -24,7 +27,6 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=false
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
-1
View File
@@ -24,7 +24,6 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
NX_REFERRALS=false
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
+1 -2
View File
@@ -16,7 +16,7 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
@@ -24,4 +24,3 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
-2
View File
@@ -17,7 +17,6 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
@@ -25,7 +24,6 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_REFERRALS=true
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
-1
View File
@@ -25,7 +25,6 @@ NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=false
NX_REFERRALS=false
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
@@ -1,5 +0,0 @@
import MarketPage from '../market';
export const ClosedMarketPage = () => {
return <MarketPage closed />;
};
@@ -1 +0,0 @@
export { ClosedMarketPage as default } from './closed-market';
@@ -126,20 +126,32 @@ export const FundingRate = ({ marketId }: { marketId: string }) => {
);
};
const useNow = () => {
const padStart = (n: number) => n.toString().padStart(2, '0');
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
const { data: fundingPeriods } = useFundingPeriodsQuery({
variables: {
marketId: marketId,
pagination: { first: 1 },
},
});
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
});
const [now, setNow] = useState(Date.now());
useEffect(() => {
const interval = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(interval);
}, []);
return now;
};
const useEvery = (marketId: string) => {
const { data: marketInfo } = useDataProvider({
dataProvider: marketInfoProvider,
variables: { marketId },
});
const node = fundingPeriods?.fundingPeriods.edges?.[0]?.node;
let startTime: number | undefined = undefined;
if (node && node.startTime && !node.endTime) {
startTime = fromNanoSeconds(node.startTime).getTime();
}
let diffFormatted = t('Unknown');
let every: number | undefined = undefined;
const sourceType =
marketInfo &&
@@ -153,51 +165,14 @@ const useEvery = (marketId: string) => {
every *= 1000;
}
}
return every;
};
const useStartTime = (marketId: string) => {
const { data: fundingPeriods } = useFundingPeriodsQuery({
variables: {
marketId: marketId,
pagination: { first: 1 },
},
});
const node = fundingPeriods?.fundingPeriods.edges?.[0]?.node;
let startTime: number | undefined = undefined;
if (node && node.startTime && !node.endTime) {
startTime = fromNanoSeconds(node.startTime).getTime();
}
return startTime;
};
const padStart = (n: number) => n.toString().padStart(2, '0');
const useFormatCountdown = (
now: number,
startTime?: number,
every?: number
) => {
if (startTime && every) {
const diff = every - ((now - startTime) % every);
const hours = (diff / 3.6e6) | 0;
const mins = ((diff % 3.6e6) / 6e4) | 0;
const secs = Math.round((diff % 6e4) / 1e3);
return `${padStart(hours)}:${padStart(mins)}:${padStart(secs)}`;
diffFormatted = `${padStart(hours)}:${padStart(mins)}:${padStart(secs)}`;
}
return t('Unknown');
};
export const FundingCountdown = ({ marketId }: { marketId: string }) => {
const now = useNow();
const startTime = useStartTime(marketId);
const every = useEvery(marketId);
return (
<div data-testid="funding-countdown">
{useFormatCountdown(now, startTime, every)}
</div>
);
return <div data-testid="funding-countdown">{diffFormatted}</div>;
};
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
+6 -24
View File
@@ -9,10 +9,9 @@ import { useGlobalStore, usePageTitleStore } from '../../stores';
import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../lib/links';
import { Links } from '../../lib/links';
import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { MarketState } from '@vegaprotocol/types';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
return markPrice && decimalPlaces
@@ -57,7 +56,7 @@ const TitleUpdater = ({
return null;
};
export const MarketPage = ({ closed }: { closed?: boolean }) => {
export const MarketPage = () => {
const { marketId } = useParams();
const navigate = useNavigate();
const currentRouteId = useGetCurrentRouteId();
@@ -71,33 +70,16 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
const { data, error, loading } = useMarket(marketId);
useEffect(() => {
if (
data?.state &&
[
MarketState.STATE_SETTLED,
MarketState.STATE_TRADING_TERMINATED,
].includes(data.state) &&
currentRouteId !== Routes.CLOSED_MARKETS &&
marketId
) {
navigate(Links.CLOSED_MARKETS(marketId));
}
}, [data?.state, currentRouteId, navigate, marketId]);
useEffect(() => {
if (data?.id && data.id !== lastMarketId && !closed) {
if (data?.id && data.id !== lastMarketId) {
update({ marketId: data.id });
}
}, [update, lastMarketId, data?.id, closed]);
}, [update, lastMarketId, data?.id]);
useEffect(() => {
if (largeScreen && view === undefined) {
setViews(
{ type: closed ? ViewType.Info : ViewType.Order },
currentRouteId
);
setViews({ type: ViewType.Order }, currentRouteId);
}
}, [setViews, view, currentRouteId, largeScreen, closed]);
}, [setViews, view, currentRouteId, largeScreen]);
const pinnedAsset = data && getAsset(data);
@@ -136,7 +136,7 @@ const MainGrid = memo(
</Tab>
) : null}
<Tab id="fills" name={t('Fills')}>
<TradingViews.fills.component />
<TradingViews.fills.component marketId={marketId} />
</Tab>
<Tab
id="accounts"
@@ -1,4 +1,4 @@
import { act, render, screen, waitFor, within } from '@testing-library/react';
import { act, render, screen, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { Closed } from './closed';
import { MarketStateMapping, PropertyKeyType } from '@vegaprotocol/types';
@@ -300,11 +300,9 @@ describe('Closed', () => {
].includes(m.node.state);
});
await waitFor(() => {
// check rows length is correct
const rows = container.getAllByRole('row');
expect(rows).toHaveLength(expectedRows.length);
});
// check rows length is correct
const rows = container.getAllByRole('row');
expect(rows).toHaveLength(expectedRows.length);
// check that only included ids are shown
const cells = screen
+1 -25
View File
@@ -4,7 +4,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n';
import type { Asset } from '@vegaprotocol/types';
@@ -22,8 +22,6 @@ import { SettlementPriceCell } from './settlement-price-cell';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { MarketCodeCell } from './market-code-cell';
import { MarketActionsDropdown } from './market-table-actions';
import type { CellClickedEvent } from 'ag-grid-community';
import { useClosedMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
type SettlementAsset = Pick<
Asset,
@@ -127,7 +125,6 @@ const ClosedMarketsDataGrid = ({
rowData: Row[];
error: Error | undefined;
}) => {
const handleOnSelect = useClosedMarketClickHandler();
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const colDefs = useMemo(() => {
@@ -284,27 +281,6 @@ const ClosedMarketsDataGrid = ({
overlayNoRowsTemplate={error ? error.message : t('No markets')}
components={components}
rowHeight={45}
onCellClicked={({ data, column, event }: CellClickedEvent<Row>) => {
if (!data) return;
// prevent navigating to the market page if any of the below cells are clicked
// event.preventDefault or event.stopPropagation dont seem to apply for aggird
const colId = column.getColId();
if (
[
'settlementDate',
'settlementDataOracleId',
'settlementAsset',
'market-actions',
].includes(colId)
) {
return;
}
// @ts-ignore metaKey exists
handleOnSelect(data.id, event ? event.metaKey : false);
}}
/>
);
};
@@ -1,5 +1,5 @@
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
import { useColumnDefs } from './use-column-defs';
@@ -8,7 +8,6 @@ export const getRowId = ({ data }: { data: { id: string } }) => data.id;
const defaultColDef = {
sortable: true,
filter: true,
resizable: true,
filterParams: { buttons: ['reset'] },
};
@@ -28,6 +28,7 @@ export const useColumnDefs = () => {
{
headerName: t('Market'),
field: 'tradableInstrument.instrument.code',
flex: 2,
cellRenderer: ({
value,
data,
@@ -49,6 +50,7 @@ export const useColumnDefs = () => {
{
headerName: t('Description'),
field: 'tradableInstrument.instrument.name',
flex: 2,
},
{
headerName: t('Trading mode'),
@@ -1,172 +0,0 @@
import {
Input,
InputError,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type { FieldValues } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import classNames from 'classnames';
import { Navigate, useSearchParams } from 'react-router-dom';
import { useEffect, useRef, useState } from 'react';
import { Button } from './buttons';
import {
useTransactionEventSubscription,
useVegaWallet,
} from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { Routes } from '../../lib/links';
export const ApplyCodeForm = () => {
const [status, setStatus] = useState<
'requested' | 'failed' | 'successful' | null
>(null);
const txHash = useRef<string | null>(null);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const {
register,
handleSubmit,
formState: { errors },
setValue,
setError,
} = useForm();
const [params] = useSearchParams();
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
useEffect(() => {
const code = params.get('code');
if (code) setValue('code', code);
}, [params, setValue]);
const onSubmit = ({ code }: FieldValues) => {
if (isReadOnly || !pubKey || !code || code.length === 0) {
return;
}
setStatus('requested');
sendTx(pubKey, {
applyReferralCode: {
id: code as string,
},
})
.then((res) => {
if (!res) {
setError('code', {
type: 'required',
message: 'The transaction could not be sent',
});
}
if (res) {
txHash.current = res.transactionHash.toLowerCase();
}
})
.catch((err) => {
if (err.message.includes('user rejected')) {
setStatus(null);
} else {
setError('code', {
type: 'required',
message: 'Your code has been rejected',
});
}
});
};
useTransactionEventSubscription({
variables: { partyId: pubKey || '' },
skip: !pubKey,
fetchPolicy: 'no-cache',
onData: ({ data: result }) =>
result.data?.busEvents?.forEach((event) => {
if (event.event.__typename === 'TransactionResult') {
const hash = event.event.hash.toLowerCase();
if (txHash.current && txHash.current === hash) {
const err = event.event.error;
const status = event.event.status;
if (err) {
setStatus(null);
setError('code', {
type: 'required',
message: err,
});
}
if (status && !err) {
setStatus('successful');
}
}
}
}),
});
if (referee || referrer) {
return <Navigate to={Routes.REFERRALS} />;
}
if (status === 'successful') {
return (
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt flex flex-row gap-2 justify-center items-center">
<span className="text-vega-green-500">
<VegaIcon name={VegaIconNames.TICK} size={20} />
</span>{' '}
<span className="pt-1">Code applied</span>
</h3>
</div>
);
}
const getButtonProps = () => {
if (isReadOnly || !pubKey) {
return {
disabled: true,
children: 'Apply',
};
}
if (status === 'requested') {
return {
disabled: true,
children: 'Confirm in wallet...',
};
}
return {
disabled: false,
children: 'Apply',
};
};
return (
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Apply a referral code
</h3>
<p className="mb-6 text-center">Enter a referral code</p>
<form
className={classNames('w-full flex flex-col gap-3', {
'animate-shake': Boolean(errors.code),
})}
onSubmit={handleSubmit(onSubmit)}
>
<label className="flex-grow">
<span className="block mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Your referral code
</span>
<Input
hasError={Boolean(errors.code)}
{...register('code', {
required: 'You have to provide a code to apply it.',
})}
/>
</label>
<Button className="w-full" type="submit" {...getButtonProps()} />
</form>
{errors.code && (
<InputError>{errors.code.message?.toString()}</InputError>
)}
</div>
);
};
@@ -1,111 +0,0 @@
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ComponentProps, ButtonHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { NavLink } from 'react-router-dom';
type RainbowButtonProps = {
variant?: 'full' | 'border';
};
export const RainbowButton = ({
variant = 'full',
children,
className,
...props
}: RainbowButtonProps & ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
className={classNames(
'bg-rainbow hover:bg-none hover:bg-rainbow enabled:hover:bg-vega-pink-500 rounded-lg overflow-hidden disabled:opacity-40',
{
'px-5 py-3 text-white': variant === 'full',
'p-[0.125rem]': variant === 'border',
},
className
)}
{...props}
>
<div
className={classNames({
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white px-5 py-3 rounded-[0.35rem] overflow-hidden':
variant === 'border',
})}
>
{children}
</div>
</button>
);
const RAINBOW_TAB_STYLE = classNames(
'inline-block',
'bg-vega-clight-500 dark:bg-vega-cdark-500',
'hover:bg-vega-clight-400 dark:hover:bg-vega-cdark-400',
'data-[state="active"]:text-white data-[state="active"]:bg-rainbow',
'data-[state="active"]:hover:bg-none data-[state="active"]:hover:bg-vega-pink-500 dark:data-[state="active"]:hover:bg-vega-pink-500',
'[&.active]:text-white [&.active]:bg-rainbow',
'[&.active]:hover:bg-none [&.active]:hover:bg-vega-pink-500 dark:[&.active]:hover:bg-vega-pink-500',
'px-5 py-3',
'first:rounded-tl-lg last:rounded-tr-lg'
);
const DISABLED_RAINBOW_TAB_STYLE = classNames(
'pointer-events-none',
'text-vega-clight-100 dark:text-vega-cdark-100',
'data-[state="active"]:text-white',
'[&.active]:text-white'
);
export const RainbowTabButton = forwardRef<
HTMLButtonElement,
{ disabled?: boolean } & ButtonHTMLAttributes<HTMLButtonElement>
>(({ children, className, disabled = false, ...props }, ref) => (
<button
ref={ref}
className={classNames(
RAINBOW_TAB_STYLE,
{ 'pointer-events-none': disabled },
className
)}
{...props}
>
{children}
</button>
));
RainbowTabButton.displayName = 'RainbowTabButton';
export const RainbowTabLink = ({
to,
children,
className,
disabled = false,
...props
}: { disabled?: boolean } & ComponentProps<typeof NavLink>) => (
<NavLink
to={to}
className={classNames(
RAINBOW_TAB_STYLE,
disabled && DISABLED_RAINBOW_TAB_STYLE,
typeof className === 'string' ? className : undefined
)}
{...props}
>
{children}
</NavLink>
);
export const Button = forwardRef<
HTMLButtonElement,
ComponentProps<typeof TradingButton>
>(({ children, intent, type, ...props }, ref) => {
return (
<TradingButton
ref={ref}
intent={intent || type === 'submit' ? Intent.Primary : Intent.None}
type={type}
{...props}
>
{children}
</TradingButton>
);
});
Button.displayName = 'TradingButton';
@@ -1,6 +0,0 @@
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local';
@@ -1,225 +0,0 @@
import {
useVegaWallet,
useVegaWalletDialogStore,
determineId,
} from '@vegaprotocol/wallet';
import { RainbowButton } from './buttons';
import { useState } from 'react';
import {
CopyWithTooltip,
Dialog,
ExternalLink,
InputError,
Intent,
TradingAnchorButton,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
import { useStakeAvailable } from './hooks/use-stake-available';
export const CreateCodeContainer = () => {
const { stakeAvailable, requiredStake } = useStakeAvailable();
if (stakeAvailable == null || requiredStake == null) {
return null;
}
return (
<CreateCodeForm
currentStakeAvailable={stakeAvailable}
requiredStake={requiredStake}
/>
);
};
export const CreateCodeForm = ({
currentStakeAvailable,
requiredStake,
}: {
currentStakeAvailable: bigint;
requiredStake: bigint;
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey } = useVegaWallet();
return (
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Create a referral code
</h3>
<p className="mb-6 text-center">
Generate a referral code to share with your friends and start earning
commission.
</p>
<div className="mb-5">
<div className="text-center">
<RainbowButton
variant="border"
onClick={() => {
if (pubKey) {
setDialogOpen(true);
} else {
openWalletDialog();
}
}}
>
{pubKey ? 'Create a referral code' : 'Connect wallet'}
</RainbowButton>
</div>
</div>
<Dialog
title="Create a referral code"
open={dialogOpen}
onChange={() => setDialogOpen(false)}
size="small"
>
<CreateCodeDialog
currentStakeAvailable={currentStakeAvailable}
setDialogOpen={setDialogOpen}
requiredStake={requiredStake}
/>
</Dialog>
</div>
);
};
const CreateCodeDialog = ({
setDialogOpen,
currentStakeAvailable,
requiredStake,
}: {
setDialogOpen: (open: boolean) => void;
currentStakeAvailable: bigint;
requiredStake: bigint;
}) => {
const createLink = useLinks(DApp.Governance);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const [err, setErr] = useState<string | null>(null);
const [code, setCode] = useState<string | null>(null);
const [status, setStatus] = useState<
'idle' | 'loading' | 'success' | 'error'
>('idle');
const onSubmit = () => {
if (isReadOnly || !pubKey) {
setErr('Not connected');
} else {
setErr(null);
setStatus('loading');
setCode(null);
sendTx(pubKey, {
createReferralSet: {
isTeam: false,
},
})
.then((res) => {
if (!res) {
setErr(`Invalid response: ${JSON.stringify(res)}`);
return;
}
const code = determineId(res.signature);
setCode(code);
setStatus('success');
})
.catch((err) => {
if (err.message.includes('user rejected')) {
setStatus('idle');
return;
}
setStatus('error');
setErr(err.message);
});
}
};
const getButtonProps = () => {
if (status === 'idle' || status === 'error') {
return {
children: 'Generate code',
onClick: () => onSubmit(),
};
}
if (status === 'loading') {
return {
children: 'Confirm in wallet...',
disabled: true,
};
}
if (status === 'success') {
return {
children: 'Close',
intent: Intent.Success,
onClick: () => setDialogOpen(false),
};
}
};
// TODO: Add when network parameters are updated
if (
currentStakeAvailable === BigInt(0) ||
currentStakeAvailable < requiredStake
) {
return (
<div className="flex flex-col gap-4">
<p>
You need at least{' '}
{addDecimalsFormatNumber(requiredStake.toString(), 18)} VEGA staked to
generate a referral code and participate in the referral program.
</p>
<TradingAnchorButton
href={createLink(TokenStaticLinks.ASSOCIATE)}
intent={Intent.Primary}
target="_blank"
>
Stake some $VEGA now
</TradingAnchorButton>
</div>
);
}
return (
<div className="flex flex-col gap-4">
{(status === 'idle' || status === 'loading' || status === 'error') && (
<p>
Generate a referral code to share with your friends and start earning
commission.
</p>
)}
{status === 'success' && code && (
<div className="flex items-center gap-2">
<div className="flex-1 min-w-0 p-2 text-sm rounded bg-vega-clight-700 dark:bg-vega-cdark-700">
<p className="overflow-hidden whitespace-nowrap text-ellipsis">
{code}
</p>
</div>
<CopyWithTooltip text={code}>
<TradingButton
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</TradingButton>
</CopyWithTooltip>
</div>
)}
<TradingButton
fill={true}
intent={Intent.Primary}
{...getButtonProps()}
/>
{err && <InputError>{err}</InputError>}
{/* TODO: Add links */}
<div className="flex justify-center pt-5 mt-2 text-sm border-t gap-4 text-default border-default">
<ExternalLink>About the referral program</ExternalLink>
<ExternalLink>Disclaimer</ExternalLink>
</div>
</div>
);
};
@@ -1,77 +0,0 @@
import { isRouteErrorResponse, useNavigate, useRouteError } from 'react-router';
import { RainbowButton } from './buttons';
import { AnimatedDudeWithWire } from './graphics/dude';
import { LayoutWithSky } from './layout';
import { Routes } from '../../lib/links';
export const ErrorBoundary = () => {
const error = useRouteError();
const navigate = useNavigate();
const title = isRouteErrorResponse(error)
? `${error.status} ${error.statusText}`
: 'Something went wrong';
const code = isRouteErrorResponse(error) ? error.status : 0;
const messages: Record<number, string> = {
0: 'An unknown error occurred.',
404: "The page you're looking for doesn't exists.",
};
return (
<LayoutWithSky className="pt-32">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire className="animate-spin" />
</div>
<h1 className="text-6xl font-alpha calt mb-10">{title}</h1>
{Object.keys(messages).includes(code.toString()) ? (
<p className="text-lg mb-10">{messages[code]}</p>
) : null}
<p className="text-lg mb-10">
<RainbowButton
onClick={() => navigate('..')}
variant="border"
className="text-xs"
>
Go back and try again
</RainbowButton>
</p>
</LayoutWithSky>
);
};
export const NotFound = () => {
const navigate = useNavigate();
return (
<div className="pt-32">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire className="animate-spin" />
</div>
<h1 className="text-6xl font-alpha calt mb-10">{'Not found'}</h1>
<p className="text-lg mb-10">
{"The page you're looking for doesn't exists."}
</p>
<p className="text-lg mb-10">
<RainbowButton
onClick={() => navigate(Routes.REFERRALS)}
variant="border"
className="text-xs"
>
Go back and try again
</RainbowButton>
</p>
</div>
);
};
@@ -1,70 +0,0 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
export const Dude = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="41"
height="47"
viewBox="0 0 41 47"
fill="none"
className={className}
>
<path
d="M21.1895 0.298767L5.08827 27.4101L8.96133 29.7103L4.36099 37.4564L8.23404 39.7566L12.8344 32.0105L16.7074 34.3107L12.1071 42.0568L15.9801 44.3569L20.5805 36.6108L24.4535 38.911L40.5547 11.7996L21.1895 0.298767Z"
className="fill-black dark:fill-white"
/>
<path
d="M35.9346 15.1683L20.4424 5.96765L14.3086 16.2958L29.8008 25.4965L35.9346 15.1683Z"
className="fill-white dark:fill-black"
/>
<path
d="M25.646 17.7895L23.064 16.2561L21.5305 18.8381L24.1126 20.3716L25.646 17.7895Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.7612 16.7412L27.1792 15.2077L25.6458 17.7898L28.2278 19.3232L29.7612 16.7412Z"
className="fill-black dark:fill-white"
/>
<path
d="M33.877 15.6925L31.2949 14.159L29.7615 16.7411L32.3435 18.2745L33.877 15.6925Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.0342 26.7874L26.4521 25.2539L24.9187 27.836L27.5007 29.3694L29.0342 26.7874Z"
fill="#FF077F"
/>
</svg>
);
};
export const Wire = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="157"
height="88"
viewBox="0 0 157 88"
fill="none"
className={className}
>
<path
d="M109.398 6.12235C127.37 -3.81898 146.791 1.45045 153.465 14.307C160.138 27.1636 154.195 43.9948 140.438 52.1164C126.68 60.238 105.767 54.9998 84.9212 43.464C64.0752 31.9281 32.2412 6.42016 18.8175 24.185C6.90871 40.719 41.9332 68.4495 29.2664 82.7049C23.187 88.4974 11.1379 88.2645 0.968295 80.3398"
className="stroke-black dark:stroke-white"
strokeWidth="1.5"
strokeMiterlimit="10"
/>
</svg>
);
};
export const AnimatedDudeWithWire = ({ className }: { className?: string }) => (
<div className="relative">
<Wire className="absolute top-[25px]" />
<Dude
className={classNames(
'absolute left-[96px] animate-[wave_20s_ease-in-out_infinite]',
className
)}
/>
</div>
);
@@ -1,133 +0,0 @@
import { gql, useQuery } from '@apollo/client';
import { getNumberFormat } from '@vegaprotocol/utils';
import { addDays } from 'date-fns';
import sortBy from 'lodash/sortBy';
import omit from 'lodash/omit';
// TODO: Generate query
// eslint-disable-next-line
const REFERRAL_PROGRAM_QUERY = gql`
query ReferralProgram {
currentReferralProgram {
id
version
endOfProgramTimestamp
windowLength
endedAt
benefitTiers {
minimumEpochs
minimumRunningNotionalTakerVolume
referralDiscountFactor
referralRewardFactor
}
stakingTiers {
minimumStakedTokens
referralRewardMultiplier
}
}
}
`;
const STAKING_TIERS_MAPPING: Record<number, string> = {
1: 'Tradestarter',
2: 'Mid level degen',
3: 'Reward hoarder',
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MOCK = {
data: {
currentReferralProgram: {
id: 'abc',
version: 1,
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
windowLength: 10,
benefitTiers: [
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '30000',
referralDiscountFactor: '0.01',
referralRewardFactor: '0.01',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '20000',
referralDiscountFactor: '0.05',
referralRewardFactor: '0.05',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '10000',
referralDiscountFactor: '0.001',
referralRewardFactor: '0.001',
},
],
stakingTiers: [
{
minimumStakedTokens: '10000',
referralRewardMultiplier: '1',
},
{
minimumStakedTokens: '20000',
referralRewardMultiplier: '2',
},
{
minimumStakedTokens: '30000',
referralRewardMultiplier: '3',
},
],
},
},
loading: false,
error: undefined,
};
export const useReferralProgram = () => {
const { data, loading, error } = useQuery(REFERRAL_PROGRAM_QUERY, {
fetchPolicy: 'cache-and-network',
});
if (!data) {
return {
benefitTiers: [],
stakingTiers: [],
details: undefined,
loading,
error,
};
}
const benefitTiers = sortBy(data.currentReferralProgram.benefitTiers, (t) =>
Number(t.referralRewardFactor)
)
.reverse()
.map((t, i) => {
return {
tier: i + 1,
commission: Number(t.referralRewardFactor) * 100 + '%',
discount: Number(t.referralDiscountFactor) * 100 + '%',
volume: getNumberFormat(0).format(
Number(t.minimumRunningNotionalTakerVolume)
),
};
});
const stakingTiers = sortBy(
data.currentReferralProgram.stakingTiers,
(t) => t.referralRewardMultiplier
).map((t, i) => {
return {
tier: i + 1,
label: STAKING_TIERS_MAPPING[i + 1],
...t,
};
});
return {
benefitTiers,
stakingTiers,
details: omit(data.currentReferralProgram, 'benefitTiers', 'stakingTiers'),
loading,
error,
};
};
@@ -1,114 +0,0 @@
import { gql, useQuery } from '@apollo/client';
import { removePaginationWrapper } from '@vegaprotocol/utils';
const REFERRER_QUERY = gql`
query ReferralSets($partyId: ID!) {
referralSets(referrer: $partyId) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
const REFEREE_QUERY = gql`
query ReferralSets($partyId: ID!) {
referralSets(referee: $partyId) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
const REFEREES_QUERY = gql`
query ReferralSets($code: ID!) {
referralSetReferees(id: $code) {
edges {
node {
referralSetId
refereeId
joinedAt
atEpoch
}
}
}
}
`;
// TODO: generate types after perps work is merged
export type ReferralData = {
code: string;
referees: Array<{
refereeId: string;
joinedAt: string;
atEpoch: number;
}>;
};
export const useReferral = (
pubKey: string | null,
role: 'referrer' | 'referee'
) => {
const query = {
referrer: REFERRER_QUERY,
referee: REFEREE_QUERY,
};
const {
data: referralData,
loading: referralLoading,
error: referralError,
} = useQuery(query[role], {
variables: {
partyId: pubKey,
},
skip: !pubKey,
fetchPolicy: 'cache-and-network',
});
// A user can only have 1 active referral program at a time
const referral = referralData?.referralSets.edges.length
? referralData.referralSets.edges[0].node
: undefined;
const {
data: refereesData,
loading: refereesLoading,
error: refereesError,
} = useQuery(REFEREES_QUERY, {
variables: {
code: referral?.id,
},
skip: !referral?.id,
fetchPolicy: 'cache-and-network',
});
const referees = removePaginationWrapper(
refereesData?.referralSetReferees.edges
);
const data =
referral && refereesData
? {
code: referral.id,
referees,
}
: undefined;
return {
data: data as ReferralData | undefined,
loading: referralLoading || refereesLoading,
error: referralError || refereesError,
};
};
@@ -1,34 +0,0 @@
import { gql, useQuery } from '@apollo/client';
import { useVegaWallet } from '@vegaprotocol/wallet';
const STAKE_QUERY = gql`
query CreateCode($partyId: ID!) {
party(id: $partyId) {
stakingSummary {
currentStakeAvailable
}
}
networkParameter(key: "referralProgram.minStakedVegaTokens") {
value
}
}
`;
export const useStakeAvailable = () => {
const { pubKey } = useVegaWallet();
const { data } = useQuery(STAKE_QUERY, {
variables: { partyId: pubKey || '' },
skip: !pubKey,
// TODO: remove when network params available
errorPolicy: 'ignore',
});
return {
stakeAvailable: data
? BigInt(data.party?.stakingSummary.currentStakeAvailable || '0')
: undefined,
requiredStake: data
? BigInt(data.networkParameter?.value || '0')
: undefined,
};
};
@@ -1,52 +0,0 @@
import { Table } from './table';
export const HowItWorksTable = () => (
<Table
className="bg-none bg-vega-clight-800 dark:bg-vega-cdark-800"
noHeader
noCollapse
columns={[{ name: 'number', className: 'pr-0' }, { name: 'step' }]}
data={[
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
1
</span>
),
step: 'Referrers generate a code assigned to their key via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
2
</span>
),
step: 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
3
</span>
),
step: 'Discounts are applied automatically during trading based on the key(s) used',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
4
</span>
),
step: 'Referrers earn commission based on a percentage of the taker fees their referees pay',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
5
</span>
),
step: 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee',
},
]}
></Table>
);
@@ -1,31 +0,0 @@
import classNames from 'classnames';
import { AnimatedDudeWithWire } from './graphics/dude';
export const LandingBanner = () => {
return (
<div className={classNames('relative mb-20')}>
<div className="">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire />
</div>
<div className="pt-32 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
Earn commission & stake rewards
</h1>
<p className="text-lg mb-10">
Invite friends and earn commission in the form of Vega rewards from
the trading fees they pay. Stake those rewards to earn multipliers
on future rewards.
</p>
<p className="text-lg">
Any friends that join using the code will receive discounts off
trading fees.
</p>
</div>
</div>
</div>
);
};
@@ -1,35 +0,0 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { SKY_BACKGROUND } from './constants';
import { Outlet } from 'react-router-dom';
export const Layout = ({
className,
children,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
'max-w-[1440px]',
'mx-auto px-16 md:px-32 pb-32',
'relative z-0',
className
)}
{...props}
>
{children || <Outlet />}
</div>
);
};
export const LayoutWithSky = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div className={classNames('h-full overflow-auto', SKY_BACKGROUND)}>
<Layout className={className} {...props} />
</div>
);
};
@@ -1,113 +0,0 @@
import { Tile } from './tile';
import {
CopyWithTooltip,
Input,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Button, RainbowButton } from './buttons';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import type { ReferralData } from './hooks/use-referral';
import { useReferral } from './hooks/use-referral';
import { CreateCodeContainer } from './create-code-form';
import classNames from 'classnames';
const CodeTile = ({
code,
as,
}: {
code: string;
as: 'referrer' | 'referee';
}) => {
return (
<Tile variant="rainbow">
<h3 className="mb-1 text-lg calt">Your referral code</h3>
{as === 'referrer' && (
<p className="mb-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Share this code with friends
</p>
)}
<div className="flex gap-2">
<Input size={1} readOnly value={code} />
<CopyWithTooltip text={code}>
<Button
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</Button>
</CopyWithTooltip>
</div>
</Tile>
);
};
export const ReferralStatistics = () => {
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey } = useVegaWallet();
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
if (!pubKey) {
return (
<div className="text-center">
<RainbowButton variant="border" onClick={() => openWalletDialog()}>
Connect wallet
</RainbowButton>
</div>
);
}
if (referee?.code) {
return <Statistics data={referee} as="referee" />;
}
if (referrer?.code) {
return <Statistics data={referrer} as="referrer" />;
}
return <CreateCodeContainer />;
};
const Statistics = ({
data,
as,
}: {
data: ReferralData;
as: 'referrer' | 'referee';
}) => {
return (
<div
className={classNames('grid grid-cols-1 grid-rows-1 gap-5 mx-auto', {
'md:w-1/2': as === 'referee',
'md:w-2/3': as === 'referrer',
})}
>
<div
className={classNames('grid grid-rows-1 gap-5', {
'grid-cols-2': as === 'referrer',
'grid-cols-1': as === 'referee',
})}
>
{as === 'referrer' && data?.referees && (
<Tile className="py-3 h-full">
<div className="absolute top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%]">
<h3 className="mb-1 text-6xl text-center">
{data.referees.length}
</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
{data.referees.length === 1
? 'Trader referred'
: 'Total traders referred'}
</p>
</div>
</Tile>
)}
<CodeTile code={data?.code} as={as} />
</div>
</div>
);
};
@@ -1,59 +0,0 @@
import {
TradingAnchorButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { HowItWorksTable } from './how-it-works-table';
import { LandingBanner } from './landing-banner';
import { TiersContainer } from './tiers';
import { RainbowTabLink } from './buttons';
import { Outlet } from 'react-router-dom';
import { Routes } from '../../lib/links';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
export const Referrals = () => {
const { pubKey } = useVegaWallet();
const { data: referee } = useReferral(pubKey, 'referee');
const { data: referrer } = useReferral(pubKey, 'referrer');
return (
<>
<LandingBanner />
<div>
<div className="flex justify-center">
<RainbowTabLink end to={Routes.REFERRALS}>
Your referrals
</RainbowTabLink>
<RainbowTabLink
disabled={Boolean(referee || referrer)}
to={Routes.REFERRALS_APPLY_CODE}
>
Apply a code
</RainbowTabLink>
</div>
<div className="py-16 border-t border-b border-vega-cdark-500">
<Outlet />
</div>
</div>
<TiersContainer />
<div className="mt-10 mb-5 text-center">
<h2 className="text-2xl">How it works</h2>
</div>
<div className="md:w-[60%] mx-auto">
<HowItWorksTable />
<div className="mt-5">
<TradingAnchorButton
className="mx-auto w-max"
href="https://docs.vega.xyz/"
target="_blank"
>
Read the terms <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</TradingAnchorButton>
</div>
</div>
</>
);
};
@@ -1,116 +0,0 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TableColumnDefinition = {
displayName?: string;
name: string;
tooltip?: string;
className?: string;
};
type TableProps = {
columns: TableColumnDefinition[];
data: Record<TableColumnDefinition['name'] | 'className', React.ReactNode>[];
noHeader?: boolean;
noCollapse?: boolean;
};
const INNER_BORDER_STYLE = `border-b ${BORDER_COLOR}`;
export const Table = ({
columns,
data,
noHeader = false,
noCollapse = false,
className,
...props
}: TableProps & HTMLAttributes<HTMLTableElement>) => {
const header = (
<thead className={classNames({ 'max-md:hidden': !noCollapse })}>
<tr>
{columns.map(({ displayName, name, tooltip }) => (
<th
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100',
INNER_BORDER_STYLE
)}
>
<span className="flex flex-row gap-2 items-center">
<span>{displayName}</span>
{tooltip ? (
<Tooltip description={tooltip}>
<button className="text-vega-clight-400 dark:text-vega-cdark-400 no-underline decoration-transparent w-[12px] h-[12px] inline-flex">
<VegaIcon size={12} name={VegaIconNames.INFO} />
</button>
</Tooltip>
) : null}
</span>
</th>
))}
</tr>
</thead>
);
return (
<table
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
BORDER_COLOR,
GRADIENT,
className
)}
{...props}
>
{!noHeader && header}
<tbody>
{data.map((d, i) => (
<tr
key={i}
className={classNames(d['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
},
INNER_BORDER_STYLE,
{
'border-none': i === data.length - 1 && noCollapse,
'md:border-none': i === data.length - 1,
'max-md:border-none':
i === data.length - 1 && j === columns.length - 1,
},
className
)}
key={`${i}-${name}`}
>
{/** display column name in mobile view */}
{!noCollapse &&
!noHeader &&
displayName &&
displayName.length > 0 && (
<span
aria-hidden
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
>
{displayName}
</span>
)}
<span>{d[name]}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
);
};

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