feat(explorer): explorer oracle section rework (#5135)
This commit is contained in:
parent
8069aa5ee7
commit
6e677084a3
@ -1,68 +0,0 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -77,7 +77,7 @@
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": [
|
||||
"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"
|
||||
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.73.0/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
export type HashProps = {
|
||||
text: string;
|
||||
truncate?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -7,10 +8,16 @@ 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 }: HashProps) => {
|
||||
const Hash = ({ text, truncate = false }: HashProps) => {
|
||||
const h = truncate ? text.slice(0, 6) : text;
|
||||
|
||||
return (
|
||||
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
|
||||
{text}
|
||||
<code
|
||||
title={text}
|
||||
className="break-all font-mono"
|
||||
style={{ wordWrap: 'break-word' }}
|
||||
>
|
||||
{h}
|
||||
</code>
|
||||
);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ query ExplorerMarket($id: ID!) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
@ -22,6 +23,5 @@ query ExplorerMarket($id: ID!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ export const ExplorerMarketDocument = gql`
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
@ -36,7 +37,6 @@ export const ExplorerMarketDocument = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -63,6 +63,9 @@ describe('Market link component', () => {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -23,6 +23,7 @@ const MarketLink = ({
|
||||
}: MarketLinkProps) => {
|
||||
const { data, error, loading } = useExplorerMarketQuery({
|
||||
variables: { id },
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
let label = <span>{id}</span>;
|
||||
|
@ -0,0 +1,57 @@
|
||||
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,20 +3,93 @@ 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;
|
||||
};
|
||||
|
||||
const OracleLink = ({ id, ...props }: OracleLinkProps) => {
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.ORACLES}/${id}`}
|
||||
>
|
||||
<Hash text={id} />
|
||||
</Link>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -74,6 +74,9 @@ function renderExistingAmend(
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -124,6 +127,9 @@ function renderExistingAmend(
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -152,6 +158,9 @@ function renderExistingAmend(
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -22,5 +22,9 @@ export const TimeAgo = ({ date, ...props }: TimeAgoProps) => {
|
||||
return <>{t('Date unknown')}</>;
|
||||
}
|
||||
|
||||
return <span {...props}>{t(`${distanceToNow} ago`)}</span>;
|
||||
return (
|
||||
<span {...props} title={date} className="underline decoration-dotted">
|
||||
{t(`${distanceToNow} ago`)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
@ -1,131 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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');
|
||||
});
|
||||
});
|
@ -1,17 +0,0 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
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');
|
||||
});
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -1,94 +0,0 @@
|
||||
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,10 +1,11 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
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';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
export type VegaPeggedReference = components['schemas']['vegaPeggedReference'];
|
||||
|
||||
export interface TxDetailsOrderProps {
|
||||
offset: string;
|
||||
|
@ -33,6 +33,14 @@ 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,10 +1,50 @@
|
||||
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';
|
||||
import { ChainEvent } from './chain-events';
|
||||
|
||||
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 '-';
|
||||
}
|
||||
}
|
||||
|
||||
interface TxDetailsChainEventProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
|
@ -5,7 +5,6 @@ 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';
|
||||
|
||||
@ -40,40 +39,32 @@ export const TxDetailsLiquidityAmendment = ({
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<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 ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>{t('Commitment amount')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
<PriceInMarket
|
||||
price={amendment.commitmentAmount}
|
||||
marketId={marketId}
|
||||
decimalSource="SETTLEMENT_ASSET"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{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} />
|
||||
</>
|
||||
) : null}
|
||||
{amendment.fee ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Fee')}</TableCell>
|
||||
<TableCell>{fee}%</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
|
@ -5,7 +5,6 @@ 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,40 +38,32 @@ export const TxDetailsLiquiditySubmission = ({
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<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 ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>{t('Commitment amount')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
<PriceInMarket
|
||||
price={submission.commitmentAmount}
|
||||
marketId={marketId}
|
||||
decimalSource="SETTLEMENT_ASSET"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{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} />
|
||||
</>
|
||||
) : null}
|
||||
{submission.fee ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Fee')}</TableCell>
|
||||
<TableCell>{fee}%</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
dataConnection(pagination: { first: 30 }) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@ -45,6 +45,16 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
operator
|
||||
}
|
||||
}
|
||||
... on DataSourceSpecConfigurationTimeTrigger {
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
triggers {
|
||||
initial
|
||||
every
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
@ -103,6 +113,23 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
}
|
||||
}
|
||||
}
|
||||
... on EthCallSpec {
|
||||
abi
|
||||
address
|
||||
requiredConfirmations
|
||||
method
|
||||
filters {
|
||||
key {
|
||||
type
|
||||
name
|
||||
numberDecimalPlaces
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +139,7 @@ fragment ExplorerOracleDataSource on OracleSpec {
|
||||
}
|
||||
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection(pagination: { first: 50 }) {
|
||||
oracleSpecsConnection(pagination: { first: 30 }) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
|
@ -1,22 +1,85 @@
|
||||
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 {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
}
|
||||
dataSourceSpecForTradingTermination {
|
||||
id
|
||||
}
|
||||
...ExplorerOracleFuture
|
||||
}
|
||||
... on Perpetual {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
...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
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSourceSpecForSettlementSchedule {
|
||||
id
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on EthCallSpec {
|
||||
address
|
||||
}
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,4 +95,27 @@ query ExplorerOracleFormMarkets {
|
||||
}
|
||||
}
|
||||
}
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: { last: 1 }) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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' } } } } }, 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 ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', 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 ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', abi?: Array<string> | null, args?: Array<string> | null, method: string, requiredConfirmations: number, address: string, normalisers?: Array<{ __typename?: 'Normaliser', name: string, expression: string }> | null, trigger: { __typename?: 'EthCallTrigger', trigger: { __typename?: 'EthTimeTrigger', initial?: any | null, every?: number | null, until?: any | null } }, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } | { __typename?: 'DataSourceSpecConfigurationTimeTrigger' } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } | null };
|
||||
export type ExplorerOracleSpecByIdQuery = { __typename?: 'Query', oracleSpec?: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType, numberDecimalPlaces?: number | null }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } | { __typename?: 'EthCallSpec', 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 const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
dataConnection(pagination: {first: 30}) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
@ -68,6 +68,16 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
operator
|
||||
}
|
||||
}
|
||||
... on DataSourceSpecConfigurationTimeTrigger {
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
triggers {
|
||||
initial
|
||||
every
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
@ -126,6 +136,23 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
... on EthCallSpec {
|
||||
abi
|
||||
address
|
||||
requiredConfirmations
|
||||
method
|
||||
filters {
|
||||
key {
|
||||
type
|
||||
name
|
||||
numberDecimalPlaces
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +163,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
export const ExplorerOracleSpecsDocument = gql`
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection(pagination: {first: 50}) {
|
||||
oracleSpecsConnection(pagination: {first: 30}) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
|
@ -3,33 +3,106 @@ import * as Types from '@vegaprotocol/types';
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
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 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 ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
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 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 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 {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
}
|
||||
dataSourceSpecForTradingTermination {
|
||||
id
|
||||
}
|
||||
...ExplorerOracleFuture
|
||||
}
|
||||
... on Perpetual {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
...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
|
||||
}
|
||||
}
|
||||
}
|
||||
dataSourceSpecForSettlementSchedule {
|
||||
id
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on EthCallSpec {
|
||||
address
|
||||
}
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,8 +119,32 @@ export const ExplorerOracleFormMarketsDocument = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
dataSourceSpec {
|
||||
...ExplorerOracleDataSourceSpec
|
||||
}
|
||||
dataConnection(pagination: {last: 1}) {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ExplorerOracleForMarketsMarketFragmentDoc}`;
|
||||
${ExplorerOracleForMarketsMarketFragmentDoc}
|
||||
${ExplorerOracleDataSourceSpecFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleFormMarketsQuery__
|
||||
|
@ -51,15 +51,26 @@ describe('Oracle Data view', () => {
|
||||
{
|
||||
node: {
|
||||
externalData: {
|
||||
__typename: 'ExternalData',
|
||||
data: {
|
||||
broadcastAt: '2022-01-01',
|
||||
__typename: 'Data',
|
||||
matchedSpecIds: ['123'],
|
||||
broadcastAt: '2023-01-01T00:00:00Z',
|
||||
data: [
|
||||
{
|
||||
__typename: 'Property',
|
||||
name: 'Test-name',
|
||||
value: 'Test-data',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
} as DataConnection)
|
||||
} as ExplorerOracleDataConnectionFragment['dataConnection'])
|
||||
);
|
||||
expect(res.getByText('Broadcast data')).toBeInTheDocument();
|
||||
expect(res.getByText('Test-name')).toBeInTheDocument();
|
||||
expect(res.getByText('Test-data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,39 +1,55 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import filter from 'recursive-key-filter';
|
||||
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
import { TimeAgo } from '../../../components/time-ago';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
const cellSpacing = 'px-3';
|
||||
|
||||
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 || data.edges.length > 1) {
|
||||
if (!data || !data.edges?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<details data-testid="oracle-data">
|
||||
<summary>{t('Broadcast data')}</summary>
|
||||
<ul>
|
||||
{data.edges.map((d) => {
|
||||
if (!d) {
|
||||
return null;
|
||||
}
|
||||
<>
|
||||
<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;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={d.node.externalData.data.broadcastAt}>
|
||||
<SyntaxHighlighter data={filter(d, ['__typename'])} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</details>
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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,6 +26,19 @@ 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;
|
||||
}
|
||||
@ -39,14 +52,10 @@ export function OracleDetailsType({ sourceType }: OracleDetailsTypeProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isInternal = isInternalSourceType(sourceType);
|
||||
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Type</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
{isInternal ? 'Internal data' : 'External data'}
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered">{getTypeString(sourceType)}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
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">⇒</span>
|
||||
<code>{sourceType.sourceType.method}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
@ -1,14 +1,17 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { getConditionsOrFilters, OracleFilter } from './oracle-filter';
|
||||
import type { Filter } from './oracle-filter';
|
||||
import { OracleFilter } from './oracle-filter';
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import {
|
||||
ConditionOperator,
|
||||
DataSourceSpecStatus,
|
||||
PropertyKeyType,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { Condition } from '@vegaprotocol/types';
|
||||
|
||||
const mockExternalSpec = {
|
||||
type Spec =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
||||
|
||||
const mockExternalSpec: Spec = {
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
filters: [
|
||||
@ -16,12 +19,12 @@ const mockExternalSpec = {
|
||||
__typename: 'Filter',
|
||||
key: {
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
name: 'test',
|
||||
name: 'testKey',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
__typename: 'Condition',
|
||||
value: 'test',
|
||||
value: 'testValue',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
@ -30,16 +33,6 @@ const mockExternalSpec = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimeSpec = {
|
||||
__typename: 'DataSourceSpecConfigurationTime',
|
||||
conditions: [
|
||||
{
|
||||
value: '123',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function renderComponent(data: ExplorerOracleDataSourceFragment) {
|
||||
return <OracleFilter data={data} />;
|
||||
}
|
||||
@ -70,11 +63,16 @@ describe('Oracle Filter view', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ExplorerOracleDataSourceFragment)
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(res.getByText('Filter')).toBeInTheDocument();
|
||||
// Avoids asserting on how the data is presented because it is very rudimentary
|
||||
// Renders a comprehensible summary of key = value
|
||||
expect(res.getByText('testKey')).toBeInTheDocument();
|
||||
expect(res.getByText('=')).toBeInTheDocument();
|
||||
expect(res.getByText('testValue')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
|
||||
@ -88,75 +86,59 @@ describe('Oracle Filter view', () => {
|
||||
data: {
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
sourceType: mockTimeSpec,
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfigurationTime',
|
||||
conditions: [
|
||||
{
|
||||
value: '1',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ExplorerOracleDataSourceFragment)
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(res.getByText('Filter')).toBeInTheDocument();
|
||||
expect(res.getByText('Time')).toBeInTheDocument();
|
||||
expect(res.getByText('=')).toBeInTheDocument();
|
||||
expect(res.getByTitle('1').textContent).toMatch(/1970/);
|
||||
// Avoids asserting on how the data is presented because it is very rudimentary
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
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],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
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');
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
|
@ -1,36 +1,15 @@
|
||||
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.
|
||||
* data sources, as a list.
|
||||
*
|
||||
* Renders nothing if there is no data (which will frequently)
|
||||
* be the case) and if there is data, currently renders a simple
|
||||
@ -42,16 +21,53 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
||||
}
|
||||
|
||||
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
||||
const f = getConditionsOrFilters(s);
|
||||
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>;
|
||||
|
||||
if (!f) {
|
||||
return null;
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<details>
|
||||
<summary>{t('Filter')}</summary>
|
||||
<SyntaxHighlighter data={filter(f, ['__typename'])} />
|
||||
</details>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ function renderComponent(id: string, mocks: MockedResponse[]) {
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Table>
|
||||
<tbody>
|
||||
<tbody data-testid="wrapper">
|
||||
<OracleMarkets id={id} />
|
||||
</tbody>
|
||||
</Table>
|
||||
@ -23,8 +23,7 @@ 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.getByText('Market')).toBeInTheDocument();
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
expect(res.getByTestId('wrapper')).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders that this is a termination source for the right market', async () => {
|
||||
@ -34,21 +33,58 @@ 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -59,15 +95,18 @@ 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -91,21 +130,58 @@ 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -116,15 +192,18 @@ 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,5 +1,4 @@
|
||||
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';
|
||||
@ -24,38 +23,37 @@ export function OracleMarkets({ id }: OracleMarketsProps) {
|
||||
);
|
||||
|
||||
if (markets) {
|
||||
const m = markets.find((m) => {
|
||||
const p = m.tradableInstrument.instrument.product;
|
||||
const m = markets.filter((market) => {
|
||||
const p = market.tradableInstrument.instrument.product;
|
||||
if (
|
||||
((p.__typename === 'Future' || p.__typename === 'Perpetual') &&
|
||||
p.dataSourceSpecForSettlementData.id === id) ||
|
||||
('dataSourceSpecForTradingTermination' in p &&
|
||||
p.dataSourceSpecForTradingTermination.id === id)
|
||||
p.dataSourceSpecForTradingTermination.id === id) ||
|
||||
(p.__typename === 'Perpetual' &&
|
||||
p.dataSourceSpecForSettlementSchedule.id === id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (m && m.id) {
|
||||
if (m && m.length > 0) {
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{getLabel(id, m)}</TableHeader>
|
||||
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
|
||||
<MarketLink id={m.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<>
|
||||
{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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Market')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<span>{id}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getLabel(
|
||||
|
@ -0,0 +1,37 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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>,
|
||||
{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>
|
||||
);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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')}>></span>;
|
||||
case 'OPERATOR_GREATER_THAN_OR_EQUAL':
|
||||
return <span title={t('greater than or equal')}>≥</span>;
|
||||
case 'OPERATOR_LESS_THAN':
|
||||
return <span title={t('less than')}><</span>;
|
||||
case 'OPERATOR_LESS_THAN_OR_EQUAL':
|
||||
return <span title={t('less than or equal')}>≤</span>;
|
||||
}
|
||||
|
||||
return <span>{operator}</span>;
|
||||
}
|
@ -14,7 +14,9 @@ import { OracleFilter } from './oracle-filter';
|
||||
import { OracleDetailsType } from './oracle-details-type';
|
||||
import { OracleMarkets } from './oracle-markets';
|
||||
import { OracleSigners } from './oracle-signers';
|
||||
import OracleLink from '../../../components/links/oracle-link/oracle-link';
|
||||
import { OracleEthSource } from './oracle-eth-source';
|
||||
import Hash from '../../../components/links/hash';
|
||||
import { getStatusString } from '../../../components/links/oracle-link/oracle-link';
|
||||
|
||||
export type SourceType =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
||||
@ -38,10 +40,8 @@ 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,23 +49,27 @@ export const OracleDetails = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('ID')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<OracleLink id={id} />
|
||||
<Hash text={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('Matched data')}</TableHeader>
|
||||
<TableHeader scope="row">{t('Filter')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
{showBroadcasts ? reportsCount : reportsCount > 0 ? '✅' : '❌'}
|
||||
<OracleFilter data={dataSource} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<OracleFilter data={dataSource} />
|
||||
{showBroadcasts && dataConnection ? (
|
||||
<OracleData data={dataConnection} />
|
||||
) : null}
|
||||
{dataConnection ? <OracleData data={dataConnection} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,28 @@
|
||||
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import compact from 'lodash/compact';
|
||||
import { AsyncRenderer } 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 filter from 'recursive-key-filter';
|
||||
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';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading, error } = useExplorerOracleSpecsQuery({
|
||||
const { data, loading, error } = useExplorerOracleFormMarketsQuery({
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
const [hoveredOracle, setHoveredOracle] = useState('');
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
@ -30,36 +38,148 @@ const Oracles = () => {
|
||||
data.oracleSpecsConnection.edges?.length === 0
|
||||
}
|
||||
>
|
||||
{data?.oracleSpecsConnection?.edges
|
||||
? data.oracleSpecsConnection.edges.map((o) => {
|
||||
const id = o?.node.dataSourceSpec.spec.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
<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 = '-';
|
||||
|
||||
const dataConnection = o?.node.dataConnection;
|
||||
const id = o?.node.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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}
|
||||
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>
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
);
|
||||
|
@ -40,10 +40,9 @@ export const Oracle = () => {
|
||||
id={id || ''}
|
||||
dataSource={data?.oracleSpec}
|
||||
dataConnection={data?.oracleSpec.dataConnection}
|
||||
showBroadcasts={true}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
<details className="mt-5 cursor-pointer">
|
||||
<summary>JSON</summary>
|
||||
<SyntaxHighlighter data={filter(data, ['__typename'])} />
|
||||
</details>
|
||||
</div>
|
||||
|
917
apps/explorer/src/types/explorer.d.ts
vendored
917
apps/explorer/src/types/explorer.d.ts
vendored
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user