Compare commits

..
511 changed files with 16564 additions and 26012 deletions
+6 -6
View File
@@ -34,7 +34,7 @@ jobs:
${{ runner.os }}-cache-node-modules-
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
if: steps.cache.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
@@ -57,7 +57,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
@@ -96,7 +96,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
@@ -128,7 +128,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: yarn
@@ -182,7 +182,7 @@ jobs:
run: |
if [[ "${{ github.base_ref }}" == "develop" ]]; then
echo "e2e-needed=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.base_ref }}" == "main" ]]; then
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
@@ -205,7 +205,7 @@ jobs:
console-e2e:
needs: [build-sources, check-e2e-needed]
name: '(CI) trading e2e python'
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: needs.check-e2e-needed.outputs.run-tests == 'true' && contains(needs.build-sources.outputs.projects, 'trading')
+20 -17
View File
@@ -10,7 +10,7 @@ on:
inputs:
console-test-branch:
type: choice
description: 'main: v0.72.14, develop: v0.73.4'
description: 'main: v0.72.14, develop: v0.73.0-preview7'
options:
- main
- develop
@@ -32,9 +32,9 @@ jobs:
# cache node modules
#----------------------------------------------
- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: '16'
cache: yarn
- name: Cache node modules
@@ -153,19 +153,25 @@ jobs:
run: |
docker load --input /tmp/console-image.tar
docker image ls -a
#----------------------------------------------
# check-out frontend-monorepo
# check-out tests repo
#----------------------------------------------
- name: Checkout frontend-monorepo
- name: Checkout console test repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha || github.sha }}
#----------------------------------------------
# get vega version
#----------------------------------------------
- name: Set VEGA_VERSION from .env
id: set_vega_version
run: echo "VEGA_VERSION=$(grep VEGA_VERSION apps/trading/e2e/.env | cut -d '=' -f2)" >> $GITHUB_ENV
repository: vegaprotocol/console-test
ref: ${{ needs.console-test-branch.outputs.console-branch }}
- name: Load console test envs
id: console-test-env
uses: falti/dotenv-action@v1.0.4
with:
path: '.env.${{ needs.console-test-branch.outputs.console-branch }}'
export-variables: true
keys-case: upper
log-variables: true
#----------------------------------------------
# ----- Setup python -----
#----------------------------------------------
@@ -188,25 +194,22 @@ jobs:
#----------------------------------------------
- name: Install dependencies
run: poetry install --no-interaction --no-root
working-directory: apps/trading/e2e
#----------------------------------------------
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
run: poetry run python -m vega_sim.tools.load_binaries --force --version ${{ env.VEGA_VERSION }}
working-directory: apps/trading/e2e
#----------------------------------------------
# install playwrightworking-directory: apps/trading/e2e
# install playwright
#----------------------------------------------
- name: install playwright
run: poetry run playwright install --with-deps chromium
working-directory: apps/trading/e2e
#----------------------------------------------
# run tests
#----------------------------------------------
- name: Run tests
run: CONSOLE_IMAGE_NAME=ci/trading:local poetry run pytest -v -s --numprocesses 4 --dist loadfile --durations=15
working-directory: apps/trading/e2e
#----------------------------------------------
# upload traces
#----------------------------------------------
+3 -3
View File
@@ -18,11 +18,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 20
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v4
uses: actions/setup-node@v2
with:
node-version-file: '.nvmrc'
node-version: 16.15.1
- name: Run Cypress tests
uses: cypress-io/github-action@v4
-8
View File
@@ -57,14 +57,6 @@ jobs:
path: './frontend-monorepo'
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: './frontend-monorepo/.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
cache-dependency-path: './frontend-monorepo/yarn.lock'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
id: cache-node-modules
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
+3 -3
View File
@@ -18,13 +18,13 @@ jobs:
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
node-version: 16
- name: Install dependencies
run: |
rm package.json
rm package.json
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
- name: Check PR title
+1 -1
View File
@@ -93,7 +93,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
-9
View File
@@ -52,12 +52,3 @@ cypress.env.json
/apps/**/cypress/reports/
/apps/**/cypress/downloads/
/apps/**/fixtures/wallet/node**
#console-test
__pycache__/
apps/trading/e2e/logs/
apps/trading/e2e/.pytest_cache/
apps/trading/e2e/traces/
.nx/cache
+1 -1
View File
@@ -1 +1 @@
20.9.0
16.20.2
-2
View File
@@ -8,5 +8,3 @@ __generated___
apps/static/src/assets/devnet-tranches.json
apps/static/src/assets/mainnet-tranches.json
apps/static/src/assets/testnet-tranches.json
/.nx/cache
-1
View File
@@ -1,4 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"singleQuote": true
}
+1 -2
View File
@@ -1,4 +1,3 @@
* @vegaprotocol/frontend
apps/ @vegaprotocol/frontend-qa
libs/ @vegaprotocol/frontend-qa
* @vegaprotocol/frontend-qa
*.graphql @vegaprotocol/core
+1 -1
View File
@@ -136,7 +136,7 @@ The [`docker`](./docker) subfolder has some docker configurations for easily set
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
```bash
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=20.9.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
```
### Computing ipfs-hash of the build
+1 -3
View File
@@ -4,9 +4,7 @@
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"cypress/unsafe-to-chain-command": 0
}
"rules": {}
}
]
}
+1 -1
View File
@@ -17,7 +17,7 @@
}
},
"lint": {
"executor": "@nx/eslint:lint",
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"]
@@ -39,6 +39,7 @@ context('Market page', { tags: '@regression' }, function () {
cy.contains('Test market 1').click();
cy.getByTestId(marketHeaders).should('have.text', 'Test market 1');
cy.validate_element_from_table('Name', 'Test market 1');
cy.validate_element_from_table('Market ID', this.createdMarketId);
cy.validate_element_from_table('Trading Mode', 'Opening auction');
cy.validate_element_from_table('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5');
@@ -0,0 +1,68 @@
context('Oracle page', { tags: '@smoke' }, () => {
describe('Verify elements on page', () => {
before('create market and navigate to oracle page', () => {
cy.createMarket();
cy.visit('/oracles');
});
it('should see oracle data', () => {
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
cy.getByTestId('oracle-details')
.should('exist')
.eq(0)
.within(() => {
cy.get('tr')
.eq(0)
.within(() => {
cy.get('th').should('have.text', 'ID');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/oracles/');
});
cy.get('tr')
.eq(1)
.within(() => {
cy.get('th').should('have.text', 'Type');
cy.get('td').should('have.text', 'External data');
});
cy.get('tr')
.eq(2)
.within(() => {
cy.get('th').should('have.text', 'Signer');
cy.getByTestId('keytype').should('have.text', 'Vega');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/parties/');
});
cy.get('tr')
.eq(3)
.within(() => {
cy.get('th').should('have.text', 'Settlement for');
cy.get('a').invoke('text').should('have.length', 64);
cy.get('a')
.should('have.attr', 'href')
.and('contain', '/markets/');
});
cy.get('tr')
.eq(4)
.within(() => {
cy.get('th').should('have.text', 'Matched data');
cy.get('td').should('have.text', '❌');
});
cy.get('details')
.eq(0)
.within(() => {
cy.contains('Filter').click();
cy.get('.language-json').should('exist');
});
cy.get('details')
.eq(1)
.within(() => {
cy.contains('JSON').click();
cy.get('.language-json').should('exist');
});
});
});
});
});
+10 -3
View File
@@ -53,7 +53,7 @@
}
},
"lint": {
"executor": "@nx/eslint:lint",
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"]
@@ -63,14 +63,21 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/explorer"],
"options": {
"jestConfig": "apps/explorer/jest.config.ts"
"jestConfig": "apps/explorer/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
},
"generate-types": {
"executor": "nx:run-commands",
"options": {
"commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.73.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.72.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
+2 -2
View File
@@ -1,6 +1,6 @@
import {
AppFailure,
NetworkLoader,
NodeFailure,
NodeGuard,
NodeSwitcherDialog,
useEnvironment,
@@ -31,7 +31,7 @@ function App() {
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
<NodeGuard
skeleton={<div>{t('Loading')}</div>}
failure={<NodeFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
>
<Suspense fallback={splashLoading}>
<RouterProvider router={router} fallbackElement={splashLoading} />
@@ -1,6 +1,5 @@
export type HashProps = {
text: string;
truncate?: boolean;
};
/**
@@ -8,16 +7,10 @@ export type HashProps = {
* are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists.
*/
const Hash = ({ text, truncate = false }: HashProps) => {
const h = truncate ? text.slice(0, 6) : text;
const Hash = ({ text }: HashProps) => {
return (
<code
title={text}
className="break-all font-mono"
style={{ wordWrap: 'break-word' }}
>
{h}
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
{text}
</code>
);
};
@@ -3,7 +3,6 @@ query ExplorerMarket($id: ID!) {
id
decimalPlaces
positionDecimalPlaces
state
tradableInstrument {
instrument {
name
@@ -23,5 +22,6 @@ query ExplorerMarket($id: ID!) {
}
}
}
state
}
}
@@ -17,7 +17,6 @@ export const ExplorerMarketDocument = gql`
id
decimalPlaces
positionDecimalPlaces
state
tradableInstrument {
instrument {
name
@@ -37,6 +36,7 @@ export const ExplorerMarketDocument = gql`
}
}
}
state
}
}
`;
@@ -63,9 +63,6 @@ describe('Market link component', () => {
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -91,6 +88,6 @@ describe('Market link component', () => {
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByTitle('123')).toBeInTheDocument();
expect(await res.findByText('123')).toBeInTheDocument();
});
});
@@ -23,7 +23,6 @@ const MarketLink = ({
}: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({
variables: { id },
fetchPolicy: 'cache-first',
});
let label = <span>{id}</span>;
@@ -1,57 +0,0 @@
import OracleLink, { getStatusString } from './oracle-link';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
describe('getStatusString', () => {
it("returns 'Unknown' for undefined status", () => {
expect(getStatusString(undefined)).toBe('Unknown');
});
it('returns the correct string for a known status', () => {
expect(getStatusString('STATUS_ACTIVE')).toBe('Active');
expect(getStatusString('STATUS_DEACTIVATED')).toBe('Deactivated');
});
});
describe('OracleLink', () => {
it('renders the truncated Oracle ID', () => {
const id = '123456789';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} />
</MemoryRouter>
</MockedProvider>
);
const idElement = screen.getByText(id.slice(0, 6));
expect(idElement).toBeInTheDocument();
expect(idElement).toHaveAttribute('title', id);
});
it('renders the Oracle status', () => {
const id = '123';
const status = 'STATUS_ACTIVE';
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} status={status} data-testid="link" />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('link')).toHaveAttribute('data-status', 'Active');
});
it('renders the Oracle data indicator', () => {
const id = '123';
const hasSeenOracleReports = true;
render(
<MockedProvider>
<MemoryRouter>
<OracleLink id={id} hasSeenOracleReports={hasSeenOracleReports} />
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('oracle-data-indicator')).toBeInTheDocument();
});
});
@@ -3,93 +3,20 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
DataSourceSpecStatus,
DataSourceSpecStatusMapping,
} from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
/**
* Returns a human-readable string for the given status, or a meaningful
* default if the status is unrecognised
* @param status string status
*/
export function getStatusString(status: string | undefined): string {
if (status && status in DataSourceSpecStatus) {
return DataSourceSpecStatusMapping[status as DataSourceSpecStatus];
}
return t('Unknown');
}
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
// The Oracle ID
id: string;
// If available, the oracle status
status?: string;
// If the oracle has corresponding data in the OracleDataConnection
hasSeenOracleReports?: boolean;
};
/**
* Given an Oracle ID, renders a data-dense link to the Oracle page. Data density is achieved by:
* - Colour coding the link based on the Oracle's status
* - Showing a small indicator if the Oracle has matched data
* - Showing a tooltip with the Oracle's status and whether it has matched data
*
* @returns
*/
export const OracleLink = ({
id,
status,
hasSeenOracleReports = false,
...props
}: OracleLinkProps) => {
const bgColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-100 hover:bg-yellow-200 border-yellow-200 dark:bg-yellow-200 dark:border-yellow-200 dark:text-gray-900 dark:border-yellow-300'
: 'bg-gray-100 hover:bg-gray-200 border-gray-200';
const indicatorColour =
status === 'STATUS_ACTIVE'
? 'bg-yellow-300 hover:bg-yellow-500 dark:bg-yellow-500'
: 'bg-gray-300 hover:bg-gray-500';
const description = (
<div>
<p>
<strong>{`Status: `}</strong>
{getStatusString(status)}
</p>
<p>
<strong>{`Matched data: `}</strong>
{hasSeenOracleReports ? (
<VegaIcon name={VegaIconNames.TICK} />
) : (
<VegaIcon name={VegaIconNames.CROSS} />
)}
</p>
</div>
);
const OracleLink = ({ id, ...props }: OracleLinkProps) => {
return (
<Tooltip description={description}>
<Link
className={`pl-2 pr-2 font-mono dark:text-black ${bgColour} rounded-sm border-solid border-2 relative`}
{...props}
to={`/${Routes.ORACLES}/${id}`}
data-status={getStatusString(status)}
>
<Hash text={id} truncate={true} />
{hasSeenOracleReports ? (
<strong
data-testid="oracle-data-indicator"
className={`absolute top-0 right-0 w-1 h-full font-thin ${indicatorColour}`}
title="Oracle has matched data"
></strong>
) : null}
</Link>
</Tooltip>
<Link
className="underline font-mono"
{...props}
to={`/${Routes.ORACLES}/${id}`}
>
<Hash text={id} />
</Link>
);
};
@@ -17,7 +17,7 @@ function renderAmendOrderDetails(
mocks: MockedResponse[]
) {
return render(
<MockedProvider mocks={mocks}>
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<AmendOrderDetails version={version} id={id} amend={amend} />
</MemoryRouter>
@@ -44,18 +44,18 @@ function renderExistingAmend(
orderByID: {
__typename: 'Order',
id: '123',
type: Schema.OrderType.TYPE_LIMIT,
type: 'GTT',
status: Schema.OrderStatus.STATUS_ACTIVE,
version: version ? version.toString() : '',
version: version,
createdAt: '123',
updatedAt: '456',
expiresAt: '789',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
price: '200',
side: Schema.Side.SIDE_BUY,
side: 'BUY',
peggedOrder: null,
remaining: '99',
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND,
rejectionReason: 'rejection',
reference: '123',
size: '100',
party: {
@@ -65,9 +65,9 @@ function renderExistingAmend(
market: {
__typename: 'Market',
id: '789',
state: Schema.MarketState.STATE_ACTIVE,
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: 0,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
name: 'test',
@@ -94,18 +94,18 @@ function renderExistingAmend(
orderByID: {
__typename: 'Order',
id: '123',
type: Schema.OrderType.TYPE_LIMIT,
type: 'GTT',
status: Schema.OrderStatus.STATUS_ACTIVE,
version: '100',
version: 100,
createdAt: '123',
updatedAt: '456',
expiresAt: '789',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
peggedOrder: null,
price: '200',
side: Schema.Side.SIDE_BUY,
side: 'BUY',
remaining: '99',
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND,
rejectionReason: 'rejection',
reference: '123',
size: '200',
party: {
@@ -114,10 +114,10 @@ function renderExistingAmend(
},
market: {
__typename: 'Market',
id: '8888',
state: Schema.MarketState.STATE_ACTIVE,
id: 'amend-to-order-latest-version',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: 0,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
name: 'amend-to-order-latest-version-test',
@@ -136,13 +136,13 @@ function renderExistingAmend(
request: {
query: ExplorerMarketDocument,
variables: {
id: '8888',
id: '789',
},
},
result: {
data: {
market: {
id: '8888',
id: '789',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
@@ -152,9 +152,6 @@ function renderExistingAmend(
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 8,
},
},
},
},
@@ -219,10 +216,12 @@ describe('Amend order details', () => {
it('Fetches latest version when version is not specified', async () => {
const amend: Amend = {
price: '123',
price: '-7879',
};
const res = renderExistingAmend('123', undefined, amend);
expect(await res.findByText('test-label')).toBeInTheDocument();
expect(
await res.findByText('amend-to-order-latest-version')
).toBeInTheDocument();
});
});
@@ -46,10 +46,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
<div className={wrapperClasses}>
<div className="mb-12 lg:mb-0">
<div className="relative block rounded-lg px-3 py-6 md:px-6 lg:-mr-7">
<h2 className="display-5 mb-4 text-3xl font-bold">
<h2 className="text-3xl font-bold mb-4 display-5">
{t('Order not found')}
</h2>
<p className="mb-12 text-gray-500">
<p className="text-gray-500 mb-12">
{t('No order created from this transaction')}
</p>
</div>
@@ -68,24 +68,24 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
<div className={wrapperClasses}>
<div className="mb-12 lg:mb-0">
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
<h2 className="display-5 mb-4 text-3xl font-bold">
<h2 className="text-3xl font-bold mb-4 display-5">
{t('Edits to ')}
{sideText[o.side]}
{t(' order')}
</h2>
<div className="mb-4 text-gray-500">
<p className="text-gray-500 mb-4">
In <MarketLink id={o.market.id} />, updated at{' '}
<Time date={o.updatedAt} />.
</div>
</p>
<div className="grid gap-x-6 md:grid-cols-4">
<div className="grid md:grid-cols-4 gap-x-6">
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
<div className="mb-12 md:mb-0">
<h2 className="text-dark mb-4 text-2xl font-bold">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('New size')}
</h2>
<h5
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
className={`text-lg font-medium text-gray-500 mb-0 capitalize ${getSideDeltaColour(
amend.sizeDelta
)}`}
>
@@ -96,10 +96,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
{amend.price && amend.price !== '0' ? (
<div className="">
<h2 className="text-dark mb-4 text-2xl font-bold">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('New price')}
</h2>
<h5 className="mb-0 text-lg font-medium text-gray-500">
<h5 className="text-lg font-medium text-gray-500 mb-0">
<PriceInMarket price={amend.price} marketId={o.market.id} />
</h5>
</div>
@@ -108,10 +108,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
{amend.peggedReference &&
amend.peggedReference !== 'PEGGED_REFERENCE_UNSPECIFIED' ? (
<div className="">
<h2 className="text-dark mb-4 text-2xl font-bold">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('New reference')}
</h2>
<h5 className="mb-0 text-lg font-medium text-gray-500">
<h5 className="text-lg font-medium text-gray-500 mb-0">
{peggedReference[amend.peggedReference]}
</h5>
</div>
@@ -119,10 +119,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
{amend.peggedOffset ? (
<div className="">
<h2 className="text-dark mb-4 text-2xl font-bold">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('New offset')}
</h2>
<h5 className="mb-0 text-lg font-medium text-gray-500">
<h5 className="text-lg font-medium text-gray-500 mb-0">
{amend.peggedOffset}
</h5>
</div>
@@ -22,9 +22,5 @@ export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
return <>{t('Date unknown')}</>;
}
return (
<span {...props} title={date} className="underline decoration-dotted">
{t(`${distanceToNow} ago`)}
</span>
);
return <span {...props}>{t(`${distanceToNow} ago`)}</span>;
};
@@ -0,0 +1,6 @@
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
@@ -0,0 +1,49 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number } | null };
export const ExplorerSettlementAssetForMarketDocument = gql`
query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
}
}
`;
/**
* __useExplorerSettlementAssetForMarketQuery__
*
* To run a query within a React component, call `useExplorerSettlementAssetForMarketQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerSettlementAssetForMarketQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerSettlementAssetForMarketQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerSettlementAssetForMarketQuery(baseOptions: Apollo.QueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export function useExplorerSettlementAssetForMarketLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>(ExplorerSettlementAssetForMarketDocument, options);
}
export type ExplorerSettlementAssetForMarketQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketQuery>;
export type ExplorerSettlementAssetForMarketLazyQueryHookResult = ReturnType<typeof useExplorerSettlementAssetForMarketLazyQuery>;
export type ExplorerSettlementAssetForMarketQueryResult = Apollo.QueryResult<ExplorerSettlementAssetForMarketQuery, ExplorerSettlementAssetForMarketQueryVariables>;
@@ -0,0 +1,131 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { Side } from '@vegaprotocol/types';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import { LiquidityProvisionDetailsRow } from './liquidity-provision-details-row';
import type { VegaSide } from './liquidity-provision-details-row';
describe('LiquidityProvisionDetails component', () => {
function renderComponent(
order: LiquidityOrder,
side: VegaSide,
normaliseProportionsTo: number,
marketId: string
) {
return render(
<MockedProvider>
<table>
<tbody data-testid="container">
<LiquidityProvisionDetailsRow
order={order}
marketId={marketId}
normaliseProportionsTo={normaliseProportionsTo}
side={side}
/>
</tbody>
</table>
</MockedProvider>
);
}
it('renders null for an order with no proportion', () => {
const mockOrder = {
offset: '1',
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders null for a null order', () => {
const res = renderComponent(
null as unknown as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
expect(res.getByTestId('container')).toBeEmptyDOMElement();
});
it('renders a row when the order is as expected', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_MID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_BUY,
100,
'123'
);
// Row test ids and keys are based on the side, reference and proportion
expect(res.getByTestId('SIDE_BUY-20-1')).toBeInTheDocument();
expect(res.getByText('+1')).toBeInTheDocument();
expect(res.getByText('Mid')).toBeInTheDocument();
expect(res.getByText('20%')).toBeInTheDocument();
});
it('normalises offsets when normaliseToProportion is not 100', () => {
const mockOrder = {
offset: '1',
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('-1')).toBeInTheDocument();
expect(res.getByText('Best Bid')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
});
it('handles a missing offset gracefully (should not happen)', () => {
const mockOrder = {
proportion: 20,
reference: PeggedReference.PEGGED_REFERENCE_BEST_BID,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
it('handles a missing reference gracefully (should not happen)', () => {
const mockOrder = {
offset: '1',
proportion: 20,
};
const res = renderComponent(
mockOrder as LiquidityOrder,
Side.SIDE_SELL,
50,
'123'
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
});
@@ -0,0 +1,73 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../../types/explorer';
import { TableRow } from '../../../../table';
import { LiquidityProvisionOffset } from './liquidity-provision-offset';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type VegaSide = components['schemas']['vegaSide'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export type LiquidityProvisionDetailsRowProps = {
order?: LiquidityProvisionOrder;
marketId?: string;
side: VegaSide;
// If this is
normaliseProportionsTo: number;
};
/**
*
* Note: offset is formatted by settlement asset on the market, assuming that is available
* Note: Due to the mix of references (MID vs BEST_X), it's not possible to correctly order
* the orders by their actual distance from a midpoint. This would require us knowing
* the best bid (now or at placement) and the mid. Getting the data for *now* would be
* misleading for LP submissions in the past. There is no API for getting <mid />
* at the time of a transaction.
*/
export function LiquidityProvisionDetailsRow({
normaliseProportionsTo,
order,
side,
marketId,
}: LiquidityProvisionDetailsRowProps) {
if (!order || !order.proportion) {
return null;
}
const proportion =
normaliseProportionsTo === 100
? order.proportion
: Math.round((order.proportion / normaliseProportionsTo) * 100);
const key = `${side}-${proportion}-${order.offset ? order.offset : ''}`;
return (
<TableRow modifier="bordered" key={key} data-testid={key}>
<td className="text-right px-2">
{order.offset && marketId ? (
<LiquidityProvisionOffset
offset={order.offset}
side={side}
marketId={marketId}
/>
) : (
'-'
)}
</td>
<td className="text-center">
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
</td>
<td className="text-center">{proportion}%</td>
</TableRow>
);
}
@@ -0,0 +1,22 @@
import { render } from '@testing-library/react';
import { LiquidityProvisionMid } from './liquidity-provision-mid';
describe('LiquidityProvisionMid component', () => {
function renderComponent() {
return render(
<table>
<tbody data-testid="container">
<LiquidityProvisionMid />
</tbody>
</table>
);
}
it('renders a basic row that spans the whole table', () => {
const res = renderComponent();
const display = res.getByTestId('mid-display');
expect(res.getByTestId('mid')).toBeInTheDocument();
expect(display).toBeInTheDocument();
expect(display).toHaveAttribute('colspan', '3');
});
});
@@ -0,0 +1,17 @@
import { TableRow } from '../../../../table';
/**
* In a LiquidityProvision table, this row is the midpoint. Above our LP orders on the
* buy side, below are LP orders on the sell side. This component simply divides them.
*
* There is no API that can give us the mid price when the order was created, and even
* if there was it isn't clear that would be appropriate for this centre row. So instead
* it's a simple divider.
*/
export function LiquidityProvisionMid() {
return (
<TableRow modifier="bordered" data-testid="mid">
<td data-testid="mid-display" colSpan={3} className="bg-white"></td>
</TableRow>
);
}
@@ -0,0 +1,67 @@
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import { ExplorerSettlementAssetForMarketDocument } from '../__generated__/Explorer-settlement-asset';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
import {
getFormattedOffset,
LiquidityProvisionOffset,
} from './liquidity-provision-offset';
const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
market: {
id: '123',
__typename: 'Market',
decimalPlaces: 5,
},
};
describe('LiquidityProvisionOffset component', () => {
function renderComponent(
offset: string,
side: VegaSide,
marketId: string,
mocks: MockedResponse[]
) {
return render(
<MockedProvider mocks={mocks}>
<LiquidityProvisionOffset
offset={offset}
side={side}
marketId={marketId}
/>
</MockedProvider>
);
}
it('renders a simple row before market data comes in', () => {
const res = renderComponent('1', 'SIDE_BUY', '123', []);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('replaces unformatted with formatted if the market data comes in', () => {
const mock = {
request: {
query: ExplorerSettlementAssetForMarketDocument,
variables: {
id: '123',
},
result: {
data: decimalsMock,
},
},
};
const res = renderComponent('1', 'SIDE_BUY', '123', [mock]);
expect(res.getByText('+1')).toBeInTheDocument();
});
it('getFormattedOffset returns the unformatted offset if there is not enough data', () => {
const res = getFormattedOffset('1', {});
expect(res).toEqual('1');
});
it('getFormattedOffset decimal formats a number if it comes in with market data', () => {
const res = getFormattedOffset('1', decimalsMock);
expect(res).toEqual('0.00001');
});
});
@@ -0,0 +1,59 @@
import { useExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import type { ExplorerSettlementAssetForMarketQuery } from '../__generated__/Explorer-settlement-asset';
import type { VegaSide } from './liquidity-provision-details-row';
export type LiquidityProvisionOffsetProps = {
side: VegaSide;
offset: string;
marketId: string;
};
/**
* Correctly formats an LP's offset according to the market settlement decimal places.
* Initially this will appear unformatted, then when the query loads in the proper formatted
* value will be displayed
*
* @see getFormattedOffset
*/
export function LiquidityProvisionOffset({
side,
offset,
marketId,
}: LiquidityProvisionOffsetProps) {
const { data } = useExplorerSettlementAssetForMarketQuery({
variables: {
id: marketId,
},
});
// getFormattedOffset handles missing results/loading states
const formattedOffset = getFormattedOffset(offset, data);
const label = side === 'SIDE_BUY' ? '+' : '-';
const className = side === 'SIDE_BUY' ? 'text-vega-green' : 'text-vega-pink';
return <span className={className}>{`${label}${formattedOffset}`}</span>;
}
/**
* Does the work of formatting the number now we have the market decimal places.
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
* this function will return the unformatted number
*
* @see LiquidityProvisionOffset
* @param data the result of a ExplorerSettlementAssetForMarketQuery
* @param offset the unformatted offset
* @returns string the offset of this lp order formatted with the settlement decimal places
*/
export function getFormattedOffset(
offset: string,
data?: ExplorerSettlementAssetForMarketQuery
) {
const decimals = data?.market?.decimalPlaces;
if (!decimals) {
return offset;
}
return addDecimalsFormatNumber(offset, decimals);
}
@@ -0,0 +1,209 @@
import { MockedProvider } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import type { LiquidityOrder } from '@vegaprotocol/types';
import { PeggedReference } from '@vegaprotocol/types';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import {
LiquidityProvisionDetails,
sumProportions,
} from './liquidity-provision-details';
function mockProportion(proportion: number): LiquidityOrder {
return {
proportion,
reference: PeggedReference.PEGGED_REFERENCE_MID,
offset: '1',
};
}
describe('sumProportions function', () => {
it('returns 0 if the side is undefined', () => {
const side: LiquidityOrder[] = undefined as unknown as LiquidityOrder[];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('returns 0 if the side is empty', () => {
const side: LiquidityOrder[] = [];
const res = sumProportions(side);
expect(res).toEqual(0);
});
it('sums 1 item correctly (under 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(10)];
const res = sumProportions(side);
expect(res).toEqual(10);
});
it('sums 2 item correctly (exactly 100%)', () => {
const side: LiquidityOrder[] = [mockProportion(50), mockProportion(50)];
const res = sumProportions(side);
expect(res).toEqual(100);
});
it('sums 3 item correctly to over 100%', () => {
const side: LiquidityOrder[] = [
mockProportion(20),
mockProportion(40),
mockProportion(50),
];
const res = sumProportions(side);
expect(res).toEqual(110);
});
});
describe('LiquidityProvisionDetails component', () => {
function renderComponent(provision: LiquiditySubmission) {
return render(
<MockedProvider>
<LiquidityProvisionDetails provision={provision} />
</MockedProvider>
);
}
it('handles an LP with no buys or sells by returning empty (should never happen)', () => {
const mock: LiquiditySubmission = {};
const res = renderComponent(mock);
expect(res.container).toBeEmptyDOMElement();
});
it('handles an LP with no sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with no buys by just rendering sells', () => {
const mock: LiquiditySubmission = {
marketId: '123',
sells: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('handles an LP with sells by just rendering buys', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
});
it('handles an LP with both sides', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
sells: [
{
offset: '4',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 50,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('Price offset')).toBeInTheDocument();
expect(res.getByText('Price reference')).toBeInTheDocument();
expect(res.getByText('Proportion')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-1')).toBeInTheDocument();
expect(res.getByTestId('SIDE_BUY-50-2')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-4')).toBeInTheDocument();
expect(res.getByTestId('SIDE_SELL-50-2')).toBeInTheDocument();
});
it('normalises proportions when they do not total 100%', () => {
const mock: LiquiditySubmission = {
marketId: '123',
buys: [
{
offset: '1',
proportion: 25,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
{
offset: '2',
proportion: 30,
reference: PeggedReference.PEGGED_REFERENCE_MID,
},
],
};
const res = renderComponent(mock);
expect(res.getByText('45%')).toBeInTheDocument();
expect(res.getByText('55%')).toBeInTheDocument();
});
});
@@ -0,0 +1,94 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import type { LiquiditySubmission } from '../tx-liquidity-submission';
import { TableRow } from '../../../table';
import { LiquidityProvisionMid } from './components/liquidity-provision-mid';
import { LiquidityProvisionDetailsRow } from './components/liquidity-provision-details-row';
import { Side } from '@vegaprotocol/types';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export type LiquidityProvisionOrder =
components['schemas']['vegaLiquidityOrder'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
/**
* Given a side of a liquidity provision order, returns the total
* It should be 100%, but it isn't always and if it isn't the proportion
* reported for each order should be scaled
*
* @returns number
*/
export function sumProportions(
side: LiquiditySubmission['buys'] | LiquiditySubmission['sells']
): number {
if (!side || side.length === 0) {
return 0;
}
return side.reduce((total, o) => total + (o.proportion || 0), 0);
}
export type LiquidityProvisionDetailsProps = {
provision: LiquiditySubmission;
};
/**
* Renders a table displaying all buys and sells in this LP. It is valid for there
* to be no buys or sells.
*
* It might seem logical to turn proportions in to values based on the total commitment
* but based on the current API structure it is awkward, and given that non-LP orders
* will change the amount that is actually deployed vs assigned to a level, we decided
* not to bother going down that route.
*/
export function LiquidityProvisionDetails({
provision,
}: LiquidityProvisionDetailsProps) {
if (!provision.buys?.length && !provision.sells?.length) {
return null;
}
// We need to do some additional calcs if these aren't both 100
const buyTotal = sumProportions(provision.buys);
const sellTotal = sumProportions(provision.sells);
return (
<table>
<thead>
<TableRow modifier="bordered">
<th className="px-2 pb-1">{t('Price offset')}</th>
<th className="px-2 pb-1">{t('Price reference')}</th>
<th className="px-2 pb-1">{t('Proportion')}</th>
</TableRow>
</thead>
<tbody>
{provision.buys?.map((b, i) => (
<LiquidityProvisionDetailsRow
order={b}
marketId={provision.marketId}
side={Side.SIDE_BUY}
key={`SIDE_BUY-${i}`}
normaliseProportionsTo={buyTotal}
/>
))}
<LiquidityProvisionMid />
{provision.sells?.map((s, i) => (
<LiquidityProvisionDetailsRow
order={s}
marketId={provision.marketId}
side={Side.SIDE_SELL}
key={`SIDE_SELL-${i}`}
normaliseProportionsTo={sellTotal}
/>
))}
</tbody>
</table>
);
}
@@ -1,20 +1,11 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
export const LiquidityReferenceLabel: Record<VegaPeggedReference, string> = {
PEGGED_REFERENCE_BEST_ASK: t('Best Ask'),
PEGGED_REFERENCE_BEST_BID: t('Best Bid'),
PEGGED_REFERENCE_MID: t('Mid'),
PEGGED_REFERENCE_UNSPECIFIED: '-',
};
export interface TxDetailsOrderProps {
offset: string;
reference: VegaPeggedReference;
@@ -33,14 +33,6 @@ const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_HOLDING: 'Holding',
ACCOUNT_TYPE_LIQUIDITY_FEES_BONUS_DISTRIBUTION: 'Bonus Distribution',
ACCOUNT_TYPE_LP_LIQUIDITY_FEES: 'LP Liquidity Fees',
ACCOUNT_TYPE_NETWORK_TREASURY: 'Network Treasury',
ACCOUNT_TYPE_VESTING_REWARDS: 'Vesting Rewards',
ACCOUNT_TYPE_VESTED_REWARDS: 'Vested Rewards',
ACCOUNT_TYPE_REWARD_AVERAGE_POSITION: 'Reward Average Position',
ACCOUNT_TYPE_REWARD_RELATIVE_RETURN: 'Reward Relative Return',
ACCOUNT_TYPE_REWARD_RETURN_VOLATILITY: 'Reward Return Volatility',
ACCOUNT_TYPE_REWARD_VALIDATOR_RANKING: 'Reward Validator Ranking',
ACCOUNT_TYPE_PENDING_FEE_REFERRAL_REWARD: 'Pending Fee Referral Reward',
};
interface TransferParticipantsProps {
@@ -1,50 +1,10 @@
import { t } from '@vegaprotocol/i18n';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableWithTbody } from '../../table';
import { defaultAbiCoder, base64 } from 'ethers/lib/utils';
import { ChainEvent } from './chain-events';
import { BigNumber } from 'ethers';
import type { AbiType } from '../../../lib/encoders/abis/abi-types';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
interface AbiOutput {
type: AbiType;
internalType: AbiType;
name: string;
}
/**
* Decodes the b64/ABIcoded result from an eth cal
* @param data
* @returns
*/
export function decodeEthCallResult(
data: BlockExplorerTransactionResult
): string {
const ethResult = data.command.chainEvent?.contractCall.result;
try {
// Decode the result string: base64 => uint8array
const data = base64.decode(ethResult);
// Parse the escaped ABI in to an object
const abi = JSON.parse(
'[{"inputs":[],"name":"latestAnswer","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"}]'
);
// Pull the expected types out of the Oracles ABI
const types: AbiType[] = abi[0].outputs.map((o: AbiOutput) => o.type);
const rawResult = defaultAbiCoder.decode(types, data);
// Finally, convert the resulting BigNumber in to a string
const res = BigNumber.from(rawResult[0]).toString();
return res;
} catch (e) {
return '-';
}
}
import { ChainEvent } from './chain-events';
interface TxDetailsChainEventProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -16,6 +16,7 @@ import { TxDetailsOrderAmend } from './tx-order-amend';
import { TxDetailsWithdrawSubmission } from './tx-withdraw-submission';
import { TxDetailsDelegate } from './tx-delegation';
import { TxDetailsUndelegate } from './tx-undelegation';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
import { TxDetailsDataSubmission } from './tx-data-submission';
@@ -27,7 +28,6 @@ import { TxDetailsStateVariable } from './tx-state-variable-proposal';
import { TxProposal } from './tx-proposal';
import { TxDetailsTransfer } from './tx-transfer';
import { TxDetailsStopOrderSubmission } from './tx-stop-order-submission';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
interface TxDetailsWrapperProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -107,7 +107,7 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
return TxDetailsWithdrawSubmission;
case 'Liquidity Provision Order':
return TxDetailsLiquiditySubmission;
case 'Amend Liquidity Provision Order':
case 'Amend LiquidityProvision Order':
return TxDetailsLiquidityAmendment;
case 'Cancel LiquidityProvision Order':
return TxDetailsLiquidityCancellation;
@@ -5,6 +5,7 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
@@ -39,32 +40,40 @@ export const TxDetailsLiquidityAmendment = ({
: '-';
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
{amendment.commitmentAmount ? (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>{t('Market')}</TableCell>
<TableCell>
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
{amendment.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<PriceInMarket
price={amendment.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
) : null}
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={amendment} />
</>
);
};
@@ -0,0 +1,86 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquiditySubmission', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionSubmission: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionSubmission is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -1,30 +1,31 @@
import { t } from '@vegaprotocol/i18n';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MarketLink } from '../../links';
import { MarketLink } from '../../links/';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import { TxDetailsShared } from './shared/tx-details-shared';
import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission'];
interface TxDetailsLiquidityAmendmentProps {
interface TxDetailsLiquiditySubmissionProps {
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
blockData: TendermintBlocksResponse | undefined;
}
/**
* An existing liquidity order is being created.
* Someone cancelled an order
*/
export const TxDetailsLiquiditySubmission = ({
txData,
pubKey,
blockData,
}: TxDetailsLiquidityAmendmentProps) => {
}: TxDetailsLiquiditySubmissionProps) => {
if (!txData || !txData.command.liquidityProvisionSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -38,38 +39,40 @@ export const TxDetailsLiquiditySubmission = ({
: '-';
return (
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
<TableRow modifier="bordered">
<TableCell>{t('Market')}</TableCell>
<TableCell>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
{submission.commitmentAmount ? (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
blockData={blockData}
/>
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>{t('Market')}</TableCell>
<TableCell>
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
<MarketLink id={marketId} />
</TableCell>
</TableRow>
) : null}
{submission.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
{submission.reference ? (
<TableRow modifier="bordered">
<TableCell>{t('Reference')}</TableCell>
<TableCell>{submission.reference}</TableCell>
</TableRow>
) : null}
</TableWithTbody>
{submission.commitmentAmount ? (
<TableRow modifier="bordered">
<TableCell>{t('Commitment amount')}</TableCell>
<TableCell>
<PriceInMarket
price={submission.commitmentAmount}
marketId={marketId}
decimalSource="SETTLEMENT_ASSET"
/>
</TableCell>
</TableRow>
) : null}
{submission.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
<LiquidityProvisionDetails provision={submission} />
</>
);
};
@@ -12,7 +12,6 @@ import {
SPECIAL_CASE_NETWORK,
SPECIAL_CASE_NETWORK_ID,
} from '../../links/party-link/party-link';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
type Transfer = components['schemas']['commandsv1Transfer'];
@@ -64,16 +63,10 @@ export const TxDetailsTransfer = ({
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
<TableRow modifier="bordered" data-testid="type">
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
</TableRow>
<TableRow modifier="bordered" data-testid="id">
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
<TableCell>
{txSignatureToDeterministicId(txData.signature.value)}
</TableCell>
</TableRow>
<TxDetailsShared
txData={txData}
pubKey={pubKey}
@@ -81,7 +74,7 @@ export const TxDetailsTransfer = ({
hideTypeRow={true}
/>
{from ? (
<TableRow modifier="bordered" data-testid="from">
<TableRow modifier="bordered">
<TableCell>{t('From')}</TableCell>
<TableCell>
<PartyLink id={from} />
@@ -89,7 +82,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.to ? (
<TableRow modifier="bordered" data-testid="to">
<TableRow modifier="bordered">
<TableCell>{t('To')}</TableCell>
<TableCell>
<PartyLink id={transfer.to} />
@@ -97,7 +90,7 @@ export const TxDetailsTransfer = ({
</TableRow>
) : null}
{transfer.asset && transfer.amount ? (
<TableRow modifier="bordered" data-testid="amount">
<TableRow modifier="bordered">
<TableCell>{t('Amount')}</TableCell>
<TableCell>
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
@@ -18,13 +18,11 @@ import { FilterLabel } from './tx-filter-label';
export type FilterOption =
| 'Amend LiquidityProvision Order'
| 'Amend Order'
| 'Apply Referral Code'
| 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order'
| 'Cancel Order'
| 'Cancel Transfer Funds'
| 'Chain Event'
| 'Create Referral Set'
| 'Delegate'
| 'Ethereum Key Rotate Submission'
| 'Issue Signatures'
@@ -42,7 +40,6 @@ export type FilterOption =
| 'Submit Order'
| 'Transfer Funds'
| 'Undelegate'
| 'Update Referral Set'
| 'Validator Heartbeat'
| 'Vote on Proposal'
| 'Withdraw';
@@ -104,30 +104,10 @@ export function getLabelForProposal(
}
} else if (proposal.terms?.updateMarket) {
return t('Proposal: Update market');
} else if (proposal.terms?.updateSpotMarket) {
return t('Proposal: Update spot');
} else if (proposal.terms?.updateMarketState) {
const type = proposal.terms.updateMarketState.changes?.updateType;
if (type === 'MARKET_STATE_UPDATE_TYPE_TERMINATE') {
return t('Proposal: Market terminate');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_SUSPEND') {
return t('Proposal: Market suspend');
} else if (type === 'MARKET_STATE_UPDATE_TYPE_RESUME') {
return t('Proposal: Market resume');
}
return t('Proposal: Market state');
} else if (proposal.terms?.updateNetworkParameter) {
return t('Proposal: Network parameter');
} else if (proposal.terms?.updateReferralProgram) {
return t('Proposal: Referral program');
} else if (proposal.terms?.updateVolumeDiscountProgram) {
return t('Proposal: Discount program');
} else if (proposal.terms?.newFreeform) {
return t('Proposal: Freeform');
} else if (proposal.terms?.newTransfer) {
return t('Proposal: Transfer');
} else if (proposal.terms?.cancelTransfer) {
return t('Proposal: Transfer cancel');
} else {
return t('Proposal');
}
@@ -1,15 +1,6 @@
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
import { getTypeLabelForTransfer } from './details/tx-transfer';
import type { components } from '../../../types/explorer';
import {
TxDetailsTransfer,
getTypeLabelForTransfer,
} from './details/tx-transfer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
type Transfer = components['schemas']['commandsv1Transfer'];
describe('TX: Transfer: getLabelForTransfer', () => {
@@ -65,70 +56,3 @@ describe('TX: Transfer: getLabelForTransfer', () => {
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
});
});
describe('TxDetailsTransfer', () => {
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
const mockTxData: Partial<BlockExplorerTransactionResult> = {
hash: 'test',
submitter:
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
command: {
nonce: '5188810881378065222',
blockHeight: '14951513',
transfer: {
fromAccountType: 'ACCOUNT_TYPE_GENERAL',
to: '78432a2808f20b18a46ccc6a917bdc4d63c2b9e7007f777bdcab5a9f462c5ba6',
toAccountType: 'ACCOUNT_TYPE_GENERAL',
asset:
'dd20590509d30d20bdbbe64dc1090c1140c7690121a9b9940bc66f62dfa2e599',
amount: '4800000000',
reference: '',
oneOff: {
deliverOn: '0',
},
},
},
signature: {
value:
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
},
};
it('renders basic transfer details', () => {
const { getByTestId } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsTransfer
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockTxData.command.submitter}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
const id = getByTestId('id');
expect(id.children[0].textContent).toEqual('Transfer ID');
expect(id.children[1].textContent).toEqual(
'51f3bab5eb2637651012507a64d497790a734248792c16e5cf36df8984074fbd'
);
const type = getByTestId('type');
expect(type.children[1].textContent).toEqual('Transfer');
const from = getByTestId('from');
expect(from.children[1].textContent).toEqual(mockTxData.submitter);
const to = getByTestId('to');
expect(to.children[1].textContent).toEqual(mockTxData.command.transfer.to);
});
});
@@ -82,32 +82,6 @@ describe('Txs infinite list item', () => {
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('renders data even with missing time', () => {
render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
block="1"
code={0}
command={{}}
/>
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time')).toHaveTextContent('-');
});
it('renders data correctly', () => {
render(
<MockedProvider>
@@ -121,7 +95,6 @@ describe('Txs infinite list item', () => {
block="1"
code={0}
command={{}}
createdAt="1970-11-01T18:07:15Z"
/>
</tbody>
</table>
@@ -132,6 +105,5 @@ describe('Txs infinite list item', () => {
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-time').textContent).toMatch(/years ago/);
});
});
@@ -9,7 +9,6 @@ import { PartyLink } from '../links';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import type { Screen } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
import { TimeAgo } from '../time-ago';
const DEFAULT_TRUNCATE_LENGTH = 7;
@@ -33,7 +32,6 @@ export const TxsInfiniteListItem = ({
type,
block,
command,
createdAt,
}: Partial<BlockExplorerTransactionResult>) => {
const { screenSize } = useScreenDimensions();
const idTruncateLength = useMemo(
@@ -87,11 +85,6 @@ export const TxsInfiniteListItem = ({
endChars={5}
/>
</td>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<td className="text-sm items-center font-mono" data-testid="tx-time">
{createdAt ? <TimeAgo date={createdAt} /> : '-'}
</td>
)}
</tr>
);
};
@@ -3,7 +3,6 @@ import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list';
import { Loader } from '@vegaprotocol/ui-toolkit';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
interface TxsInfiniteListProps {
hasMoreTxs: boolean;
@@ -20,16 +19,7 @@ interface ItemProps {
}
const Item = ({ tx }: ItemProps) => {
const {
hash,
submitter,
type,
command,
block,
code,
createdAt,
index: blockIndex,
} = tx;
const { hash, submitter, type, command, block, code, index: blockIndex } = tx;
return (
<TxsInfiniteListItem
type={type}
@@ -39,7 +29,6 @@ const Item = ({ tx }: ItemProps) => {
hash={hash}
block={block}
index={blockIndex}
createdAt={createdAt}
/>
);
};
@@ -50,7 +39,6 @@ export const TxsInfiniteList = ({
className,
hasFilters = false,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
if (!txs || txs.length === 0) {
if (!areTxsLoading) {
return (
@@ -78,9 +66,6 @@ export const TxsInfiniteList = ({
<th>{t('Type')}</th>
<th className="text-left">{t('From')}</th>
<th>{t('Block')}</th>
{['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize) && (
<th>{t('Time')}</th>
)}
</tr>
</thead>
<tbody>
@@ -43,7 +43,7 @@ export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
url.searchParams.append('first', count);
url.searchParams.append('after', params.after);
} else {
url.searchParams.append('last', count);
url.searchParams.append('first', count);
}
// Hacky fix for param as array
@@ -6,7 +6,7 @@ describe('getTxsDataUrl', () => {
count: 10,
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?last=10';
const expectedUrl = 'https://example.com/transactions?first=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
@@ -41,7 +41,7 @@ describe('getTxsDataUrl', () => {
baseUrl: 'https://example.com/transactions',
};
const expectedUrl =
'https://example.com/transactions?last=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
+1 -1
View File
@@ -133,7 +133,7 @@ export const ErrorBoundary = () => {
);
};
export const GHOST = (
const GHOST = (
<svg
width="56"
height="85"
@@ -36,50 +36,30 @@ const PERCENTAGE_PARAMS = [
'governance.proposal.updateNetParam.requiredMajority',
'governance.proposal.updateNetParam.requiredParticipation',
'governance.proposal.updateMarket.minProposerEquityLikeShare',
'governance.proposal.VolumeDiscountProgram.requiredMajority',
'governance.proposal.VolumeDiscountProgram.requiredParticipation',
'governance.proposal.referralProgram.requiredMajority',
'governance.proposal.transfer.requiredMajority',
'governance.proposal.updateAsset.requiredMajority',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.transfer.requiredParticipation',
'governance.proposal.referralProgram.requiredParticipation',
'network.validators.ersatz.rewardFactor',
'network.validators.ersatz.multipleOfTendermintValidators',
'validators.vote.required',
'referralProgram.maxReferralRewardFactor',
'referralProgram.maxReferralDiscountFactor',
'referralProgram.maxReferralRewardProportion',
].map((p) => p.toLowerCase());
];
const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens',
'validators.delegation.minAmount',
'governance.proposal.transfer.maxAmount',
'reward.staking.delegation.minimumValidatorStake',
'reward.staking.delegation.maxPayoutPerParticipant',
'reward.staking.delegation.maxPayoutPerEpoch',
'spam.protection.voting.min.tokens',
'spam.protection.proposal.min.tokens',
'governance.proposal.transfer.minVoterBalance',
'governance.proposal.freeform.minProposerBalance',
'governance.proposal.updateNetParam.minVoterBalance',
'governance.proposal.updateMarket.minVoterBalance',
'governance.proposal.asset.minVoterBalance',
'governance.proposal.updateNetParam.minProposerBalance',
'governance.proposal.freeform.minVoterBalance',
'spam.protection.proposal.min.tokens',
'governance.proposal.updateMarket.minProposerBalance',
'governance.proposal.asset.minProposerBalance',
'governance.proposal.transfer.minProposerBalance',
'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance',
'governance.proposal.referralProgram.minProposerBalance',
'governance.proposal.referralProgram.minVoterBalance',
'governance.proposal.VolumeDiscountProgram.minProposerBalance',
'governance.proposal.VolumeDiscountProgram.minVoterBalance',
].map((p) => p.toLowerCase());
];
export const renderGroupedParams = (
group: GroupedParams,
@@ -151,12 +131,12 @@ export const NetworkParameterRow = ({
<div className="pb-2">
<SyntaxHighlighter data={JSON.parse(value)} />
</div>
) : BIG_NUMBER_PARAMS.includes(key.toLowerCase()) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key.toLowerCase()) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : isNaN(Number(value)) ? (
value
) : BIG_NUMBER_PARAMS.includes(key) ? (
addDecimalsFormatNumber(Number(value), 18)
) : PERCENTAGE_PARAMS.includes(key) ? (
`${formatNumber(Number(value) * 100, 0)}%`
) : (
formatNumber(Number(value), 4)
)}
@@ -1,5 +1,5 @@
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: { first: 30 }) {
dataConnection {
edges {
node {
externalData {
@@ -45,16 +45,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
}
}
... on DataSourceDefinitionExternal {
@@ -113,23 +103,6 @@ fragment ExplorerOracleDataSource on OracleSpec {
}
}
}
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
}
}
}
@@ -139,7 +112,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
}
query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: { first: 30 }) {
oracleSpecsConnection(pagination: { first: 50 }) {
pageInfo {
hasNextPage
}
@@ -1,85 +1,22 @@
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
fragment ExplorerOracleForMarketsMarket on Market {
id
state
tradableInstrument {
instrument {
product {
... on Future {
...ExplorerOracleFuture
}
... on Perpetual {
...ExplorerOraclePerpetual
}
}
}
}
}
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
... on Perpetual {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForSettlementSchedule {
id
}
}
}
@@ -95,27 +32,4 @@ query ExplorerOracleFormMarkets {
}
}
}
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: { last: 1 }) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
}
+5 -32
View File
@@ -5,23 +5,23 @@ 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', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, 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 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', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, 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', 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 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', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, 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', 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 const ExplorerOracleDataConnectionFragmentDoc = gql`
fragment ExplorerOracleDataConnection on OracleSpec {
dataConnection(pagination: {first: 30}) {
dataConnection {
edges {
node {
externalData {
@@ -68,16 +68,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
}
}
... on DataSourceDefinitionExternal {
@@ -136,23 +126,6 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
}
}
}
... on EthCallSpec {
abi
address
requiredConfirmations
method
filters {
key {
type
name
numberDecimalPlaces
}
conditions {
value
operator
}
}
}
}
}
}
@@ -163,7 +136,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
${ExplorerOracleDataConnectionFragmentDoc}`;
export const ExplorerOracleSpecsDocument = gql`
query ExplorerOracleSpecs {
oracleSpecsConnection(pagination: {first: 30}) {
oracleSpecsConnection(pagination: {first: 50}) {
pageInfo {
hasNextPage
}
@@ -3,106 +3,33 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerOraclePerpetualFragment = { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } };
export type ExplorerOracleFutureFragment = { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } };
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleDataSourceSpecFragment = { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } };
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Spot' } } } };
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string, status: Types.DataSourceSpecStatus } } | { __typename?: 'Spot' } } } } }> } | null, oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, 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 } | { __typename?: 'EthCallSpec', address: string } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null>, triggers: Array<{ __typename?: 'InternalTimeTrigger', initial?: number | null, every?: number | null } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Perpetual', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceSpec', id: string } } | { __typename?: 'Spot' } } } } }> } | null };
export const ExplorerOracleFutureFragmentDoc = gql`
fragment ExplorerOracleFuture on Future {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForTradingTermination {
id
status
}
}
`;
export const ExplorerOraclePerpetualFragmentDoc = gql`
fragment ExplorerOraclePerpetual on Perpetual {
dataSourceSpecForSettlementData {
id
status
}
dataSourceSpecForSettlementSchedule {
id
status
}
}
`;
export const ExplorerOracleForMarketsMarketFragmentDoc = gql`
fragment ExplorerOracleForMarketsMarket on Market {
id
state
tradableInstrument {
instrument {
product {
... on Future {
...ExplorerOracleFuture
}
... on Perpetual {
...ExplorerOraclePerpetual
}
}
}
}
}
${ExplorerOracleFutureFragmentDoc}
${ExplorerOraclePerpetualFragmentDoc}`;
export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
spec {
id
status
data {
sourceType {
... on DataSourceDefinitionInternal {
sourceType {
... on DataSourceSpecConfigurationTime {
conditions {
value
operator
}
}
... on DataSourceSpecConfigurationTimeTrigger {
conditions {
value
operator
}
triggers {
initial
every
}
}
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForTradingTermination {
id
}
}
... on DataSourceDefinitionExternal {
sourceType {
... on EthCallSpec {
address
}
... on DataSourceSpecConfiguration {
signers {
signer {
... on ETHAddress {
address
}
... on PubKey {
key
}
}
}
}
... on Perpetual {
dataSourceSpecForSettlementData {
id
}
dataSourceSpecForSettlementSchedule {
id
}
}
}
@@ -119,32 +46,8 @@ export const ExplorerOracleFormMarketsDocument = gql`
}
}
}
oracleSpecsConnection {
edges {
node {
dataSourceSpec {
...ExplorerOracleDataSourceSpec
}
dataConnection(pagination: {last: 1}) {
edges {
node {
externalData {
data {
data {
name
value
}
}
}
}
}
}
}
}
}
}
${ExplorerOracleForMarketsMarketFragmentDoc}
${ExplorerOracleDataSourceSpecFragmentDoc}`;
${ExplorerOracleForMarketsMarketFragmentDoc}`;
/**
* __useExplorerOracleFormMarketsQuery__
@@ -51,26 +51,15 @@ describe('Oracle Data view', () => {
{
node: {
externalData: {
__typename: 'ExternalData',
data: {
__typename: 'Data',
matchedSpecIds: ['123'],
broadcastAt: '2023-01-01T00:00:00Z',
data: [
{
__typename: 'Property',
name: 'Test-name',
value: 'Test-data',
},
],
broadcastAt: '2022-01-01',
},
},
},
},
],
} as ExplorerOracleDataConnectionFragment['dataConnection'])
} as DataConnection)
);
expect(res.getByText('Test-name')).toBeInTheDocument();
expect(res.getByText('Test-data')).toBeInTheDocument();
expect(res.getByText('Broadcast data')).toBeInTheDocument();
});
});
@@ -1,55 +1,39 @@
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
import { TimeAgo } from '../../../components/time-ago';
import { t } from '@vegaprotocol/i18n';
const cellSpacing = 'px-3';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
interface OracleDataTypeProps {
data: ExplorerOracleDataConnectionFragment['dataConnection'];
}
/**
* If there is data that has matched this oracle, this view will
* render the data inside a collapsed element so that it can be viewed.
* Currently the data is just rendered as a JSON view, because
* that Does The Job, rather than because it's good.
*/
export function OracleData({ data }: OracleDataTypeProps) {
if (!data || !data.edges?.length) {
if (!data || !data.edges?.length || data.edges.length > 1) {
return null;
}
return (
<>
<h2 className="text-3xl font-bold mb-4 display-5 mt-5">
{t('Recent data')}
</h2>
<table>
<thead>
<tr className="text-left">
<th className={cellSpacing}>Value</th>
<th className={cellSpacing}>Key</th>
<th className={cellSpacing}>Date</th>
</tr>
</thead>
<tbody>
{data.edges.map((d) => {
if (!d) {
return null;
}
<details data-testid="oracle-data">
<summary>{t('Broadcast data')}</summary>
<ul>
{data.edges.map((d) => {
if (!d) {
return null;
}
const broadcastAt = d.node.externalData.data.broadcastAt;
const node = d.node.externalData.data.data?.at(0);
if (!node || !node.value || !node.name || !node || !broadcastAt) {
return null;
}
return (
<tr key={d.node.externalData.data.broadcastAt}>
<td className={`${cellSpacing} font-mono`}>{node.value}</td>
<td className={`${cellSpacing} font-mono`}>{node.name}</td>
<td className={cellSpacing}>
<TimeAgo date={broadcastAt} />
</td>
</tr>
);
})}
</tbody>
</table>
</>
return (
<li key={d.node.externalData.data.broadcastAt}>
<SyntaxHighlighter data={filter(d, ['__typename'])} />
</li>
);
})}
</ul>
</details>
);
}
@@ -55,13 +55,13 @@ describe('Oracle type view', () => {
const s = mock('prices.external.whatever');
expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s));
expect(res.getByText('External Data')).toBeInTheDocument();
expect(res.getByText('External data')).toBeInTheDocument();
});
it('Renders External data otherwise', () => {
const s = mock('prices.external.vegaprotocol.builtin.');
expect(isInternalSourceType(s)).toEqual(false);
const res = render(renderWrappedComponent(s));
expect(res.getByText('External Data')).toBeInTheDocument();
expect(res.getByText('External data')).toBeInTheDocument();
});
});
@@ -26,19 +26,6 @@ export function isInternalSourceType(s: SourceType) {
return false;
}
export function getExternalType(s: SourceType) {
if (s.sourceType.__typename === 'EthCallSpec') {
return 'Ethereum Contract Call';
} else {
return 'External Data';
}
}
export function getTypeString(s: SourceType) {
const isInternal = isInternalSourceType(s);
return isInternal ? 'Internal data' : getExternalType(s);
}
interface OracleDetailsTypeProps {
sourceType: SourceType;
}
@@ -52,10 +39,14 @@ export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
return null;
}
const isInternal = isInternalSourceType(sourceType);
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Type</TableHeader>
<TableCell modifier="bordered">{getTypeString(sourceType)}</TableCell>
<TableCell modifier="bordered">
{isInternal ? 'Internal data' : 'External data'}
</TableCell>
</TableRow>
);
}
@@ -1,39 +0,0 @@
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { SourceType } from './oracle';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
interface OracleDetailsEthSourceProps {
sourceType: SourceType;
}
/**
* Given an Oracle that sources data from Ethereum, this component will render
* a link to the smart contract and some basic details
*/
export function OracleEthSource({ sourceType }: OracleDetailsEthSourceProps) {
if (
sourceType.__typename !== 'DataSourceDefinitionExternal' ||
sourceType.sourceType.__typename !== 'EthCallSpec'
) {
return null;
}
const address = sourceType.sourceType.address;
if (!address) {
return null;
}
return (
<TableRow modifier="bordered">
<TableHeader scope="row">Ethereum Contract</TableHeader>
<TableCell modifier="bordered">
<EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />
<span className="mx-3">&rArr;</span>
<code>{sourceType.sourceType.method}</code>
</TableCell>
</TableRow>
);
}
@@ -1,17 +1,14 @@
import { render } from '@testing-library/react';
import { OracleFilter } from './oracle-filter';
import { getConditionsOrFilters, OracleFilter } from './oracle-filter';
import type { Filter } from './oracle-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import {
ConditionOperator,
DataSourceSpecStatus,
PropertyKeyType,
} from '@vegaprotocol/types';
import type { Condition } from '@vegaprotocol/types';
type Spec =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
const mockExternalSpec: Spec = {
const mockExternalSpec = {
sourceType: {
__typename: 'DataSourceSpecConfiguration',
filters: [
@@ -19,12 +16,12 @@ const mockExternalSpec: Spec = {
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'testKey',
name: 'test',
},
conditions: [
{
__typename: 'Condition',
value: 'testValue',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
@@ -33,6 +30,16 @@ const mockExternalSpec: Spec = {
},
};
const mockTimeSpec = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '123',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
};
function renderComponent(data: ExplorerOracleDataSourceFragment) {
return <OracleFilter data={data} />;
}
@@ -63,16 +70,11 @@ describe('Oracle Filter view', () => {
},
},
},
dataConnection: {
edges: [],
},
})
} as ExplorerOracleDataSourceFragment)
);
// Renders a comprehensible summary of key = value
expect(res.getByText('testKey')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByText('testValue')).toBeInTheDocument();
expect(res.getByText('Filter')).toBeInTheDocument();
// Avoids asserting on how the data is presented because it is very rudimentary
});
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
@@ -86,59 +88,75 @@ describe('Oracle Filter view', () => {
data: {
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
value: '1',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
sourceType: mockTimeSpec,
},
},
},
},
dataConnection: {
edges: [],
},
})
} as ExplorerOracleDataSourceFragment)
);
expect(res.getByText('Time')).toBeInTheDocument();
expect(res.getByText('=')).toBeInTheDocument();
expect(res.getByTitle('1').textContent).toMatch(/1970/);
expect(res.getByText('Filter')).toBeInTheDocument();
// Avoids asserting on how the data is presented because it is very rudimentary
});
});
it('DataSourceSpecConfigurationTime handles empty conditions', () => {
const res = render(
renderComponent({
dataSourceSpec: {
spec: {
id: 'irrelevant-test-data',
createdAt: 'irrelevant-test-data',
status: DataSourceSpecStatus.STATUS_ACTIVE,
data: {
sourceType: {
__typename: 'DataSourceDefinitionInternal',
sourceType: {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [undefined as unknown as Condition],
},
},
},
describe('getConditionsOrFilter', () => {
it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => {
expect(getConditionsOrFilters({})).toBeNull();
});
it('Returns the conditions object for time specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfigurationTime',
conditions: [
{
__typename: 'Condition',
value: '100',
operator: ConditionOperator.OPERATOR_GREATER_THAN,
},
],
};
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'key' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return conditions on a time spec'
);
}
expect(res[0].__typename).toEqual('Condition');
});
it('Returns the filters object for external specs', () => {
const mock: Filter = {
__typename: 'DataSourceSpecConfiguration',
filters: [
{
__typename: 'Filter',
key: {
type: PropertyKeyType.TYPE_INTEGER,
name: 'test',
},
conditions: [
{
__typename: 'Condition',
value: 'test',
operator: ConditionOperator.OPERATOR_EQUALS,
},
],
},
dataConnection: {
edges: [],
},
})
);
],
};
// This should never happen, but for coverage sake we test that it does this
const ul = res.getByRole('list');
expect(ul).toBeInTheDocument();
expect(ul).toBeEmptyDOMElement();
const res = getConditionsOrFilters(mock);
// This ugly construction is due to lazy typing on getConditionsOrFilter
if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) {
throw new Error(
'getConditionsOrFilter did not return filters on a external spec'
);
}
expect(res[0].__typename).toEqual('Filter');
});
});
@@ -1,15 +1,36 @@
import { t } from '@vegaprotocol/i18n';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
import { OracleSpecCondition } from './oracle-spec/condition';
import { getCharacterForOperator } from './oracle-spec/operator';
interface OracleFilterProps {
data: ExplorerOracleDataSourceFragment;
}
export type Filter =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']['sourceType'];
/**
* Given the main Filter view just uses a JSON dump view, this function
* selects the correct filter to dump in to that view. Internal oracles
* (i.e. the Time oracle) have conditions while external data sources
* have filters
*
* @param s A data source
* @returns Object an object containing conditions or filters
*/
export function getConditionsOrFilters(s: Filter) {
if (s.__typename === 'DataSourceSpecConfiguration') {
return s.filters;
} else if (s.__typename === 'DataSourceSpecConfigurationTime') {
return s.conditions;
}
return null;
}
/**
* Shows the conditions that this oracle is using to filter
* data sources, as a list.
* data sources.
*
* Renders nothing if there is no data (which will frequently)
* be the case) and if there is data, currently renders a simple
@@ -21,53 +42,16 @@ export function OracleFilter({ data }: OracleFilterProps) {
}
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
return (
<ul>
{s.conditions
.filter((c) => !!c)
.map((c) => {
if (!c) {
return null;
}
return (
<OracleSpecCondition key={c.value} data={c} type={s.__typename} />
);
})}
</ul>
);
} else if (
s.__typename === 'DataSourceSpecConfigurationTimeTrigger' &&
s.triggers
) {
return <OracleSpecInternalTimeTrigger data={s} />;
} else if (
s.__typename === 'EthCallSpec' ||
s.__typename === 'DataSourceSpecConfiguration'
) {
if (s.filters !== null && s.filters && 'filters' in s) {
return (
<ul>
{s.filters.map((f) => {
const prop = <code title={f.key.type}>{f.key.name}</code>;
const f = getConditionsOrFilters(s);
if (!f.conditions || f.conditions.length === 0) {
return prop;
} else {
return f.conditions.map((c) => {
return (
<li key={`${prop}${c.value}`}>
{prop} {getCharacterForOperator(c.operator)}{' '}
<code>{c.value ? c.value : '-'}</code>
</li>
);
});
}
})}
</ul>
);
}
if (!f) {
return null;
}
return null;
return (
<details>
<summary>{t('Filter')}</summary>
<SyntaxHighlighter data={filter(f, ['__typename'])} />
</details>
);
}
@@ -11,7 +11,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
<MemoryRouter>
<MockedProvider mocks={mocks}>
<Table>
<tbody data-testid="wrapper">
<tbody>
<OracleMarkets id={id} />
</tbody>
</Table>
@@ -23,7 +23,8 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
describe('Oracle Markets component', () => {
it('Renders a row with the market ID initially', () => {
const res = render(renderComponent('123', []));
expect(res.getByTestId('wrapper')).toBeEmptyDOMElement();
expect(res.getByText('Market')).toBeInTheDocument();
expect(res.getByText('123')).toBeInTheDocument();
});
it('Renders that this is a termination source for the right market', async () => {
@@ -33,58 +34,21 @@ describe('Oracle Markets component', () => {
},
result: {
data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '456',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: '789',
status: 'Active',
},
},
},
@@ -95,18 +59,15 @@ describe('Oracle Markets component', () => {
node: {
__typename: 'Market',
id: 'abc',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
status: 'Active',
},
},
},
@@ -130,58 +91,21 @@ describe('Oracle Markets component', () => {
},
result: {
data: {
oracleSpecsConnection: {
edges: [
{
node: {
dataConnection: {
edges: [
{
node: {
externalData: {
data: {
data: {
name: '123',
value: '456',
},
},
},
},
},
],
},
dataSourceSpec: {
spec: {
id: '789',
state: 'Active',
status: 'Active',
data: {
sourceType: {},
},
},
},
},
},
],
},
marketsConnection: {
edges: [
{
node: {
__typename: 'Market',
id: '123',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: '789',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: '123',
status: 'Active',
},
},
},
@@ -192,18 +116,15 @@ describe('Oracle Markets component', () => {
node: {
__typename: 'Market',
id: 'abc',
state: 'Active',
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
id: 'def',
status: 'Active',
},
dataSourceSpecForTradingTermination: {
id: 'ghi',
status: 'Active',
},
},
},
@@ -1,4 +1,5 @@
import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { MarketLink } from '../../../components/links';
import { TableRow, TableCell, TableHeader } from '../../../components/table';
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
@@ -23,37 +24,38 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
);
if (markets) {
const m = markets.filter((market) => {
const p = market.tradableInstrument.instrument.product;
const m = markets.find((m) => {
const p = m.tradableInstrument.instrument.product;
if (
((p.__typename === 'Future' || p.__typename === 'Perpetual') &&
p.dataSourceSpecForSettlementData.id === id) ||
('dataSourceSpecForTradingTermination' in p &&
p.dataSourceSpecForTradingTermination.id === id) ||
(p.__typename === 'Perpetual' &&
p.dataSourceSpecForSettlementSchedule.id === id)
p.dataSourceSpecForTradingTermination.id === id)
) {
return true;
}
return false;
});
if (m && m.length > 0) {
if (m && m.id) {
return (
<>
{m.map((market) => (
<TableRow modifier="bordered" key={`m-${market.id}`}>
<TableHeader scope="row">{getLabel(id, market)}</TableHeader>
<TableCell modifier="bordered" data-testid={`m-${market.id}`}>
<MarketLink id={market.id} />
</TableCell>
</TableRow>
))}
</>
<TableRow modifier="bordered">
<TableHeader scope="row">{getLabel(id, m)}</TableHeader>
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
<MarketLink id={m.id} />
</TableCell>
</TableRow>
);
}
}
return null;
return (
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Market')}</TableHeader>
<TableCell modifier="bordered">
<span>{id}</span>
</TableCell>
</TableRow>
);
}
export function getLabel(
@@ -1,37 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import fromUnixTime from 'date-fns/fromUnixTime';
import { getCharacterForOperator } from './operator';
import type { Condition } from '@vegaprotocol/types';
export interface OracleSpecCondition {
data: Condition;
type?: string;
}
export function OracleSpecCondition({ data, type }: OracleSpecCondition) {
const c = getCharacterForOperator(data.operator);
const value =
type === 'DataSourceSpecConfigurationTime' && data.value
? fromUnixTime(parseInt(data.value)).toLocaleString()
: data.value;
const typeLabel =
type === 'DataSourceSpecConfigurationTime' ? (
<span>{t('Time')}</span>
) : (
type
);
return (
<li key={`${typeLabel}${c}${value}`}>
{typeLabel} {c}{' '}
{value && (
<span
title={data.value || value}
className="underline decoration-dotted"
>
{value}
</span>
)}
</li>
);
}
@@ -1,44 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
import secondsToMinutes from 'date-fns/secondsToMinutes';
import fromUnixTime from 'date-fns/fromUnixTime';
export interface OracleSpecInternalTimeTriggerProps {
data: DataSourceSpecConfigurationTimeTrigger;
}
export function OracleSpecInternalTimeTrigger({
data,
}: OracleSpecInternalTimeTriggerProps) {
return (
<div>
<span>{t('Time')}</span>,&nbsp;
{data.triggers.map((tr) => {
return (
<span>
{tr?.initial ? (
<span title={`${tr.initial}`}>
<strong>{t('starting at')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{fromUnixTime(tr.initial).toLocaleString()}
</em>
</span>
) : (
''
)}
{tr?.every ? (
<span title={`${tr.every} ${t('seconds')}`}>
, <strong>{t('every')}</strong>{' '}
<em className="not-italic underline decoration-dotted">
{secondsToMinutes(tr.every)} {t('minutes')}
</em>{' '}
</span>
) : (
''
)}
</span>
);
})}
</div>
);
}
@@ -1,21 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { ConditionOperator } from '@vegaprotocol/types';
export function getCharacterForOperator(
operator: ConditionOperator
): React.ReactElement {
switch (operator) {
case 'OPERATOR_EQUALS':
return <span title={t('equals')}>=</span>;
case 'OPERATOR_GREATER_THAN':
return <span title={t('greater than')}>&gt;</span>;
case 'OPERATOR_GREATER_THAN_OR_EQUAL':
return <span title={t('greater than or equal')}>&ge;</span>;
case 'OPERATOR_LESS_THAN':
return <span title={t('less than')}>&lt;</span>;
case 'OPERATOR_LESS_THAN_OR_EQUAL':
return <span title={t('less than or equal')}>&le;</span>;
}
return <span>{operator}</span>;
}
@@ -14,9 +14,7 @@ import { OracleFilter } from './oracle-filter';
import { OracleDetailsType } from './oracle-details-type';
import { OracleMarkets } from './oracle-markets';
import { OracleSigners } from './oracle-signers';
import { OracleEthSource } from './oracle-eth-source';
import Hash from '../../../components/links/hash';
import { getStatusString } from '../../../components/links/oracle-link/oracle-link';
import OracleLink from '../../../components/links/oracle-link/oracle-link';
export type SourceType =
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
@@ -40,8 +38,10 @@ export const OracleDetails = ({
id,
dataSource,
dataConnection,
showBroadcasts = false,
}: OracleDetailsProps) => {
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
const reportsCount: number = dataConnection.edges?.length || 0;
return (
<div>
@@ -49,27 +49,23 @@ export const OracleDetails = ({
<TableRow modifier="bordered">
<TableHeader scope="row">{t('ID')}</TableHeader>
<TableCell modifier="bordered">
<Hash text={id} />
<OracleLink id={id} />
</TableCell>
</TableRow>
<OracleDetailsType sourceType={sourceType} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Status')}</TableHeader>
<TableCell modifier="bordered">
{getStatusString(dataSource.dataSourceSpec.spec.status)}
</TableCell>
</TableRow>
<OracleSigners sourceType={sourceType} />
<OracleEthSource sourceType={sourceType} />
<OracleMarkets id={id} />
<TableRow modifier="bordered">
<TableHeader scope="row">{t('Filter')}</TableHeader>
<TableHeader scope="row">{t('Matched data')}</TableHeader>
<TableCell modifier="bordered">
<OracleFilter data={dataSource} />
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
</TableCell>
</TableRow>
</TableWithTbody>
{dataConnection ? <OracleData data={dataConnection} /> : null}
<OracleFilter data={dataSource} />
{showBroadcasts && dataConnection ? (
<OracleData data={dataConnection} />
) : null}
</div>
);
};
@@ -1,28 +1,20 @@
import compact from 'lodash/compact';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { OracleDetails } from '../components/oracle';
import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
import { MarketLink } from '../../../components/links';
import { OracleLink } from '../../../components/links/oracle-link/oracle-link';
import { useState } from 'react';
import { MarketStateMapping } from '@vegaprotocol/types';
import type { MarketState } from '@vegaprotocol/types';
const cellSpacing = 'px-3';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
const { data, loading, error } = useExplorerOracleSpecsQuery({
errorPolicy: 'ignore',
});
useDocumentTitle(['Oracles']);
useScrollToLocation();
const [hoveredOracle, setHoveredOracle] = useState('');
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
@@ -38,148 +30,36 @@ const Oracles = () => {
data.oracleSpecsConnection.edges?.length === 0
}
>
<table className="text-left">
<thead>
<tr>
<th className={cellSpacing}>Market</th>
<th className={cellSpacing}>Type</th>
<th className={cellSpacing}>State</th>
<th className={cellSpacing}>Settlement</th>
<th className={cellSpacing}>Termination</th>
</tr>
</thead>
<tbody>
{data?.marketsConnection?.edges
? data.marketsConnection.edges.map((o) => {
let hasSeenOracleReports = false;
let settlementOracle = '-';
let settlementOracleStatus = '-';
let terminationOracle = '-';
let terminationOracleStatus = '-';
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
const id = o?.node.id;
if (!id) {
return null;
}
const dataConnection = o?.node.dataConnection;
if (
o.node.tradableInstrument.instrument.product.__typename ===
'Future'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.status;
} else if (
o.node.tradableInstrument.instrument.product.__typename ===
'Perpetual'
) {
settlementOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.id;
terminationOracle =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.id;
settlementOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.status;
terminationOracleStatus =
o.node.tradableInstrument.instrument.product
.dataSourceSpecForSettlementSchedule.status;
}
const oracleInformationUnfiltered =
data?.oracleSpecsConnection?.edges?.map((e) =>
e && e.node ? e.node : undefined
) || [];
const oracleInformation = compact(oracleInformationUnfiltered)
.filter(
(o) =>
o.dataConnection.edges &&
o.dataConnection.edges.length > 0 &&
(o.dataSourceSpec.spec.id === settlementOracle ||
o.dataSourceSpec.spec.id === terminationOracle)
)
.at(0);
if (oracleInformation) {
hasSeenOracleReports = true;
}
const oracleList = `${settlementOracle} ${terminationOracle}`;
return (
<tr
id={id}
key={id}
className={
hoveredOracle.length > 0 &&
oracleList.indexOf(hoveredOracle) > -1
? 'bg-gray-100 dark:bg-gray-800'
: ''
}
data-testid="oracle-details"
data-oracles={oracleList}
>
<td className={cellSpacing}>
<MarketLink id={id} />
</td>
<td className={cellSpacing}>
{
o.node.tradableInstrument.instrument.product
.__typename
}
</td>
<td className={cellSpacing}>
{MarketStateMapping[o.node.state as MarketState]}
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === settlementOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={settlementOracle}
status={settlementOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() => setHoveredOracle(settlementOracle)}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
<td
className={
hoveredOracle.length > 0 &&
hoveredOracle === terminationOracle
? `indent-1 ${cellSpacing}`
: cellSpacing
}
>
<OracleLink
id={terminationOracle}
status={terminationOracleStatus}
hasSeenOracleReports={hasSeenOracleReports}
onMouseOver={() =>
setHoveredOracle(terminationOracle)
}
onMouseOut={() => setHoveredOracle('')}
/>
</td>
</tr>
);
})
: null}
</tbody>
</table>
return (
<div
id={id}
key={id}
className="mb-10"
data-testid="oracle-details"
>
<OracleDetails
id={id}
dataSource={o?.node}
dataConnection={dataConnection}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</AsyncRenderer>
</section>
);
@@ -40,9 +40,10 @@ export const Oracle = () => {
id={id || ''}
dataSource={data?.oracleSpec}
dataConnection={data?.oracleSpec.dataConnection}
showBroadcasts={true}
/>
<details className="mt-5 cursor-pointer">
<summary>JSON</summary>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(data, ['__typename'])} />
</details>
</div>
@@ -1,108 +0,0 @@
import { BackgroundVideo } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
const RestrictedPage = () => {
const errorTitle = '451 Unavailable';
const errorMessage =
'Due to uncertainty about the legal and regulatory status of the content hosted on this site, it is not available to visitors in your jurisdiction.';
return (
<div>
<BackgroundVideo className="brightness-50" />
<div
className={classNames(
'max-w-[620px] p-2 mt-[10vh]',
'mx-auto my-0',
'antialiased text-white',
'overflow-hidden relative',
'flex flex-col gap-2'
)}
>
<div className="flex gap-4">
<div>{GHOST}</div>
<h1 className="text-[2.7rem] font-alpha calt break-words uppercase">
{errorTitle}
</h1>
</div>
<div className="text-sm mt-10 overflow-auto font-mono">
{errorMessage}
</div>
</div>
</div>
);
};
const GHOST = (
<svg
width="56"
height="85"
viewBox="0 0 150 234"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.43 93.6821L0 104.097V172.435H5.13793V106.2L14.0779 97.3247L10.43 93.6821Z"
fill="black"
/>
<path d="M105.328 18.521H7.70703V172.435H105.328V18.521Z" fill="white" />
<path d="M38.5364 64.6953H33.3984V69.8258H38.5364V64.6953Z" fill="black" />
<path d="M43.6731 69.8257H38.5352V74.9561H43.6731V69.8257Z" fill="black" />
<path d="M48.8098 74.9561H43.6719V80.0865H48.8098V74.9561Z" fill="black" />
<path d="M38.5364 74.9561H33.3984V80.0865H38.5364V74.9561Z" fill="black" />
<path d="M48.8098 64.6953H43.6719V69.8258H48.8098V64.6953Z" fill="black" />
<path d="M69.3606 64.6953H64.2227V69.8258H69.3606V64.6953Z" fill="black" />
<path d="M74.5012 69.8257H69.3633V74.9561H74.5012V69.8257Z" fill="black" />
<path d="M79.6379 74.9561H74.5V80.0865H79.6379V74.9561Z" fill="black" />
<path d="M69.3606 74.9561H64.2227V80.0865H69.3606V74.9561Z" fill="black" />
<path d="M79.6379 64.6953H74.5V69.8258H79.6379V64.6953Z" fill="black" />
<path d="M79.6398 90.3477H33.3984V95.4781H79.6398V90.3477Z" fill="black" />
<path d="M48.8098 172.435H43.6719V234H48.8098V172.435Z" fill="black" />
<path d="M69.3606 172.435H64.2227V234H69.3606V172.435Z" fill="black" />
<path
d="M129.87 0.00408026L92.0273 14.3682L111.195 64.7203L149.038 50.3562L129.87 0.00408026Z"
fill="#FF0081"
/>
<path
d="M108.565 21.7836L103.762 23.6064L105.587 28.4024L110.39 26.5795L108.565 21.7836Z"
fill="white"
/>
<path
d="M115.189 24.7596L110.387 26.583L112.213 31.3785L117.015 29.5551L115.189 24.7596Z"
fill="white"
/>
<path
d="M121.826 27.7348L117.023 29.5576L118.849 34.3536L123.652 32.5307L121.826 27.7348Z"
fill="white"
/>
<path
d="M128.447 30.7059L123.645 32.5293L125.471 37.3247L130.273 35.5014L128.447 30.7059Z"
fill="white"
/>
<path
d="M135.084 33.6854L130.281 35.5083L132.107 40.3043L136.91 38.4814L135.084 33.6854Z"
fill="white"
/>
<path
d="M124.799 21.1156L119.996 22.939L121.822 27.7344L126.625 25.911L124.799 21.1156Z"
fill="white"
/>
<path
d="M127.775 14.492L122.973 16.3154L124.799 21.1109L129.601 19.2875L127.775 14.492Z"
fill="white"
/>
<path
d="M118.838 34.3499L114.035 36.1733L115.861 40.9688L120.664 39.1454L118.838 34.3499Z"
fill="white"
/>
<path
d="M115.857 40.9691L111.055 42.7925L112.881 47.5879L117.683 45.7645L115.857 40.9691Z"
fill="white"
/>
<path
d="M129.938 52.3822L139.906 91.5276L100.703 101.429L101.988 106.406L146.174 95.2215L134.922 51.0996L129.938 52.3822Z"
fill="black"
/>
</svg>
);
export default RestrictedPage;
@@ -1,6 +1,5 @@
export const Routes = {
HOME: '/',
RESTRICTED: '/restricted',
TX: 'txs',
BLOCKS: 'blocks',
PARTIES: 'parties',
@@ -30,7 +30,6 @@ import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment';
import RestrictedPage from './restricted';
export type Navigable = {
path: string;
@@ -357,14 +356,6 @@ export const routerConfig: Route[] = [
...validators,
],
},
{
path: Routes.RESTRICTED,
element: <RestrictedPage />,
handle: {
name: t('Restricted'),
text: t('Restricted'),
},
},
];
export const router = createBrowserRouter(routerConfig);
@@ -14,10 +14,6 @@ export interface BlockExplorerTransactionResult {
value: string;
};
error?: string;
// These aren't strictly optional but are new in 0.73.0 so we need to make them optional
createdAt?: string;
version?: string;
pow?: string;
}
export interface BlockExplorerTransactions {
+247 -670
View File
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -2,11 +2,7 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
"types": ["node"]
},
"files": [
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
+1 -7
View File
@@ -3,13 +3,7 @@
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": [
"jest",
"node",
"@testing-library/jest-dom",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"include": [
"**/*.test.ts",
+1 -3
View File
@@ -4,9 +4,7 @@
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"cypress/unsafe-to-chain-command": 0
}
"rules": {}
}
]
}
+1 -1
View File
@@ -17,7 +17,7 @@
}
},
"lint": {
"executor": "@nx/eslint:lint",
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/governance-e2e/**/*.{js,ts}"]
@@ -61,7 +61,6 @@ const marketProposalType = 'proposal-type';
describe(
'Governance flow for proposal details',
{ tags: '@slow' },
// @ts-ignore clash between jest and cypress
function () {
before('connect wallets and set approval limit', function () {
cy.visit('/');
@@ -79,7 +78,6 @@ describe(
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
});
// @ts-ignore clash between jest and cypress
beforeEach('visit proposals tab', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -325,7 +323,6 @@ describe(
});
});
cy.VegaWalletSubmitProposal(
// @ts-ignore this is any
createSuccessorMarketProposalTxBody(this.parentMarketId)
);
navigateTo(navigation.proposals);
@@ -337,7 +334,6 @@ describe(
cy.getByTestId(proposalTermsToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
// @ts-ignore this is any
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
@@ -356,7 +352,6 @@ describe(
validateProposalDetailsDiff(
'Parent Market ID',
proposalChangeType.ADDED,
// @ts-ignore this is any
this.parentMarketId
);
validateProposalDetailsDiff(
@@ -455,7 +450,6 @@ describe(
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
// @ts-ignore this is any
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
@@ -472,7 +466,6 @@ describe(
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
// @ts-ignore this is any
.and('eq', this.parentMarketId);
});
});
@@ -483,7 +476,6 @@ describe(
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
// @ts-ignore this is any
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
@@ -497,7 +489,6 @@ describe(
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
// @ts-ignore this is any
.and('eq', this.parentMarketId);
});
});
@@ -508,7 +499,6 @@ describe(
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
submitUniqueRawProposal({
proposalBody: proposalPath,
// @ts-ignore this is any
updateMarketId: this.parentMarketId,
enactmentTimestamp: enactmentTimestamp,
closingTimestamp: closingTimestamp,
@@ -528,7 +518,6 @@ describe(
cy.getByTestId('proposal-update-market-state').within(() => {
getProposalInformationFromTable('Market ID')
.invoke('text')
// @ts-ignore this is any
.and('eq', this.parentMarketId);
getProposalDetailsValue('Termination Price').should(
'contain.text',
@@ -548,10 +537,10 @@ describe(
cy.VegaWalletSubmitProposal(createGovernanceTransferProposalTxBody());
cy.reload();
getProposalFromTitle('Governance transfer proposal').within(() => {
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'New transfer');
cy.getByTestId(marketProposalType).should('have.text', 'NewTransfer');
cy.getByTestId(governanceTransferToggle).click();
cy.getByTestId('proposal-transfer-details-table').within(() => {
getProposalInformationFromTable('Source Type')
@@ -601,7 +590,7 @@ describe(
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(marketProposalType).should('have.text', 'Cancel transfer');
cy.getByTestId(marketProposalType).should('have.text', 'CancelTransfer');
getProposalInformationFromTable('Error details')
.invoke('text')
.and('eq', 'Governance transfer invalid transfer id not found');
@@ -37,7 +37,6 @@ context(
vegaWalletSetSpecifiedApprovalAmount('1000');
});
// @ts-ignore clash between jest and cypress
beforeEach('visit proposals', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -78,7 +78,6 @@ context(
vegaWalletSetSpecifiedApprovalAmount('1000');
});
// @ts-ignore clash between jest and cypress
beforeEach('visit governance tab', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -302,7 +301,6 @@ context(
cy.getByTestId(voteButtons).should('not.exist');
cy.getByTestId('min-proposal-requirements').should(
'have.text',
// @ts-ignore this is any
`You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal`
);
});
@@ -70,7 +70,6 @@ context(
vegaWalletSetSpecifiedApprovalAmount('1000');
});
// @ts-ignore clash between jest and cypress
beforeEach('visit governance tab', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -217,7 +216,6 @@ context(
// 3003-PMAN-001
it(
'Able to submit valid new market proposal',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
const proposalTitle = 'Test new market proposal';
@@ -633,7 +631,6 @@ context(
);
cy.fixture('/proposals/successor-market').then((newMarketProposal) => {
newMarketProposal.changes.successor.parentMarketId =
// @ts-ignore clash between jest and cypress
this.parentMarketId;
const newMarketPayload = JSON.stringify(newMarketProposal);
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
@@ -661,7 +658,6 @@ context(
.should('have.text', 'Successor market to: TEST.24h')
.find('a')
.should('have.attr', 'href')
// @ts-ignore clash between jest and cypress
.and('contain', this.parentMarketId);
});
});
@@ -33,14 +33,12 @@ const voteMajorityNotMet = 'token-majority-not-met';
const voteMajorityMet = 'token-majority-met';
const votesForPercentage = 'votes-for-percentage';
// @ts-ignore clash between jest and cypress
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
before('connect wallets and set approval limit', function () {
vegaWalletSetSpecifiedApprovalAmount('1000');
cy.visit('/');
});
// @ts-ignore clash between jest and cypress
beforeEach('visit proposals tab', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -108,7 +106,6 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
it('Newly created proposals list - shows open proposals in an open state', function () {
// 3001-VOTE-004
// 3001-VOTE-035
// @ts-ignore clash between jest and cypress
createRawProposal(this.minProposerBalance);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
@@ -64,7 +64,6 @@ context(
describe('Eth wallet - contains VEGA tokens', function () {
beforeEach(
// @ts-ignore clash between jest and cypress
'teardown wallet & drill into a specific validator',
function () {
cy.clearLocalStorage();
@@ -238,7 +237,6 @@ context(
// 1002-STKE-041 1002-STKE-053
it(
'Able to remove part of a stake against a validator',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
ensureSpecifiedUnstakedTokensAreAssociated('4');
@@ -52,7 +52,6 @@ context(
describe('Eth wallet - contains VEGA tokens', function () {
beforeEach(
// @ts-ignore clash between jest and cypress
'teardown wallet & drill into a specific validator',
function () {
cy.clearLocalStorage();
@@ -68,7 +67,6 @@ context(
it(
'Able to associate tokens - from wallet',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
//1004-ASSO-003
@@ -48,7 +48,6 @@ context(
depositAsset(usdcEthAddress, '1000', 5);
});
// @ts-ignore clash between jest and cypress
beforeEach('Navigate to withdrawal page', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -102,7 +101,6 @@ context(
// eslint-disable-next-line
it.skip(
'Able to withdraw asset: -eth wallet connected -withdraw funds button',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
// fill in withdrawal form
@@ -25,7 +25,6 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
);
});
// @ts-ignore clash between jest and cypress
beforeEach('visit home page', function () {
cy.clearLocalStorage();
turnTelemetryOff();
@@ -52,7 +52,6 @@ context(
.invoke('text')
.then(($newPageNumber) => {
const newPageNumber = Number($newPageNumber.slice(5));
// @ts-ignore clash between jest and cypress
expect(newPageNumber).to.be.greaterThan(currentPageNumber);
cy.getByTestId('goto-previous-page').click();
cy.getByTestId('page-info').should(
@@ -44,7 +44,6 @@ context('Validators Page - verify elements on page', function () {
cy.mockChainId();
});
// @ts-ignore clash between jest and cypress
describe('with wallets disconnected', { tags: '@smoke' }, function () {
it('Should have validators tab highlighted', function () {
verifyTabHighlighted(navigation.validators);
@@ -77,7 +76,6 @@ context('Validators Page - verify elements on page', function () {
describe(
'Should be able to see validator list from the staking page',
{ tags: '@regression' },
// @ts-ignore clash between jest and cypress
function () {
// 1002-STKE-050
it('Should be able to see validator names', function () {
@@ -182,7 +180,6 @@ context('Validators Page - verify elements on page', function () {
describe(
'Should be able to see static information about a validator',
{ tags: '@smoke' },
// @ts-ignore clash between jest and cypress
function () {
before('connect wallets and click on validator', function () {
cy.mockChainId();
@@ -198,7 +198,6 @@ context(
});
})
.then(function () {
// @ts-ignore clash between jest and cypress
expect(parseFloat(this.value).toFixed(1)).to.equal(
(Math.round((this.locked + this.unlocked) * 100) / 100).toFixed(
1
@@ -271,7 +270,6 @@ context(
});
})
.then(function () {
// @ts-ignore clash between jest and cypress
expect(this.value).to.equal(this.locked + this.unlocked);
});
});
@@ -105,7 +105,6 @@ context(
// 0002-WCON-008
it(
'should have truncated account number visible',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
cy.get(walletContainer).within(() => {
@@ -126,7 +125,6 @@ context(
it(
'should have Vega Associated currency value visible',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
cy.get(walletContainer).within(() => {
@@ -137,19 +135,14 @@ context(
}
);
it(
'should have Unstaked value visible',
// @ts-ignore clash between jest and cypress
{ tags: '@smoke' },
function () {
cy.get(walletContainer).within(() => {
cy.get(vegaUnstaked)
.should('be.visible')
.invoke('text')
.and('not.be.empty');
});
}
);
it('should have Unstaked value visible', { tags: '@smoke' }, function () {
cy.get(walletContainer).within(() => {
cy.get(vegaUnstaked)
.should('be.visible')
.invoke('text')
.and('not.be.empty');
});
});
it('should have Governance button visible', function () {
cy.get(walletContainer).within(() => {
@@ -306,7 +299,6 @@ context(
.siblings(txTimeout)
.should((elementAmount) => {
const displayedAmount = parseFloat(elementAmount.text());
// @ts-ignore clash between jest and cypress
expect(displayedAmount).be.gte(expectedAmount);
});

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