feat(oracles,trading): 3224 add oracle links to market info (#3297)
This commit is contained in:
parent
d02feee5c6
commit
b9c4057ce5
@ -19,6 +19,7 @@ CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
|
|||||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||||
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
|
||||||
|
CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
|
||||||
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
|
||||||
CYPRESS_VEGA_ENV=CUSTOM
|
CYPRESS_VEGA_ENV=CUSTOM
|
||||||
|
@ -180,7 +180,15 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
|||||||
'termination.BTC.value'
|
'termination.BTC.value'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// check that links to github for oracle proofs are shown
|
||||||
cy.getByTestId(accordionContent)
|
cy.getByTestId(accordionContent)
|
||||||
|
.getByTestId('oracle-proof-links')
|
||||||
|
.find(`[data-testid="${externalLink}"]`)
|
||||||
|
.should('have.attr', 'href')
|
||||||
|
.and('contain', 'https://github.com/vegaprotocol/well-known');
|
||||||
|
|
||||||
|
cy.getByTestId(accordionContent)
|
||||||
|
.getByTestId('oracle-spec-links')
|
||||||
.find(`[data-testid="${externalLink}"]`)
|
.find(`[data-testid="${externalLink}"]`)
|
||||||
.should('have.attr', 'href')
|
.should('have.attr', 'href')
|
||||||
.and('contain', '/oracles');
|
.and('contain', '/oracles');
|
||||||
|
@ -35,6 +35,8 @@ type MarketPageMockData = {
|
|||||||
trigger?: Schema.AuctionTrigger;
|
trigger?: Schema.AuctionTrigger;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ORACLE_PUBKEY = Cypress.env('ORACLE_PUBKEY');
|
||||||
|
|
||||||
const marketDataOverride = (
|
const marketDataOverride = (
|
||||||
data: MarketPageMockData
|
data: MarketPageMockData
|
||||||
): PartialDeep<MarketDataQuery> => ({
|
): PartialDeep<MarketDataQuery> => ({
|
||||||
@ -96,7 +98,54 @@ const mockTradingPage = (
|
|||||||
aliasGQLQuery(req, 'Margins', marginsQuery());
|
aliasGQLQuery(req, 'Margins', marginsQuery());
|
||||||
aliasGQLQuery(req, 'Assets', assetsQuery());
|
aliasGQLQuery(req, 'Assets', assetsQuery());
|
||||||
aliasGQLQuery(req, 'Asset', assetQuery());
|
aliasGQLQuery(req, 'Asset', assetQuery());
|
||||||
aliasGQLQuery(req, 'MarketInfo', marketInfoQuery());
|
aliasGQLQuery(
|
||||||
|
req,
|
||||||
|
'MarketInfo',
|
||||||
|
marketInfoQuery({
|
||||||
|
market: {
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
product: {
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
sourceType: {
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceSpecForTradingTermination: {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
sourceType: {
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
aliasGQLQuery(req, 'Trades', tradesQuery());
|
aliasGQLQuery(req, 'Trades', tradesQuery());
|
||||||
aliasGQLQuery(req, 'Chart', chartQuery());
|
aliasGQLQuery(req, 'Chart', chartQuery());
|
||||||
aliasGQLQuery(req, 'Candles', candlesQuery());
|
aliasGQLQuery(req, 'Candles', candlesQuery());
|
||||||
@ -127,6 +176,40 @@ export const addMockTradingPage = () => {
|
|||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
mockTradingPage(req, state, tradingMode, trigger);
|
mockTradingPage(req, state, tradingMode, trigger);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Prevent request to github, return some dummy content
|
||||||
|
cy.intercept(
|
||||||
|
'GET',
|
||||||
|
/^https:\/\/raw.githubusercontent.com\/vegaprotocol\/well-known/,
|
||||||
|
{
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
name: 'Another oracle',
|
||||||
|
url: 'https://zombo.com',
|
||||||
|
description_markdown:
|
||||||
|
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||||
|
oracle: {
|
||||||
|
status: 'GOOD',
|
||||||
|
status_reason: '',
|
||||||
|
first_verified: '2022-01-01T00:00:00.000Z',
|
||||||
|
last_verified: '2022-12-31T00:00:00.000Z',
|
||||||
|
type: 'public_key',
|
||||||
|
public_key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
proofs: [
|
||||||
|
{
|
||||||
|
format: 'signed_message',
|
||||||
|
available: true,
|
||||||
|
type: 'public_key',
|
||||||
|
public_key: ORACLE_PUBKEY,
|
||||||
|
message: 'SOMEHEX',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/public_key-${ORACLE_PUBKEY}.toml`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
|||||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||||
|
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
|
||||||
NX_VEGA_ENV=STAGNET3
|
NX_VEGA_ENV=STAGNET3
|
||||||
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
NX_VEGA_EXPLORER_URL=https://stagnet3.explorer.vega.xyz
|
||||||
|
@ -277,6 +277,7 @@ function compileEnvVars() {
|
|||||||
),
|
),
|
||||||
ETH_LOCAL_PROVIDER_URL: process.env['NX_ETH_LOCAL_PROVIDER_URL'],
|
ETH_LOCAL_PROVIDER_URL: process.env['NX_ETH_LOCAL_PROVIDER_URL'],
|
||||||
ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'],
|
ETH_WALLET_MNEMONIC: process.env['NX_ETH_WALLET_MNEMONIC'],
|
||||||
|
ORACLE_PROOFS_URL: process.env['NX_ORACLE_PROOFS_URL'],
|
||||||
VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'],
|
VEGA_DOCS_URL: process.env['NX_VEGA_DOCS_URL'],
|
||||||
VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
|
VEGA_EXPLORER_URL: process.env['NX_VEGA_EXPLORER_URL'],
|
||||||
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
|
VEGA_TOKEN_URL: process.env['NX_VEGA_TOKEN_URL'],
|
||||||
|
@ -20,6 +20,7 @@ const schemaObject = {
|
|||||||
GIT_COMMIT_HASH: z.optional(z.string()),
|
GIT_COMMIT_HASH: z.optional(z.string()),
|
||||||
GIT_ORIGIN_URL: z.optional(z.string()),
|
GIT_ORIGIN_URL: z.optional(z.string()),
|
||||||
GITHUB_FEEDBACK_URL: z.optional(z.string()),
|
GITHUB_FEEDBACK_URL: z.optional(z.string()),
|
||||||
|
ORACLE_PROOFS_URL: z.optional(z.string().url()),
|
||||||
VEGA_ENV: z.nativeEnum(Networks),
|
VEGA_ENV: z.nativeEnum(Networks),
|
||||||
VEGA_EXPLORER_URL: z.optional(z.string()),
|
VEGA_EXPLORER_URL: z.optional(z.string()),
|
||||||
VEGA_TOKEN_URL: z.optional(z.string()),
|
VEGA_TOKEN_URL: z.optional(z.string()),
|
||||||
|
@ -1,3 +1,34 @@
|
|||||||
|
fragment DataSource on DataSourceDefinition {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceDefinitionExternal {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceSpecConfiguration {
|
||||||
|
signers {
|
||||||
|
signer {
|
||||||
|
... on PubKey {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
... on ETHAddress {
|
||||||
|
address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on DataSourceDefinitionInternal {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceSpecConfigurationTime {
|
||||||
|
conditions {
|
||||||
|
operator
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query MarketInfo($marketId: ID!) {
|
query MarketInfo($marketId: ID!) {
|
||||||
market(id: $marketId) {
|
market(id: $marketId) {
|
||||||
id
|
id
|
||||||
@ -79,9 +110,15 @@ query MarketInfo($marketId: ID!) {
|
|||||||
}
|
}
|
||||||
dataSourceSpecForSettlementData {
|
dataSourceSpecForSettlementData {
|
||||||
id
|
id
|
||||||
|
data {
|
||||||
|
...DataSource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataSourceSpecForTradingTermination {
|
dataSourceSpecForTradingTermination {
|
||||||
id
|
id
|
||||||
|
data {
|
||||||
|
...DataSource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataSourceSpecBinding {
|
dataSourceSpecBinding {
|
||||||
settlementDataProperty
|
settlementDataProperty
|
||||||
|
@ -3,14 +3,47 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
|
export type DataSourceFragment = { __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?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } };
|
||||||
|
|
||||||
export type MarketInfoQueryVariables = Types.Exact<{
|
export type MarketInfoQueryVariables = Types.Exact<{
|
||||||
marketId: Types.Scalars['ID'];
|
marketId: Types.Scalars['ID'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
|
export type MarketInfoQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradingMode: Types.MarketTradingMode, lpPriceRange: string, proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null, marketTimestamps: { __typename?: 'MarketTimestamps', open: any, close: any }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string } } } | null> | null } | null, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, riskFactors?: { __typename?: 'RiskFactor', market: string, short: string, long: string } | null, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', id: string, name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, symbol: string, name: string, decimals: number }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string, 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?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null } | null> } } } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null } } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const DataSourceFragmentDoc = gql`
|
||||||
|
fragment DataSource on DataSourceDefinition {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceDefinitionExternal {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceSpecConfiguration {
|
||||||
|
signers {
|
||||||
|
signer {
|
||||||
|
... on PubKey {
|
||||||
|
key
|
||||||
|
}
|
||||||
|
... on ETHAddress {
|
||||||
|
address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
... on DataSourceDefinitionInternal {
|
||||||
|
sourceType {
|
||||||
|
... on DataSourceSpecConfigurationTime {
|
||||||
|
conditions {
|
||||||
|
operator
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const MarketInfoDocument = gql`
|
export const MarketInfoDocument = gql`
|
||||||
query MarketInfo($marketId: ID!) {
|
query MarketInfo($marketId: ID!) {
|
||||||
market(id: $marketId) {
|
market(id: $marketId) {
|
||||||
@ -93,9 +126,15 @@ export const MarketInfoDocument = gql`
|
|||||||
}
|
}
|
||||||
dataSourceSpecForSettlementData {
|
dataSourceSpecForSettlementData {
|
||||||
id
|
id
|
||||||
|
data {
|
||||||
|
...DataSource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataSourceSpecForTradingTermination {
|
dataSourceSpecForTradingTermination {
|
||||||
id
|
id
|
||||||
|
data {
|
||||||
|
...DataSource
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dataSourceSpecBinding {
|
dataSourceSpecBinding {
|
||||||
settlementDataProperty
|
settlementDataProperty
|
||||||
@ -131,7 +170,7 @@ export const MarketInfoDocument = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${DataSourceFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useMarketInfoQuery__
|
* __useMarketInfoQuery__
|
||||||
|
@ -85,7 +85,7 @@ export const MarketInfoContainer = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Info = ({ market, onSelect }: InfoProps) => {
|
export const Info = ({ market, onSelect }: InfoProps) => {
|
||||||
const { VEGA_TOKEN_URL, VEGA_EXPLORER_URL } = useEnvironment();
|
const { VEGA_TOKEN_URL } = useEnvironment();
|
||||||
const headerClassName = 'uppercase text-lg';
|
const headerClassName = 'uppercase text-lg';
|
||||||
|
|
||||||
if (!market) return null;
|
if (!market) return null;
|
||||||
@ -124,6 +124,10 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
title: t('Instrument'),
|
title: t('Instrument'),
|
||||||
content: <InstrumentInfoPanel market={market} />,
|
content: <InstrumentInfoPanel market={market} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Oracle'),
|
||||||
|
content: <OracleInfoPanel market={market} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('Settlement asset'),
|
title: t('Settlement asset'),
|
||||||
content: <SettlementAssetInfoPanel market={market} />,
|
content: <SettlementAssetInfoPanel market={market} />,
|
||||||
@ -177,23 +181,6 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
title: t('Liquidity price range'),
|
title: t('Liquidity price range'),
|
||||||
content: <LiquidityPriceRangeInfoPanel market={market} />,
|
content: <LiquidityPriceRangeInfoPanel market={market} />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t('Oracle'),
|
|
||||||
content: (
|
|
||||||
<OracleInfoPanel market={market}>
|
|
||||||
<ExternalLink
|
|
||||||
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData.id}`}
|
|
||||||
>
|
|
||||||
{t('View settlement data oracle specification')}
|
|
||||||
</ExternalLink>
|
|
||||||
<ExternalLink
|
|
||||||
href={`${VEGA_EXPLORER_URL}/oracles#${market.tradableInstrument.instrument.product.dataSourceSpecForTradingTermination.id}`}
|
|
||||||
>
|
|
||||||
{t('View termination oracle specification')}
|
|
||||||
</ExternalLink>
|
|
||||||
</OracleInfoPanel>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const marketGovPanels = [
|
const marketGovPanels = [
|
||||||
@ -236,17 +223,17 @@ export const Info = ({ market, onSelect }: InfoProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<p className={headerClassName}>{t('Market data')}</p>
|
<h3 className={headerClassName}>{t('Market data')}</h3>
|
||||||
<Accordion panels={marketDataPanels} />
|
<Accordion panels={marketDataPanels} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<MarketProposalNotification marketId={market.id} />
|
<MarketProposalNotification marketId={market.id} />
|
||||||
<p className={headerClassName}>{t('Market specification')}</p>
|
<h3 className={headerClassName}>{t('Market specification')}</h3>
|
||||||
<Accordion panels={marketSpecPanels} />
|
<Accordion panels={marketSpecPanels} />
|
||||||
</div>
|
</div>
|
||||||
{VEGA_TOKEN_URL && market.proposal?.id && (
|
{VEGA_TOKEN_URL && market.proposal?.id && (
|
||||||
<div>
|
<div>
|
||||||
<p className={headerClassName}>{t('Market governance')}</p>
|
<h3 className={headerClassName}>{t('Market governance')}</h3>
|
||||||
<Accordion panels={marketGovPanels} />
|
<Accordion panels={marketGovPanels} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -0,0 +1,188 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import {
|
||||||
|
ConditionOperator,
|
||||||
|
ConditionOperatorMapping,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
import { DataSourceProof } from './market-info-panels';
|
||||||
|
|
||||||
|
describe('DataSourceProof', () => {
|
||||||
|
const ORACLE_PUBKEY =
|
||||||
|
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f';
|
||||||
|
it('renders correct proof for external data sources', () => {
|
||||||
|
const props = {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal' as const,
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration' as const,
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer' as const,
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey' as const,
|
||||||
|
key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
name: 'Another oracle',
|
||||||
|
url: 'https://zombo.com',
|
||||||
|
description_markdown:
|
||||||
|
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||||
|
oracle: {
|
||||||
|
status: 'GOOD' as const,
|
||||||
|
status_reason: '',
|
||||||
|
first_verified: '2022-01-01T00:00:00.000Z',
|
||||||
|
last_verified: '2022-12-31T00:00:00.000Z',
|
||||||
|
type: 'public_key' as const,
|
||||||
|
public_key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
proofs: [
|
||||||
|
{
|
||||||
|
format: 'signed_message' as const,
|
||||||
|
available: true,
|
||||||
|
type: 'public_key' as const,
|
||||||
|
public_key: ORACLE_PUBKEY,
|
||||||
|
message: 'SOMEHEX',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/PubKey-${ORACLE_PUBKEY}.toml`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'termination' as const,
|
||||||
|
};
|
||||||
|
render(<DataSourceProof {...props} />);
|
||||||
|
expect(screen.getByRole('link')).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
props.providers[0].github_link
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders message if there are no providers', () => {
|
||||||
|
const props = {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal' as const,
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration' as const,
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer' as const,
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey' as const,
|
||||||
|
key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [],
|
||||||
|
type: 'termination' as const,
|
||||||
|
};
|
||||||
|
render(<DataSourceProof {...props} />);
|
||||||
|
expect(
|
||||||
|
screen.getByText('No oracle proof for termination')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders message if there are no matching proofs', () => {
|
||||||
|
const props = {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal' as const,
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration' as const,
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer' as const,
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey' as const,
|
||||||
|
key: ORACLE_PUBKEY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
name: 'Another oracle',
|
||||||
|
url: 'https://zombo.com',
|
||||||
|
description_markdown:
|
||||||
|
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||||
|
oracle: {
|
||||||
|
status: 'GOOD' as const,
|
||||||
|
status_reason: '',
|
||||||
|
first_verified: '2022-01-01T00:00:00.000Z',
|
||||||
|
last_verified: '2022-12-31T00:00:00.000Z',
|
||||||
|
type: 'public_key' as const,
|
||||||
|
public_key: 'not-the-pubkey',
|
||||||
|
},
|
||||||
|
proofs: [
|
||||||
|
{
|
||||||
|
format: 'signed_message' as const,
|
||||||
|
available: true,
|
||||||
|
type: 'public_key' as const,
|
||||||
|
public_key: 'not-the-pubkey',
|
||||||
|
message: 'SOMEHEX',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/PubKey-${ORACLE_PUBKEY}.toml`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'settlementData' as const,
|
||||||
|
};
|
||||||
|
render(<DataSourceProof {...props} />);
|
||||||
|
expect(
|
||||||
|
screen.getByText('No oracle proof for settlement data')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders message if no data source on market', () => {
|
||||||
|
const props = {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'Invalid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [],
|
||||||
|
type: 'termination' as const,
|
||||||
|
};
|
||||||
|
// @ts-ignore types are invalid
|
||||||
|
render(<DataSourceProof {...props} />);
|
||||||
|
expect(screen.getByText('Invalid data source')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders conditions for internal data sources', () => {
|
||||||
|
const condition = {
|
||||||
|
__typename: 'Condition' as const,
|
||||||
|
operator: ConditionOperator.OPERATOR_GREATER_THAN,
|
||||||
|
value: '100',
|
||||||
|
};
|
||||||
|
const props = {
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionInternal' as const,
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfigurationTime' as const,
|
||||||
|
conditions: [condition],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [],
|
||||||
|
type: 'termination' as const,
|
||||||
|
};
|
||||||
|
render(<DataSourceProof {...props} />);
|
||||||
|
expect(screen.getByText('Internal conditions')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
`${ConditionOperatorMapping[condition.operator]} ${condition.value}`
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -6,7 +6,7 @@ import {
|
|||||||
calcCandleVolume,
|
calcCandleVolume,
|
||||||
totalFeesPercentage,
|
totalFeesPercentage,
|
||||||
} from '@vegaprotocol/market-list';
|
} from '@vegaprotocol/market-list';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
@ -21,7 +21,12 @@ import type {
|
|||||||
MarketInfoWithDataAndCandles,
|
MarketInfoWithDataAndCandles,
|
||||||
} from './market-info-data-provider';
|
} from './market-info-data-provider';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
|
||||||
|
import { ConditionOperatorMapping } from '@vegaprotocol/types';
|
||||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||||
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import type { Provider } from '@vegaprotocol/oracles';
|
||||||
|
import { useOracleProofs } from '@vegaprotocol/oracles';
|
||||||
|
|
||||||
type PanelProps = Pick<
|
type PanelProps = Pick<
|
||||||
ComponentProps<typeof MarketInfoTable>,
|
ComponentProps<typeof MarketInfoTable>,
|
||||||
@ -399,7 +404,7 @@ export const LiquidityPriceRangeInfoPanel = ({
|
|||||||
market.decimalPlaces
|
market.decimalPlaces
|
||||||
)} ${quoteUnit}`,
|
)} ${quoteUnit}`,
|
||||||
}}
|
}}
|
||||||
></MarketInfoTable>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -408,9 +413,156 @@ export const LiquidityPriceRangeInfoPanel = ({
|
|||||||
export const OracleInfoPanel = ({
|
export const OracleInfoPanel = ({
|
||||||
market,
|
market,
|
||||||
...props
|
...props
|
||||||
}: MarketInfoProps & PanelProps) => (
|
}: MarketInfoProps & PanelProps) => {
|
||||||
<MarketInfoTable
|
const product = market.tradableInstrument.instrument.product;
|
||||||
data={market.tradableInstrument.instrument.product.dataSourceSpecBinding}
|
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
|
||||||
{...props}
|
const { data } = useOracleProofs(ORACLE_PROOFS_URL);
|
||||||
/>
|
return (
|
||||||
);
|
<MarketInfoTable data={product.dataSourceSpecBinding} {...props}>
|
||||||
|
<div
|
||||||
|
className="flex flex-col gap-2 mt-4"
|
||||||
|
data-testid="oracle-proof-links"
|
||||||
|
>
|
||||||
|
<DataSourceProof
|
||||||
|
data={product.dataSourceSpecForSettlementData.data}
|
||||||
|
providers={data}
|
||||||
|
type="settlementData"
|
||||||
|
/>
|
||||||
|
<DataSourceProof
|
||||||
|
data={product.dataSourceSpecForTradingTermination.data}
|
||||||
|
providers={data}
|
||||||
|
type="termination"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2" data-testid="oracle-spec-links">
|
||||||
|
<ExternalLink
|
||||||
|
href={`${VEGA_EXPLORER_URL}/oracles#${product.dataSourceSpecForSettlementData.id}`}
|
||||||
|
>
|
||||||
|
{t('View settlement data specification')}
|
||||||
|
</ExternalLink>
|
||||||
|
<ExternalLink
|
||||||
|
href={`${VEGA_EXPLORER_URL}/oracles#${product.dataSourceSpecForTradingTermination.id}`}
|
||||||
|
>
|
||||||
|
{t('View termination specification')}
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
</MarketInfoTable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataSourceProof = ({
|
||||||
|
data,
|
||||||
|
providers,
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
data: DataSourceDefinition;
|
||||||
|
providers: Provider[] | undefined;
|
||||||
|
type: 'settlementData' | 'termination';
|
||||||
|
}) => {
|
||||||
|
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||||
|
const signers = data.sourceType.sourceType.signers || [];
|
||||||
|
|
||||||
|
if (!providers?.length) {
|
||||||
|
return <NoOracleProof type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{signers.map(({ signer }, i) => {
|
||||||
|
return (
|
||||||
|
<OracleLink
|
||||||
|
key={i}
|
||||||
|
providers={providers}
|
||||||
|
signer={signer}
|
||||||
|
type={type}
|
||||||
|
index={i}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.sourceType.__typename === 'DataSourceDefinitionInternal') {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{t('Internal conditions')}</h3>
|
||||||
|
{data.sourceType.sourceType.conditions.map((condition, i) => {
|
||||||
|
if (!condition) return null;
|
||||||
|
return (
|
||||||
|
<p key={i}>
|
||||||
|
{ConditionOperatorMapping[condition.operator]} {condition.value}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>{t('Invalid data source')}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OracleLink = ({
|
||||||
|
providers,
|
||||||
|
signer,
|
||||||
|
type,
|
||||||
|
index,
|
||||||
|
}: {
|
||||||
|
providers: Provider[];
|
||||||
|
signer: SignerKind;
|
||||||
|
type: 'settlementData' | 'termination';
|
||||||
|
index: number;
|
||||||
|
}) => {
|
||||||
|
const text =
|
||||||
|
type === 'settlementData'
|
||||||
|
? t('View settlement oracle details')
|
||||||
|
: t('View termination oracle details');
|
||||||
|
const textWithCount = index > 0 ? `${text} (${index + 1})` : text;
|
||||||
|
|
||||||
|
const provider = providers.find((p) => {
|
||||||
|
if (signer.__typename === 'PubKey') {
|
||||||
|
if (
|
||||||
|
p.oracle.type === 'public_key' &&
|
||||||
|
p.oracle.public_key === signer.key
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signer.__typename === 'ETHAddress') {
|
||||||
|
if (
|
||||||
|
p.oracle.type === 'eth_address' &&
|
||||||
|
p.oracle.eth_address === signer.address
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return <NoOracleProof type={type} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
<ExternalLink href={provider.github_link}>{textWithCount}</ExternalLink>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NoOracleProof = ({
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
type: 'settlementData' | 'termination';
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
'No oracle proof for %s',
|
||||||
|
type === 'settlementData' ? 'settlement data' : 'termination'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -133,10 +133,44 @@ export const marketInfoQuery = (
|
|||||||
dataSourceSpecForSettlementData: {
|
dataSourceSpecForSettlementData: {
|
||||||
__typename: 'DataSourceSpec',
|
__typename: 'DataSourceSpec',
|
||||||
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
|
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dataSourceSpecForTradingTermination: {
|
dataSourceSpecForTradingTermination: {
|
||||||
__typename: 'DataSourceSpec',
|
__typename: 'DataSourceSpec',
|
||||||
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
|
id: 'f028fe5ea7de3890962a05a7163fdde562629af649ed81b8c8902fafb6eef04f',
|
||||||
|
data: {
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dataSourceSpecBinding: {
|
dataSourceSpecBinding: {
|
||||||
__typename: 'DataSourceSpecToFutureBinding',
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
12
libs/oracles/.babelrc
Normal file
12
libs/oracles/.babelrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nrwl/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
18
libs/oracles/.eslintrc.json
Normal file
18
libs/oracles/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
libs/oracles/README.md
Normal file
7
libs/oracles/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# oracles
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test oracles` to execute the unit tests via [Jest](https://jestjs.io).
|
10
libs/oracles/jest.config.ts
Normal file
10
libs/oracles/jest.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
export default {
|
||||||
|
displayName: 'oracles',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/libs/oracles',
|
||||||
|
};
|
4
libs/oracles/package.json
Normal file
4
libs/oracles/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@vegaprotocol/oracles",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
43
libs/oracles/project.json
Normal file
43
libs/oracles/project.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/oracles/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nrwl/web:rollup",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/libs/oracles",
|
||||||
|
"tsConfig": "libs/oracles/tsconfig.lib.json",
|
||||||
|
"project": "libs/oracles/package.json",
|
||||||
|
"entryFile": "libs/oracles/src/index.ts",
|
||||||
|
"external": ["react/jsx-runtime"],
|
||||||
|
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||||
|
"compiler": "babel",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "libs/oracles/README.md",
|
||||||
|
"input": ".",
|
||||||
|
"output": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/oracles/**/*.{ts,tsx,js,jsx}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"outputs": ["coverage/libs/oracles"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/oracles/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
libs/oracles/src/index.ts
Normal file
2
libs/oracles/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './lib/oracle-schema';
|
||||||
|
export * from './lib/use-oracle-proofs';
|
74
libs/oracles/src/lib/oracle-schema.ts
Normal file
74
libs/oracles/src/lib/oracle-schema.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
export type Provider = z.infer<typeof providerSchema>;
|
||||||
|
export type Oracle = z.infer<typeof oracleSchema>;
|
||||||
|
export type Proof = z.infer<typeof proofSchema>;
|
||||||
|
export type Status = z.infer<typeof statusSchema>;
|
||||||
|
|
||||||
|
const statusSchema = z.enum([
|
||||||
|
'UNKNOWN',
|
||||||
|
'GOOD',
|
||||||
|
'SUSPICIOUS',
|
||||||
|
'MALICIOUS',
|
||||||
|
'RETIRED',
|
||||||
|
'COMPROMISED',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const baseProofSchema = z.object({
|
||||||
|
format: z.enum(['url', 'signed_message']),
|
||||||
|
available: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const proofSchema = z.discriminatedUnion('type', [
|
||||||
|
baseProofSchema.extend({
|
||||||
|
type: z.literal('public_key'),
|
||||||
|
public_key: z.string().min(64),
|
||||||
|
message: z.string().min(1),
|
||||||
|
}),
|
||||||
|
baseProofSchema.extend({
|
||||||
|
type: z.literal('eth_address'),
|
||||||
|
eth_address: z.string().min(42),
|
||||||
|
message: z.string().min(1),
|
||||||
|
}),
|
||||||
|
baseProofSchema.extend({
|
||||||
|
type: z.literal('web'),
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
baseProofSchema.extend({
|
||||||
|
type: z.literal('github'),
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
baseProofSchema.extend({
|
||||||
|
type: z.literal('twitter'),
|
||||||
|
url: z.string().url(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const baseOracleSchema = z.object({
|
||||||
|
status: statusSchema,
|
||||||
|
status_reason: z.string(),
|
||||||
|
first_verified: z.string(),
|
||||||
|
last_verified: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const oracleSchema = z.discriminatedUnion('type', [
|
||||||
|
baseOracleSchema.extend({
|
||||||
|
type: z.literal('public_key'),
|
||||||
|
public_key: z.string().min(64),
|
||||||
|
}),
|
||||||
|
baseOracleSchema.extend({
|
||||||
|
type: z.literal('eth_address'),
|
||||||
|
eth_address: z.string().min(42),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const providerSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
url: z.string().url(),
|
||||||
|
description_markdown: z.string(),
|
||||||
|
oracle: oracleSchema,
|
||||||
|
proofs: z.array(proofSchema),
|
||||||
|
github_link: z.string().url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const providersSchema = z.array(providerSchema);
|
135
libs/oracles/src/lib/use-oracle-proofs.spec.ts
Normal file
135
libs/oracles/src/lib/use-oracle-proofs.spec.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
|
import type { Provider } from './oracle-schema';
|
||||||
|
import { useOracleProofs, cache, invalidateCache } from './use-oracle-proofs';
|
||||||
|
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
const mockFetch = global.fetch as jest.Mock;
|
||||||
|
|
||||||
|
const createOracleData = (): Provider[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'Another oracle',
|
||||||
|
url: 'https://zombo.com',
|
||||||
|
description_markdown:
|
||||||
|
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
|
||||||
|
oracle: {
|
||||||
|
status: 'GOOD',
|
||||||
|
status_reason: '',
|
||||||
|
first_verified: '2022-01-01T00:00:00.000Z',
|
||||||
|
last_verified: '2022-12-31T00:00:00.000Z',
|
||||||
|
type: 'public_key',
|
||||||
|
public_key:
|
||||||
|
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
},
|
||||||
|
proofs: [
|
||||||
|
{
|
||||||
|
format: 'url',
|
||||||
|
available: true,
|
||||||
|
type: 'twitter',
|
||||||
|
url: 'https://twitter.com/vegaprotocol/status/956833487230730241',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: 'signed_message',
|
||||||
|
available: true,
|
||||||
|
type: 'public_key',
|
||||||
|
public_key:
|
||||||
|
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
message: 'SOMEHEX',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
github_link:
|
||||||
|
'https://github.com/vegaprotocol/well-known/blob/feat/add-process-script/oracle-providers/PubKey-69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f.toml',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useOracleProofs', () => {
|
||||||
|
const url = 'https://foo.bar.com';
|
||||||
|
const setup = (data: Provider[]) => {
|
||||||
|
mockFetch.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve(data),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return renderHook(() => useOracleProofs(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFetch.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetches and caches', () => {
|
||||||
|
it('fetches oracle data', async () => {
|
||||||
|
const data = createOracleData();
|
||||||
|
const { result } = setup(data);
|
||||||
|
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data).toEqual(data);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// check result was cached
|
||||||
|
expect(cache).toEqual({ [url]: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses cached value if present', () => {
|
||||||
|
const data = createOracleData();
|
||||||
|
const { result } = setup(data);
|
||||||
|
|
||||||
|
expect(result.current.data).toEqual(data);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles invalid payload', async () => {
|
||||||
|
invalidateCache();
|
||||||
|
// @ts-ignore enforce invalid result
|
||||||
|
const { result } = setup([{ invalid: 'result' }]);
|
||||||
|
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.error instanceof Error).toBe(true);
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles failed to fetch', async () => {
|
||||||
|
invalidateCache();
|
||||||
|
|
||||||
|
mockFetch.mockImplementation(() => {
|
||||||
|
return Promise.reject(new Error('failed to fetch'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useOracleProofs(url));
|
||||||
|
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.error).toBe(undefined);
|
||||||
|
expect(result.current.loading).toBe(true);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.data).toBe(undefined);
|
||||||
|
expect(result.current.error instanceof Error).toBe(true);
|
||||||
|
expect(result.current.error).toEqual(new Error('failed to fetch'));
|
||||||
|
expect(result.current.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
64
libs/oracles/src/lib/use-oracle-proofs.ts
Normal file
64
libs/oracles/src/lib/use-oracle-proofs.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import type { Provider } from './oracle-schema';
|
||||||
|
import { providersSchema } from './oracle-schema';
|
||||||
|
|
||||||
|
export let cache: {
|
||||||
|
[url: string]: Provider[];
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
export const useOracleProofs = (url?: string) => {
|
||||||
|
const [data, setData] = useState<Provider[] | undefined>(() =>
|
||||||
|
url ? cache[url] : undefined
|
||||||
|
);
|
||||||
|
const [status, setStatus] = useState<'idle' | 'loading' | 'done'>('idle');
|
||||||
|
const [error, setError] = useState<Error>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let ignore = false;
|
||||||
|
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
try {
|
||||||
|
if (cache[url]) {
|
||||||
|
setData(cache[url]);
|
||||||
|
} else {
|
||||||
|
setStatus('loading');
|
||||||
|
const res = await fetch(url);
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (ignore) return;
|
||||||
|
|
||||||
|
const result = providersSchema.parse(json);
|
||||||
|
|
||||||
|
cache[url] = result;
|
||||||
|
setData(result);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
setError(err);
|
||||||
|
} else {
|
||||||
|
setError(new Error('Something went wrong'));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setStatus('done');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
};
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading: status === 'loading',
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const invalidateCache = () => {
|
||||||
|
cache = {};
|
||||||
|
};
|
25
libs/oracles/tsconfig.json
Normal file
25
libs/oracles/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
23
libs/oracles/tsconfig.lib.json
Normal file
23
libs/oracles/tsconfig.lib.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||||
|
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.test.jsx"
|
||||||
|
],
|
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
20
libs/oracles/tsconfig.spec.json
Normal file
20
libs/oracles/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ConditionOperator } from './__generated__/types';
|
||||||
import type {
|
import type {
|
||||||
AccountType,
|
AccountType,
|
||||||
AuctionTrigger,
|
AuctionTrigger,
|
||||||
@ -458,3 +459,11 @@ export const PositionStatusMapping: {
|
|||||||
POSITION_STATUS_ORDERS_CLOSED: 'Maintained by network',
|
POSITION_STATUS_ORDERS_CLOSED: 'Maintained by network',
|
||||||
POSITION_STATUS_UNSPECIFIED: 'Normal',
|
POSITION_STATUS_UNSPECIFIED: 'Normal',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ConditionOperatorMapping: { [C in ConditionOperator]: string } = {
|
||||||
|
OPERATOR_EQUALS: 'Equals',
|
||||||
|
OPERATOR_GREATER_THAN: 'Greater than',
|
||||||
|
OPERATOR_GREATER_THAN_OR_EQUAL: 'Greater than or equal to',
|
||||||
|
OPERATOR_LESS_THAN: 'Less than',
|
||||||
|
OPERATOR_LESS_THAN_OR_EQUAL: 'Less than or equal to',
|
||||||
|
};
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"@vegaprotocol/mock": ["libs/cypress/mock.ts"],
|
"@vegaprotocol/mock": ["libs/cypress/mock.ts"],
|
||||||
"@vegaprotocol/network-info": ["libs/network-info/src/index.ts"],
|
"@vegaprotocol/network-info": ["libs/network-info/src/index.ts"],
|
||||||
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
||||||
|
"@vegaprotocol/oracles": ["libs/oracles/src/index.ts"],
|
||||||
"@vegaprotocol/orders": ["libs/orders/src/index.ts"],
|
"@vegaprotocol/orders": ["libs/orders/src/index.ts"],
|
||||||
"@vegaprotocol/positions": ["libs/positions/src/index.ts"],
|
"@vegaprotocol/positions": ["libs/positions/src/index.ts"],
|
||||||
"@vegaprotocol/proposals": ["libs/proposals/src/index.ts"],
|
"@vegaprotocol/proposals": ["libs/proposals/src/index.ts"],
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"multisig-signer": "apps/multisig-signer",
|
"multisig-signer": "apps/multisig-signer",
|
||||||
"network-info": "libs/network-info",
|
"network-info": "libs/network-info",
|
||||||
"network-stats": "libs/network-stats",
|
"network-stats": "libs/network-stats",
|
||||||
|
"oracles": "libs/oracles",
|
||||||
"orders": "libs/orders",
|
"orders": "libs/orders",
|
||||||
"positions": "libs/positions",
|
"positions": "libs/positions",
|
||||||
"proposals": "libs/proposals",
|
"proposals": "libs/proposals",
|
||||||
|
Loading…
Reference in New Issue
Block a user