Compare commits

..
Author SHA1 Message Date
Matthew Russell 308bd406a6 fix: accomodate type changes for libs/liquidity 2022-09-12 22:25:14 -07:00
Matthew Russell 4ffff8a9c7 fix: query type generation and type errors 2022-09-12 22:20:38 -07:00
Matthew Russell ac4b48610c chore: fix all queries and regen types 2022-09-12 22:20:38 -07:00
Matthew Russell 2b28b29820 fix: tooltip should be on data not label 2022-09-12 22:19:20 -07:00
Matthew Russell bf5e428460 fix: unused and superfluous classes, define border color in one place 2022-09-12 14:11:28 -07:00
Matthew Russell 73359e9ff7 fix: border too thick, use grid head component 2022-09-12 12:25:40 -07:00
mattrussell36 f34edf1862 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-12 18:04:57 +00:00
m.ray d8512afb2b fix: #1297 Order submission expiresAt is only available when the time in force is of type GTT (#1308)
* fix: #1297 Order submission expires at is only available when the time in force is of type GTT

* fix: add expires at visible in edit dialog

* fix:#1297 add GTC and GTT tests for expires at
2022-09-12 14:28:07 +01:00
PeteWilliams 549e0e004d Chore/design updates (#1305)
* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* chore: design updates

* fix: fix lint tests

* chore: fixes ag grid invisible header seperators

* fix: fixing conflicts

* fix: fixing conflicts

* chore: fix header spacing

* chore: fix dropdown hover colour

* fix: formatting change to pass test
2022-09-12 14:05:39 +01:00
Joe Tsang 8b08e48840 chore: attempt to fix home tests (#1311)
* chore: attempt to fix home tests

* chore: revert timeout increase

* chore: visit homepage in hook
2022-09-12 13:56:01 +01:00
mattrussell36 846f9114d8 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-12 12:05:17 +00:00
Radosław Szpiech a9216c12a3 chore: env changed to custom (#1307) 2022-09-12 12:22:49 +02:00
d85c5d7c1f chore: LP tooltips & move market-info lib (#1267)
* feat(#473): add positions metrics data provider

* feat(#473) add positions stats

* feat(#473) add positions stats

* feat(#473): add positions stats

* feat(#473): add positions stats

* feat(#473): position metrics, test and refactoring

* feat(#473): add unit tests to positions table

* feat(#473): fix spelling, order positions by updated at desc

* feat(#473): protect from division by 0

* feat(#473): fix trading positions e2e tests

* feat(#473): fix e2e data mocks

* feat(#473): post code review clean up

* feat(#993): dependencies handling in data provider

* feat(#993): fix e2e tests data mocks

* feat(#993): remove position metrics mocks, add market data market id

* feat: #994 add price monitoring bounds and candles update interface

* fix: move best bid price to diff section

* feat(#993): add missing mocks, fix combine function

* fix: add insurance pool and calc volume 24h

* feat: display some oracle min info, asset id, insurance pool, move open interest and 24hVol

* fix: add  market-info.cy.ts case

* fix: remove # from numbered price monitoring settings

* fix: add insurance pool test

* fix: format 24hvol

* feat(#993): set loading initially to true, add unit tests

* feat(#993): cleanup, add comments

* feat(#993): remove undefined from client type

* fix: remove indicativeVolume and oracleSpecBinding from market info

* feat(#993): cosmetic changes

* fix: add oracleSpecBinding back

* Update libs/deal-ticket/src/components/info-market.tsx

Co-authored-by: botond <105208209+notbot00@users.noreply.github.com>

* feat: add initial queries

* fix: memo yesterday's timestamp

* fix: add back info

* fix: update query

* fix: add view full oracle details link and update mappings

* fix: regen code, make link reactnode, fix index.ts

* feat: add liquidity lib, refactor info market

* fix: remove liquidity query from deal-ticket

* feat:(#993): pass informaton about update callback cause

* fix: small ui tweaks

* fix: display in grid

* feat: navigate to oracle by termination id

* feat: #491 add use liquidity provision merging

* fix: remove logs, add extra check on my liquidity provision

* fix: type number trivially inferred from a number literal, remove type annotation

* fix: cypress tests and formatting for market info

* Update libs/deal-ticket/src/components/market-info/info-market.tsx

* fix: use position decimal places for stake and market value proxy

* fix: #491 use size/position decimal places for obligation, supplied and commitment amount

* fix: add size component and use decimal places

* fix: update readme liquidity

* fix: #491 add correct asset decimal formatters

* Update libs/deal-ticket/src/components/market-info/tooltip-mapping.tsx

Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>

* fix: make link instead of button to open liquidity

* fix: #491 add liquidity page, link to trading mode tooltip, tabs hidden or choose active

* fix: remove LP dialog, use only link to page

* fix: add market id in LP view

* fix: follow trade grid design

* fix: add one line tabs , remove link styling, remove any, add value formatters

* fix: remove falsy check LP undefined

* fix: keep date formatter in LP table

* fix: add generic type market info, hooks in body function

* fix: revert number formatters

* fix: use one param only in network params query

* fix:  use network param in web3 lib

* fix: move lp container to trading app

* fix: remove resizable panel

* feat: add header component, remove isEstimate

* chore: remove unnecessary type cast

* fix: fix build with children map clone element

* chore: lint

* fix: move use network params to react helpers

* fix: add const for LP tabs

* fix: fix formatting on LP page

* fix: only show tilde for liquidity monitoring auction end date

* fix: market id being rendered twice in market info

* chore: fix lint

* fix: types for generate withdraw form query

* chore: fix intermittent failing withdrawal test

* Update libs/deal-ticket/src/components/market-info/info-market.tsx

* fix: route to LP and update selected market

* fix: add market-info lib

* fix: add tooltips to header stat

* fix: add overflow-y on div within auto sizer

* fix: fix merge conflicts

* fix: size and dialog

* fix: format target stake and supplied stake

* fix: add tooltips ag-grid and fix decimals

* fix: overflow on both axes auto-sizer

* fix: remove symbol, add to commitment

* fix: update tooltips and headers after demo

* fix: network switcher background

* fix: remove any from market info

* fix: integration test

* fix: remove e2e test for risk factors for now until grpc error sorted

* fix: get liquidity panel  by test-id

* fix: fix market-info.cy.ts test

* fix: liquidity test

* f
ix: fix link data test id

Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
Co-authored-by: botond <105208209+notbot00@users.noreply.github.com>
Co-authored-by: candida-d <62548908+candida-d@users.noreply.github.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Joe <joe@vega.xyz>
2022-09-12 11:07:29 +02:00
Joe Tsang 7675fe0ab0 test: add test for filtering assets not enabled (#1303)
* test: add test for filtering assets not enabled

* chore: fix lint

* chore: load homepage before test
2022-09-12 09:22:34 +02:00
mattrussell36 bd51564207 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-12 06:28:44 +00:00
mattrussell36 fac08bc831 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-12 00:10:11 +00:00
mattrussell36 a34ea77488 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-11 18:04:08 +00:00
mattrussell36 e50afc64ff chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-11 12:04:30 +00:00
mattrussell36 9f4bf97465 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-11 06:06:37 +00:00
mattrussell36 dc59f56a6f chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-11 00:10:25 +00:00
mattrussell36 8a9372c88a chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-10 18:05:25 +00:00
mattrussell36 8563625079 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-10 12:05:08 +00:00
mattrussell36 946ee9423b chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-10 06:08:32 +00:00
mattrussell36 e3aa1e40c8 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-10 00:11:11 +00:00
mattrussell36 179d34ff40 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-09 18:04:28 +00:00
botond 7613a9fa9f Feat/migrate libs (#1266)
* feat: add new generator with config

* feat: split off gql queries to separate files for the new generator

* fix: delete dummy schema

* feat: add generated queries with new codegen

* fix: regenerate from scratch and remove duplicates

* fix: libs and app gen folders

* fix: remove more duplicate queries

* fix: add generated files to be ignored by the formatter

* fix: format

* feat: migrate assets to use new code generator

* fix: generated imports

* fix: lint

* fix: accounts export

* fix: more imports

* fix: add type alias for new type system and regenerate

* fix: format

* fix: export

* fix: format

* fix: delete leftover file from merge

* chore: migrate candles-chart lib

* fix: remove redundant asset files

* chore: migrate accounts

* chore: update glob

* chore: add cleanup to fix-imports to support type migrations for now

* fix: imports

* fix: update market types

* fix: type import paths

* fix: format

* fix: import

* fix: types in e2e tests

* fix: format

* fix: lint
2022-09-09 16:14:48 +01:00
mattrussell36 1beaad5759 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-09 12:04:51 +00:00
Joe Tsang 8958db0612 Test/626 oracle info (#1294)
* test: update market info tests

* test: update withdrawal test to include new info

* chore: re add withdrawal mock

* chore: wait for mock

* chore: change cypress browser to electron

* chore: resolved pr comments
2022-09-09 10:36:53 +01:00
mattrussell36 4db77a8e8a chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-09 06:13:33 +00:00
mattrussell36 097cf5da94 chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-09 00:10:31 +00:00
mattrussell36 3f1ea6b83d chore: update tranches
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-09-08 18:04:18 +00:00
macqbatandmaciek 93f41d429f feat: [console-lite] - fast fix small css on market selector (#1290)
Co-authored-by: maciek <maciek@vegaprotocol.io>
2022-09-08 15:55:17 +02:00
413 changed files with 6045 additions and 7934 deletions
@@ -133,7 +133,7 @@ jobs:
working-directory: frontend-monorepo
- name: Run Cypress tests
run: yarn nx run ${{ github.event.inputs.project }}:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run ${{ github.event.inputs.project }}:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
@@ -117,7 +117,7 @@ jobs:
working-directory: frontend-monorepo
- name: Run Cypress tests
run: yarn nx run-many --target=e2e --all --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: yarn nx run-many --target=e2e --all --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
+4 -4
View File
@@ -51,7 +51,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: v0.2.3
ref: main
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
@@ -120,17 +120,17 @@ jobs:
######
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
uses: nrwl/nx-set-shas@v2
with:
working-directory: frontend-monorepo
main-branch-name: main
main-branch-name: master
- name: Install root dependencies
run: yarn install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx affected:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome
run: npx nx affected:e2e --record --key ${{ secrets.CYPRESS_RECORD_KEY }}
working-directory: frontend-monorepo
env:
CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE: ${{ secrets.CYPRESS_TRADING_TEST_VEGA_WALLET_PASSPHRASE }}
+4 -4
View File
@@ -22,9 +22,9 @@ jobs:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: main
main-branch-name: master
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v3
@@ -53,9 +53,9 @@ jobs:
with:
fetch-depth: 0
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v3
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: main
main-branch-name: master
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v3
+2 -2
View File
@@ -1,8 +1,8 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.vega.xyz
NX_VEGA_REST=https://api.token.vega.xyz
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
@@ -26,7 +26,7 @@ export interface MarketTags_market_tradableInstrument_instrument {
export interface MarketTags_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
* An instance of, or reference to, a fully specified instrument.
*/
instrument: MarketTags_market_tradableInstrument_instrument;
}
@@ -34,14 +34,14 @@ export interface MarketTags_market_tradableInstrument {
export interface MarketTags_market {
__typename: "Market";
/**
* An instance of or reference to a tradable instrument.
* An instance of, or reference to, a tradable instrument.
*/
tradableInstrument: MarketTags_market_tradableInstrument;
}
export interface MarketTags {
/**
* An instrument that is trading on the VEGA network
* An instrument that is trading on the Vega network
*/
market: MarketTags_market | null;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: PartyBalanceQuery
@@ -12,7 +12,7 @@ import { AccountType } from "./../../../../../../../libs/types/src/__generated__
export interface PartyBalanceQuery_party_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
@@ -24,7 +24,7 @@ export interface PartyBalanceQuery_party_accounts_asset {
*/
name: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
}
@@ -55,7 +55,7 @@ export interface PartyBalanceQuery_party {
export interface PartyBalanceQuery {
/**
* An entity that is trading on the VEGA network
* An entity that is trading on the Vega network
*/
party: PartyBalanceQuery_party | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AssetStatus } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { AssetStatus } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: Deposits
@@ -16,7 +16,7 @@ export interface Deposits_assetsConnection_edges_node_source_BuiltinAsset {
export interface Deposits_assetsConnection_edges_node_source_ERC20 {
__typename: "ERC20";
/**
* The address of the erc20 contract
* The address of the ERC20 contract
*/
contractAddress: string;
}
@@ -26,7 +26,7 @@ export type Deposits_assetsConnection_edges_node_source = Deposits_assetsConnect
export interface Deposits_assetsConnection_edges_node {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
@@ -38,15 +38,15 @@ export interface Deposits_assetsConnection_edges_node {
*/
symbol: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
/**
* The status of the asset in the vega network
* The status of the asset in the Vega network
*/
status: AssetStatus;
/**
* The origin source of the asset (e.g: an erc20 asset)
* The origin source of the asset (e.g: an ERC20 asset)
*/
source: Deposits_assetsConnection_edges_node_source;
}
@@ -66,7 +66,7 @@ export interface Deposits_assetsConnection {
export interface Deposits {
/**
* The list of all assets in use in the vega network or the specified asset if id is provided
* The list of all assets in use in the Vega network or the specified asset if ID is provided
*/
assetsConnection: Deposits_assetsConnection;
assetsConnection: Deposits_assetsConnection | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
export type DepositsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type DepositsQuery = { __typename?: 'Query', assetsConnection: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } } | null> | null } };
export type DepositsQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } } | null> | null } | null };
export const DepositsDocument = gql`
@@ -8,7 +8,7 @@ interface TabBarProps {
export const TabBar = ({ className }: TabBarProps) => (
<div role="group" aria-label="Tab Bar Navigation Menu" className={className}>
<div role="presentation" className="py-[42px]" />
<div className="md:hidden fixed bottom-0 left-0 right-0 bg-black py-4 border-t border-neutral-300 dark:border-neutral-700">
<div className="md:hidden fixed bottom-0 left-0 right-0 bg-black py-4 border-t border-neutral-300 dark:border-neutral-600">
<Nav tabs className="flex justify-evenly items-center" />
</div>
</div>
@@ -1,20 +1,15 @@
fragment SimpleMarketDataFields on MarketData {
market {
id
state
}
fragment SimpleMarketDataFields on ObservableMarketData {
marketId
marketState
}
query SimpleMarkets($CandleSince: String!) {
markets {
id
name
state
data {
...SimpleMarketDataFields
}
tradableInstrument {
instrument {
name
code
metadata {
tags
@@ -37,8 +32,8 @@ query SimpleMarkets($CandleSince: String!) {
}
}
subscription SimpleMarketDataSub {
marketData {
subscription SimpleMarketDataSub($marketIds: [ID!]!) {
marketsData(marketIds: $marketIds) {
...SimpleMarketDataFields
}
}
@@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { MarketState } from "@vegaprotocol/types";
// ====================================================
// GraphQL fragment: SimpleMarketDataFields
// ====================================================
export interface SimpleMarketDataFields_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Current state of the market
*/
state: MarketState;
}
export interface SimpleMarketDataFields {
__typename: "MarketData";
/**
* market ID of the associated mark price
*/
market: SimpleMarketDataFields_market;
}
@@ -3,35 +3,31 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketState } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { MarketState } from "@vegaprotocol/types";
// ====================================================
// GraphQL subscription operation: SimpleMarketDataSub
// ====================================================
export interface SimpleMarketDataSub_marketData_market {
__typename: "Market";
export interface SimpleMarketDataSub_marketsData {
__typename: "ObservableMarketData";
/**
* Market ID
* current state of the market
*/
id: string;
marketState: MarketState;
/**
* Current state of the market
* market ID of the associated mark price
*/
state: MarketState;
}
export interface SimpleMarketDataSub_marketData {
__typename: "MarketData";
/**
* market id of the associated mark price
*/
market: SimpleMarketDataSub_marketData_market;
marketId: string;
}
export interface SimpleMarketDataSub {
/**
* Subscribe to the mark price changes
*/
marketData: SimpleMarketDataSub_marketData;
marketsData: SimpleMarketDataSub_marketsData[];
}
export interface SimpleMarketDataSubVariables {
marketIds: string[];
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketState } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { MarketState } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: SimpleMarkets
@@ -60,7 +60,7 @@ export interface SimpleMarkets_markets_tradableInstrument_instrument {
export interface SimpleMarkets_markets_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
* An instance of, or reference to, a fully specified instrument.
*/
instrument: SimpleMarkets_markets_tradableInstrument_instrument;
}
@@ -88,11 +88,11 @@ export interface SimpleMarkets_markets {
*/
state: MarketState;
/**
* An instance of or reference to a tradable instrument.
* An instance of, or reference to, a tradable instrument.
*/
tradableInstrument: SimpleMarkets_markets_tradableInstrument;
/**
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by parameters
*/
candles: (SimpleMarkets_markets_candles | null)[] | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,41 +1,38 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type SimpleMarketDataFieldsFragment = { __typename?: 'MarketData', market: { __typename?: 'Market', id: string, state: Types.MarketState } };
export type SimpleMarketDataFieldsFragment = { __typename?: 'ObservableMarketData', marketId: string, marketState: Types.MarketState };
export type SimpleMarketsQueryVariables = Types.Exact<{
CandleSince: Types.Scalars['String'];
}>;
export type SimpleMarketsQuery = { __typename?: 'Query', markets?: Array<{ __typename?: 'Market', id: string, name: string, state: Types.MarketState, data?: { __typename?: 'MarketData', market: { __typename?: 'Market', id: string, state: Types.MarketState } } | null, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', symbol: string } } } }, candles?: Array<{ __typename?: 'Candle', open: string, close: string } | null> | null }> | null };
export type SimpleMarketsQuery = { __typename?: 'Query', markets?: Array<{ __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', symbol: string } } } }, candles?: Array<{ __typename?: 'Candle', open: string, close: string } | null> | null }> | null };
export type SimpleMarketDataSubSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
export type SimpleMarketDataSubSubscriptionVariables = Types.Exact<{
marketIds: Array<Types.Scalars['ID']> | Types.Scalars['ID'];
}>;
export type SimpleMarketDataSubSubscription = { __typename?: 'Subscription', marketData: { __typename?: 'MarketData', market: { __typename?: 'Market', id: string, state: Types.MarketState } } };
export type SimpleMarketDataSubSubscription = { __typename?: 'Subscription', marketsData: Array<{ __typename?: 'ObservableMarketData', marketId: string, marketState: Types.MarketState }> };
export const SimpleMarketDataFieldsFragmentDoc = gql`
fragment SimpleMarketDataFields on MarketData {
market {
id
state
}
fragment SimpleMarketDataFields on ObservableMarketData {
marketId
marketState
}
`;
export const SimpleMarketsDocument = gql`
query SimpleMarkets($CandleSince: String!) {
markets {
id
name
state
data {
...SimpleMarketDataFields
}
tradableInstrument {
instrument {
name
code
metadata {
tags
@@ -57,7 +54,7 @@ export const SimpleMarketsDocument = gql`
}
}
}
${SimpleMarketDataFieldsFragmentDoc}`;
`;
/**
* __useSimpleMarketsQuery__
@@ -87,8 +84,8 @@ export type SimpleMarketsQueryHookResult = ReturnType<typeof useSimpleMarketsQue
export type SimpleMarketsLazyQueryHookResult = ReturnType<typeof useSimpleMarketsLazyQuery>;
export type SimpleMarketsQueryResult = Apollo.QueryResult<SimpleMarketsQuery, SimpleMarketsQueryVariables>;
export const SimpleMarketDataSubDocument = gql`
subscription SimpleMarketDataSub {
marketData {
subscription SimpleMarketDataSub($marketIds: [ID!]!) {
marketsData(marketIds: $marketIds) {
...SimpleMarketDataFields
}
}
@@ -106,10 +103,11 @@ export const SimpleMarketDataSubDocument = gql`
* @example
* const { data, loading, error } = useSimpleMarketDataSubSubscription({
* variables: {
* marketIds: // value for 'marketIds'
* },
* });
*/
export function useSimpleMarketDataSubSubscription(baseOptions?: Apollo.SubscriptionHookOptions<SimpleMarketDataSubSubscription, SimpleMarketDataSubSubscriptionVariables>) {
export function useSimpleMarketDataSubSubscription(baseOptions: Apollo.SubscriptionHookOptions<SimpleMarketDataSubSubscription, SimpleMarketDataSubSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<SimpleMarketDataSubSubscription, SimpleMarketDataSubSubscriptionVariables>(SimpleMarketDataSubDocument, options);
}
@@ -33,7 +33,7 @@ export const MARKETS_QUERY = gql`
}
}
}
candles(interval: I1H, since: $CandleSince) {
candles(interval: INTERVAL_I1H, since: $CandleSince) {
open
close
}
@@ -42,19 +42,17 @@ export const MARKETS_QUERY = gql`
`;
const MARKET_DATA_SUB = gql`
subscription SimpleMarketDataSub {
marketData {
market {
id
state
}
subscription SimpleMarketDataSub($marketIds: [ID!]!) {
marketsData(marketIds: $marketIds) {
marketState
marketId
}
}
`;
export const CANDLE_SUB = gql`
subscription CandleLive($marketId: ID!) {
candles(marketId: $marketId, interval: I1H) {
candles(marketId: $marketId, interval: INTERVAL_I1H) {
close
}
}
@@ -10,7 +10,7 @@
export interface MarketMarkPrice_market_data {
__typename: "MarketData";
/**
* the mark price (actually an unsigned int)
* the mark price (an unsigned integer)
*/
markPrice: string;
}
@@ -19,7 +19,7 @@ export interface MarketMarkPrice_market {
__typename: "Market";
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
* number denominated in the currency of the market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
@@ -42,7 +42,7 @@ export interface MarketMarkPrice_market {
export interface MarketMarkPrice {
/**
* An instrument that is trading on the VEGA network
* An instrument that is trading on the Vega network
*/
market: MarketMarkPrice_market | null;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: PartyMarketData
@@ -12,11 +12,11 @@ import { AccountType } from "./../../../../../../libs/types/src/__generated__/gl
export interface PartyMarketData_party_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
}
@@ -64,15 +64,15 @@ export interface PartyMarketData_party_marginsConnection_edges_node {
*/
market: PartyMarketData_party_marginsConnection_edges_node_market;
/**
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
* this is the minimum margin required for a party to place a new order on the network (unsigned integer)
*/
initialLevel: string;
/**
* minimal margin for the position to be maintained in the network (unsigned int actually)
* minimal margin for the position to be maintained in the network (unsigned integer)
*/
maintenanceLevel: string;
/**
* if the margin is between maintenance and search, the network will initiate a collateral search (unsigned int actually)
* if the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer)
*/
searchLevel: string;
}
@@ -101,14 +101,14 @@ export interface PartyMarketData_party {
*/
accounts: PartyMarketData_party_accounts[] | null;
/**
* Margin level for a market
* Margin levels for a market
*/
marginsConnection: PartyMarketData_party_marginsConnection;
marginsConnection: PartyMarketData_party_marginsConnection | null;
}
export interface PartyMarketData {
/**
* An entity that is trading on the VEGA network
* An entity that is trading on the Vega network
*/
party: PartyMarketData_party | null;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { Side, OrderTimeInForce, OrderType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: EstimateOrder
@@ -12,15 +12,15 @@ import { Side, OrderTimeInForce, OrderType } from "./../../../../../../libs/type
export interface EstimateOrder_estimateOrder_fee {
__typename: "TradeFee";
/**
* The maker fee, aggressive party to the other party (the one who had an order in the book)
* The maker fee, paid by the aggressive party to the other party (the one who had an order in the book)
*/
makerFee: string;
/**
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
* The infrastructure fee, a fee paid to the validators to maintain the Vega network
*/
infrastructureFee: string;
/**
* The fee paid to the market makers to provide liquidity in the market
* The fee paid to the liquidity providers that committed liquidity to the market
*/
liquidityFee: string;
}
@@ -28,7 +28,7 @@ export interface EstimateOrder_estimateOrder_fee {
export interface EstimateOrder_estimateOrder_marginLevels {
__typename: "MarginLevels";
/**
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
* this is the minimum margin required for a party to place a new order on the network (unsigned integer)
*/
initialLevel: string;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: MarketPositions
@@ -12,7 +12,7 @@ import { AccountType } from "./../../../../../../libs/types/src/__generated__/gl
export interface MarketPositions_party_accounts_asset {
__typename: "Asset";
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
}
@@ -91,12 +91,12 @@ export interface MarketPositions_party {
/**
* Trading positions relating to a party
*/
positionsConnection: MarketPositions_party_positionsConnection;
positionsConnection: MarketPositions_party_positionsConnection | null;
}
export interface MarketPositions {
/**
* An entity that is trading on the VEGA network
* An entity that is trading on the Vega network
*/
party: MarketPositions_party | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -8,7 +8,7 @@ export type MarketPositionsQueryVariables = Types.Exact<{
}>;
export type MarketPositionsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', decimals: number }, market?: { __typename?: 'Market', id: string } | null }> | null, positionsConnection: { __typename?: 'PositionConnection', edges?: Array<{ __typename?: 'PositionEdge', node: { __typename?: 'Position', openVolume: string, market: { __typename?: 'Market', id: string } } }> | null } } | null };
export type MarketPositionsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', decimals: number }, market?: { __typename?: 'Market', id: string } | null }> | null, positionsConnection?: { __typename?: 'PositionConnection', edges?: Array<{ __typename?: 'PositionEdge', node: { __typename?: 'Position', openVolume: string, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export const MarketPositionsDocument = gql`
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -8,7 +8,7 @@ export type PartyMarketDataQueryVariables = Types.Exact<{
}>;
export type PartyMarketDataQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string, decimals: number }, market?: { __typename?: 'Market', id: string } | null }> | null, marginsConnection: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', initialLevel: string, maintenanceLevel: string, searchLevel: string, market: { __typename?: 'Market', id: string } } }> | null } } | null };
export type PartyMarketDataQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, accounts?: Array<{ __typename?: 'Account', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', id: string, decimals: number }, market?: { __typename?: 'Market', id: string } | null }> | null, marginsConnection?: { __typename?: 'MarginConnection', edges?: Array<{ __typename?: 'MarginEdge', node: { __typename?: 'MarginLevels', initialLevel: string, maintenanceLevel: string, searchLevel: string, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
export const PartyMarketDataDocument = gql`
+2 -2
View File
@@ -2,9 +2,9 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.vega.xyz/
NX_VEGA_REST=https://api.token.vega.xyz/
# App flags
NX_EXPLORER_ASSETS=1
+1 -1
View File
@@ -1,6 +1,6 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz/tm
NX_TENDERMINT_URL=https://tm.n06.testnet.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://lb.testnet.vega.xyz/tm/websocket
NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
NX_VEGA_ENV=TESTNET
+6 -134
View File
@@ -1,148 +1,20 @@
context('Asset page', { tags: '@regression' }, function () {
before('gather system asset information', function () {
cy.get_asset_information().as('assetsInfo');
});
import '../support/common.functions';
context('Asset page', function () {
describe('Verify elements on page', function () {
const assetsNavigation = 'a[href="/assets"]';
const assetHeader = '[data-testid="asset-header"]';
const jsonSection = '.language-json';
before('Navigate to assets page', function () {
it('Assets page is displayed', function () {
cy.visit('/');
cy.get(assetsNavigation).click();
// Check we have enough enough assets
const assetNames = Object.keys(this.assetsInfo);
assert.isAtLeast(
assetNames.length,
5,
'Ensuring we have at least 5 assets to test'
);
cy.common_validate_blocks_data_displayed(assetHeader);
});
it('should be able to see assets page sections', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
});
});
it('should be able to see all asset details displayed in JSON', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((assetsListedInJson) => {
const assetInfo = this.assetsInfo[assetName];
assert.equal(assetsListedInJson.name, assetInfo.name);
assert.equal(assetsListedInJson.id, assetInfo.id);
assert.equal(assetsListedInJson.decimals, assetInfo.decimals);
assert.equal(assetsListedInJson.symbol, assetInfo.symbol);
assert.equal(
assetsListedInJson.source.__typename,
assetInfo.source.__typename
);
if (assetInfo.source.__typename == 'ERC20') {
assert.equal(
assetsListedInJson.source.contractAddress,
assetInfo.source.contractAddress
);
}
if (assetInfo.source.__typename == 'BuiltinAsset') {
assert.equal(
assetsListedInJson.source.maxFaucetAmountMint,
assetInfo.source.maxFaucetAmountMint
);
}
let knownAssetTypes = ['BuiltinAsset', 'ERC20'];
assert.include(
knownAssetTypes,
assetInfo.source.__typename,
`Checking that current asset type of ${assetInfo.source.__typename} /
is one of: ${knownAssetTypes}: /
If fail then we need to add extra tests for un-encountered asset types`
);
});
});
});
});
it('should be able to switch assets between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
it('should be able to see assets page displayed in mobile', function () {
it('Assets page displayed in mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(assetsNavigation).click();
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
});
cy.common_validate_blocks_data_displayed(assetHeader);
});
});
});
+20 -252
View File
@@ -1,267 +1,35 @@
context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
import '../support/common.functions';
context('Network parameters page', function () {
before('visit token home page', function () {
cy.visit('/');
});
describe('Verify elements on page', function () {
const networkParametersNavigation = 'a[href="/network-parameters"]';
const networkParametersHeader = '[data-testid="network-param-header"]';
const tableRows = '[data-testid="key-value-table-row"]';
before('navigate to network parameter page', function () {
cy.visit('/');
beforeEach('Navigate to network parameter page', function () {
cy.get(networkParametersNavigation).click();
});
it('should show network parameter heading at top of page', function () {
cy.get(networkParametersHeader)
.should('have.text', 'Network Parameters')
.and('be.visible');
it('Network paremeter page is displayed', function () {
verifyNetworkParametersPageDisplayed();
});
it('should list each of the network parameters available', function () {
cy.get_network_parameters().then((network_parameters) => {
const numberOfNetworkParametersInSystem =
Object.keys(network_parameters).length;
cy.get(tableRows).should(
'have.length',
numberOfNetworkParametersInSystem
);
});
});
it('should list each network parameter displayed with json - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.json.includes(parameterName)) {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.invoke('text')
.convert_string_json_to_js_object()
.then((jsonOnPage) => {
cy.wrap(parameterValue, { log: false })
.convert_string_json_to_js_object()
.then((jsonInSystem) =>
assert.deepEqual(
jsonOnPage,
jsonInSystem,
`Checking ${parameterName} has the correct value of ${jsonInSystem}`
)
);
});
}
});
});
});
it('should list each network parameter displayed as a percentage - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.percentage.includes(parameterName)) {
const formattedPercentageParameter =
(parseFloat(parameterValue) * 100).toFixed(0) + '%';
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.invoke('text')
.then((parameterValueOnPage) => {
assert.equal(
parameterValueOnPage,
formattedPercentageParameter,
`Checking ${parameterName} has the correct value of ${formattedPercentageParameter}`
);
cy.contains(parameterValueOnPage).should('be.visible');
});
}
});
});
});
it('should list each network parameter displayed as an id - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.id.includes(parameterName)) {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.should('contain', parameterValue)
.and('be.visible')
.invoke('text');
}
});
});
});
it('should list each network parameter displayed as a date - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.date.includes(parameterName)) {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.should('contain', parameterValue)
.and('be.visible');
}
});
});
});
it('should list each network parameter displayed as a duration - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.duration.includes(parameterName)) {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.should('contain', parameterValue)
.and('be.visible');
}
});
});
});
it('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (this.networkParameterFormat.fiveDecimal.includes(parameterName)) {
cy.convert_number_to_four_decimal(parameterValue)
.add_commas_to_number_if_large_enough()
.then((parameterValueFormatted) => {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.invoke('text')
.then((parameterValueOnPage) => {
assert.equal(
parameterValueOnPage,
parameterValueFormatted,
`Checking ${parameterName} has the correct value of ${parameterValueFormatted}`
);
cy.contains(parameterValueOnPage).should('be.visible');
});
});
}
});
});
});
it('should list each network parameter displayed as a currency value with eighteen decimals - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
const parameterValue = network_parameter[1];
if (
this.networkParameterFormat.eighteenDecimal.includes(parameterName)
) {
cy.convert_number_to_eighteen_decimal(parameterValue)
.add_commas_to_number_if_large_enough()
.then((parameterValueFormatted) => {
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.invoke('text')
.then((parameterValueOnPage) => {
assert.equal(
parameterValueOnPage,
parameterValueFormatted,
`Checking ${parameterName} has the correct value of ${parameterValueFormatted}`
);
cy.contains(parameterValueOnPage).should('be.visible');
});
});
}
});
});
});
it('should be able to switch network parameter page - between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(networkParametersNavigation)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(networkParametersNavigation)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
it('should be able to see network parameters - on mobile', function () {
it('Network parameter page displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(networkParametersNavigation).click();
cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
const parameterName = network_parameter[0];
cy.get(tableRows)
.contains(parameterName)
.should('be.visible')
.next()
.should('not.be.empty')
.and('be.visible');
});
});
verifyNetworkParametersPageDisplayed();
});
});
function verifyNetworkParametersPageDisplayed() {
cy.get('[data-testid="network-param-header"]').should(
'have.text',
'Network Parameters'
);
cy.common_verify_json_parameters(18);
cy.common_verify_json_string_values(6);
cy.common_verify_json_int_values(7);
}
});
@@ -1,264 +0,0 @@
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const partiesMenuHeader = 'a[href="/parties"]';
const partiesSearchBox = '[data-testid="party-input"]';
const partiesSearchAction = '[data-testid="go-submit"]';
const partiesJsonSection = '[data-testid="parties-json"]';
const txTimeout = Cypress.env('txTimeout');
let assetData = {
fUSDC: { id: 'fUSDC', name: 'USDC (fake)', amount: '10' },
fBTC: { id: 'fBTC', name: 'BTC (fake)', amount: '6' },
fEURO: { id: 'fEURO', name: 'EURO (fake)', amount: '8' },
fDAI: { id: 'fDAI', name: 'DAI (fake)', amount: '2' },
};
const assetsInTest = Object.keys(assetData);
context('Parties page', { tags: '@regression' }, function () {
before('send-faucet assets to connected vega wallet', function () {
cy.vega_wallet_import();
assetsInTest.forEach((asset) => {
cy.vega_wallet_receive_fauceted_asset(
assetData[asset].name,
assetData[asset].amount,
vegaWalletPublicKey
);
});
cy.visit('/');
});
describe('Verify parties page content', function () {
before('navigate to parties page and search for party', function () {
cy.get(partiesMenuHeader).click();
// Deliberate slow entry of party id/key - enabling transactions to sync
cy.get(partiesSearchBox).type(vegaWalletPublicKey, { delay: 120 });
cy.get(partiesSearchAction).click();
cy.get_connected_parties_accounts().as('party_accounts');
// Ensure balance of each party asset is correct
cy.get('@party_accounts').then((accounts) => {
assetsInTest.forEach((asset) => {
cy.get_asset_decimals(assetData[asset].id).then((assetDecimals) => {
assert.equal(
accounts[assetData[asset].id].balance,
assetData[asset].amount + assetDecimals,
`Checking ${assetData[asset].id} faucet was successfull`
);
});
});
});
});
it('should see party address id - having searched', function () {
cy.getByTestId('parties-header')
.siblings()
.contains(vegaWalletPublicKey.substring(0, 14))
.should('be.visible');
});
it('should see each asset and balance - within asset data section', function () {
assetsInTest.forEach((asset) => {
cy.contains(assetData[asset].name, txTimeout).should('be.visible');
cy.contains(assetData[asset].name)
.siblings()
.contains(assetData[asset].id)
.should('be.visible');
cy.contains(assetData[asset].name, txTimeout)
.parent()
.siblings()
.within(() => {
cy.get_asset_decimals(asset).then((assetDecimals) => {
cy.contains_exactly(
assetData[asset].amount + '.' + assetDecimals
).should('be.visible');
});
});
});
});
it(
'should be able to copy the party address id',
{ browser: 'chrome' },
function () {
cy.monitor_clipboard().as('clipboard');
cy.getByTestId('parties-header')
.next()
.within(() => {
cy.get('button').click();
});
cy.get('@clipboard')
.get_copied_text_from_clipboard()
.should('equal', vegaWalletPublicKey);
}
);
it(
'should be able to copy an asset id',
{ browser: 'chrome' },
function () {
cy.monitor_clipboard().as('clipboard');
cy.contains(assetData.fDAI.name, txTimeout).should('be.visible');
cy.contains(assetData.fDAI.name)
.parent()
.parent()
.siblings()
.within(() => {
cy.get('[data-state="closed"]').last().click({ force: true });
});
cy.get('@clipboard')
.get_copied_text_from_clipboard()
.should('equal', assetData.fDAI.id);
}
);
it('should be able to see JSON of each asset containing correct balance and decimals', function () {
cy.get(partiesJsonSection).should('be.visible');
assetsInTest.forEach((assetInTest) => {
cy.get(partiesJsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.get_party_accounts_data_from_js_object()
.then((accountsListedInJson) => {
cy.get_asset_information().then((assetsInfo) => {
const assetInfo =
assetsInfo[accountsListedInJson[assetInTest].asset.name];
assert.equal(
accountsListedInJson[assetInTest].asset.name,
assetInfo.name
);
assert.equal(
accountsListedInJson[assetInTest].asset.id,
assetInfo.id
);
assert.equal(
accountsListedInJson[assetInTest].asset.decimals,
assetInfo.decimals
);
assert.equal(
accountsListedInJson[assetInTest].asset.symbol,
assetInfo.symbol
);
assert.equal(
accountsListedInJson[assetInTest].asset.source.__typename,
assetInfo.source.__typename
);
cy.get_asset_decimals(assetInTest).then((assetDecimals) => {
assert.equal(
accountsListedInJson[assetInTest].balance,
assetData[assetInTest].amount + assetDecimals
);
});
});
});
});
});
it('should be able to switch parties page between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(partiesMenuHeader)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(partiesMenuHeader)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
after(
'teardown environment to prevent test data bleeding into other tests',
function () {
if (Cypress.env('TEARDOWN_NETWORK_AFTER_FLOWS')) {
cy.restart_vegacapsule_network();
}
}
);
Cypress.Commands.add('get_asset_decimals', (assetID) => {
cy.get_asset_information().then((assetsInfo) => {
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
let decimals = '';
for (let i = 0; i < assetDecimals; i++) decimals += '0';
return decimals;
});
});
Cypress.Commands.add('get_connected_parties_accounts', () => {
const mutation =
'{partiesConnection {edges{node{accountsConnection{edges{node{\
balance type asset {id symbol decimals}}}}}}}}';
cy.request({
method: 'POST',
url: `http://localhost:3028/query`,
body: {
query: mutation,
},
headers: { 'content-type': 'application/json' },
})
.its(
`body.data.partiesConnection.edges.1.node.accountsConnection.edges`
)
.then(function (response) {
let accounts = {};
response.forEach((account) => {
accounts[account.node.asset.id] = account.node;
});
return accounts;
});
});
Cypress.Commands.add(
'get_party_accounts_data_from_js_object',
{ prevSubject: true },
(jsObject) => {
const accounts = jsObject.party.accounts.reduce(function (
account,
entry
) {
account[entry.asset.id] = entry;
return account;
},
{});
return accounts;
}
);
});
});
+18 -291
View File
@@ -1,300 +1,27 @@
context('Validator page', { tags: '@smoke' }, function () {
const validatorMenuHeading = 'a[href="/validators"]';
const tendermintDataHeader = '[data-testid="tendermint-header"]';
const vegaDataHeader = '[data-testid="vega-header"]';
const jsonSection = '.language-json';
before('Visit validators page and obtain data', function () {
cy.visit('/');
cy.get(validatorMenuHeading).click();
cy.get_validators().as('validators');
cy.get_nodes().as('nodes');
});
import '../support/common.functions';
context('Validator page', function () {
describe('Verify elements on page', function () {
before('Ensure at least two validators are present', function () {
assert.isAtLeast(
this.validators.length,
2,
'Ensuring at least two validators exist'
);
const validatorNavigation = 'a[href="/validators"]';
it('Validator page is displayed', function () {
cy.visit('/');
cy.get(validatorNavigation).click();
validateValidatorsDisplayed();
});
it('should be able to see validator page sections', function () {
cy.get(vegaDataHeader)
.contains('Vega data')
.and('is.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
cy.get(tendermintDataHeader)
.contains('Tendermint data')
.and('is.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
});
it('should be able to see relevant validator information in tendermint section', function () {
cy.get(tendermintDataHeader)
.contains('Tendermint data')
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((validatorsInJson) => {
this.validators.forEach((validator, index) => {
const validatorInJson =
validatorsInJson.result.validators[index];
assert.equal(
validatorInJson.address,
validator.address,
`Checking that validator address shown in json matches system data`
);
cy.contains(validator.address).should('be.visible');
assert.equal(
validatorInJson.pub_key.type,
validator.pub_key.type,
`Checking that validator public key type shown in json matches system data`
);
cy.contains(validator.pub_key.type).should('be.visible');
assert.equal(
validatorInJson.pub_key.value,
validator.pub_key.value,
`Checking that validator public key value shown in json matches system data`
);
cy.contains(validator.pub_key.value).should('be.visible');
assert.equal(
validatorInJson.voting_power,
validator.voting_power,
`Checking that validator voting power in json matches system data`
);
cy.contains(validator.voting_power).should('be.visible');
// Proposer priority can change frequently mid test
// Therefore only checking the field name is present.
cy.contains('proposer_priority').should('be.visible');
});
});
});
});
it('should be able to see relevant node information in vega data section', function () {
cy.get(vegaDataHeader)
.contains('Vega data')
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((nodesInJson) => {
this.nodes.forEach((node, index) => {
const nodeInJson = nodesInJson.nodes[index];
// Vegacapsule shows no info or null for following fields:
// name, infoURL, avatarUrl, location, epoch data
// Therefore, these values remain unchecked.
assert.equal(
nodeInJson.__typename,
node.__typename,
`Checking that node __typename shown in json matches system data`
);
cy.contains(node.__typename).should('be.visible');
assert.equal(
nodeInJson.id,
node.id,
`Checking that node id shown in json matches system data`
);
cy.contains(node.id).should('be.visible');
assert.equal(
nodeInJson.pubkey,
node.pubkey,
`Checking that node pubkey shown in json matches system data`
);
cy.contains(node.pubkey).should('be.visible');
assert.equal(
nodeInJson.tmPubkey,
node.tmPubkey,
`Checking that node tmPubkey shown in json matches system data`
);
cy.contains(node.tmPubkey).should('be.visible');
assert.equal(
nodeInJson.ethereumAddress,
node.ethereumAddress,
`Checking that node ethereumAddress shown in json matches system data`
);
cy.contains(node.ethereumAddress).should('be.visible');
assert.equal(
nodeInJson.stakedByOperator,
node.stakedByOperator,
`Checking that node stakedByOperator value shown in json matches system data`
);
cy.contains(node.stakedByOperator).should('be.visible');
assert.equal(
nodeInJson.stakedByDelegates,
node.stakedByDelegates,
`Checking that node stakedByDelegates value shown in json matches system data`
);
cy.contains(node.stakedByDelegates).should('be.visible');
assert.equal(
nodeInJson.stakedTotal,
node.stakedTotal,
`Checking that node stakedTotal shown in json matches system data`
);
cy.contains(node.stakedTotal).should('be.visible');
assert.equal(
nodeInJson.pendingStake,
node.pendingStake,
`Checking that node pendingStake shown in json matches system data`
);
cy.contains(node.pendingStake).should('be.visible');
assert.equal(
nodeInJson.status,
node.status,
`Checking that node status shown in json matches system data`
);
cy.contains(node.status).should('be.visible');
});
});
});
});
it('should be able to see validator page displayed on mobile', function () {
it('Validator page is displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(validatorMenuHeading).click();
cy.get(vegaDataHeader)
.contains('Vega data')
.and('is.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
cy.get(tendermintDataHeader)
.contains('Tendermint data')
.and('is.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
cy.get(tendermintDataHeader)
.contains('Tendermint data')
.next()
.within(() => {
this.validators.forEach((validator) => {
cy.contains(validator.address).should('be.visible');
cy.contains(validator.pub_key.type).should('be.visible');
cy.contains(validator.pub_key.value).should('be.visible');
cy.contains(validator.voting_power).should('be.visible');
// Proposer priority can change frequently mid test
// Therefore only checking the field name is present.
cy.contains('proposer_priority').should('be.visible');
});
});
cy.get(validatorNavigation).click();
validateValidatorsDisplayed();
});
it('should be able to switch validator page between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(validatorMenuHeading)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(validatorMenuHeading)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
Cypress.Commands.add('get_validators', () => {
cy.request({
method: 'GET',
url: `http://localhost:26617/validators`,
headers: { 'content-type': 'application/json' },
})
.its(`body.result.validators`)
.then(function (response) {
let validators = [];
response.forEach((account, index) => {
validators[index] = account;
});
return validators;
});
});
Cypress.Commands.add('get_nodes', () => {
const mutation =
'{nodes { id name infoUrl avatarUrl pubkey tmPubkey ethereumAddress \
location stakedByOperator stakedByDelegates stakedTotal pendingStake \
epochData { total offline online __typename } status name __typename}}';
cy.request({
method: 'POST',
url: `http://localhost:3028/query`,
body: {
query: mutation,
},
headers: { 'content-type': 'application/json' },
})
.its(`body.data.nodes`)
.then(function (response) {
let nodes = [];
response.forEach((node) => {
nodes.push(node);
});
return nodes;
});
});
function validateValidatorsDisplayed() {
cy.get('[data-testid="tendermint-header"]').should(
'have.text',
'Tendermint data'
);
cy.get('[data-testid="tendermint-data"]').should('not.be.empty');
}
});
});
+9 -4
View File
@@ -1,12 +1,17 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_ENV=CUSTOM
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://n01.stagnet3.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n01.stagnet3.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_BLOCK_EXPLORER=
# App flags
NX_EXPLORER_ASSETS=1
@@ -17,4 +22,4 @@ NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=1
NX_EXPLORER_ORACLES=1
NX_EXPLORER_TXS_LIST=0
NX_EXPLORER_TXS_LIST=1
+1 -1
View File
@@ -3,7 +3,7 @@ NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-exp
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_URL=http://localhost:3028/query
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
+1 -2
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_URL=https://api.n04.d.vega.xyz/graphql
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
+4 -8
View File
@@ -1,13 +1,9 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=MAINNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
# 0.53.0 misreports total nodes, including ex-validators
NX_HACK_53_TOTAL_NODES=7
-20
View File
@@ -1,20 +0,0 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=https://tm.be.mainnet-mirror.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_VEGA_ENV=MIRROR
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
# App flags
NX_EXPLORER_ASSETS=1
NX_EXPLORER_GENESIS=1
NX_EXPLORER_GOVERNANCE=1
NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=0
NX_EXPLORER_ORACLES=0
NX_EXPLORER_TXS_LIST=1
-11
View File
@@ -1,11 +0,0 @@
# App configuration variables
NX_VEGA_URL=https://api.sandbox.vega.xyz/graphql
NX_VEGA_ENV=SANDBOX
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/docs/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
-13
View File
@@ -1,13 +0,0 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
NX_VEGA_URL=https://api.n00.stagnet1.vega.xyz/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
+1 -2
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_URL=https://n01.stagnet3.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n01.stagnet3.vega.xyz/tm/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://blockexplorer.n01.stagnet3.vega.xyz/rest
+4 -5
View File
@@ -1,10 +1,9 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://tm.n07.testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n07.testnet.vega.xyz/websocket
NX_TENDERMINT_URL=https://tm.n06.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n06.testnet.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_URL=https://api.n09.testnet.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_ENV=TESTNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_URL=https://api.n09.testnet.vega.xyz/graphql
-1
View File
@@ -5,4 +5,3 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
NX_VEGA_URL=http://localhost:3003/query
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=
+1 -4
View File
@@ -14,8 +14,5 @@
"files": ["*.js", "*.jsx"],
"rules": {}
}
],
"env": {
"jest": true
}
]
}
+1 -1
View File
@@ -45,7 +45,7 @@ function App() {
menuOpen && 'h-[100vh] overflow-hidden'
} antialiased m-0 bg-white dark:bg-black text-black dark:text-white`}
>
<div className="grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r mx-auto">
<div className="min-h-[100vh] max-w-[1300px] grid grid-rows-[repeat(2,_auto)_1fr] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)] md:grid-cols-[300px_1fr] border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r mx-auto">
<Header
theme={theme}
toggleTheme={toggleTheme}
@@ -14,12 +14,7 @@ jest.mock('../search', () => ({
const renderComponent = () => (
<MemoryRouter>
<Header
theme="dark"
toggleTheme={jest.fn()}
menuOpen={false}
setMenuOpen={jest.fn()}
/>
<Header />
</MemoryRouter>
);
@@ -24,15 +24,14 @@ export const Header = ({
'md:col-span-2',
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
'p-4 gap-2 md:gap-4',
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
'dark text-white'
'border-b border-neutral-700 dark:border-neutral-300'
);
return (
<header className={headerClasses}>
<div className="flex h-full items-center sm:items-stretch gap-4">
<Link to={Routes.HOME}>
<h1
className="text-white text-3xl font-alpha uppercase calt mb-0"
className="text-3xl font-alpha uppercase calt mb-0"
data-testid="explorer-header"
>
{t('Vega Explorer')}
@@ -42,13 +41,13 @@ export const Header = ({
</div>
<button
data-testid="open-menu"
className="md:hidden text-white"
className="md:hidden"
onClick={() => setMenuOpen(!menuOpen)}
>
<Icon name={menuOpen ? 'cross' : 'menu'} />
</button>
<Search />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} className="-my-4" />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
</header>
);
};
@@ -1 +0,0 @@
export * from './info-block';
@@ -1,29 +0,0 @@
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import React from 'react';
export interface InfoBlockProps {
title: string;
subtitle: string;
tooltipInfo: string;
}
export const InfoBlock = ({ title, subtitle, tooltipInfo }: InfoBlockProps) => {
return (
<div className="flex flex-col text-center ">
<h3 className="text-4xl">{title}</h3>
<p className="text-zinc-800 dark:text-zinc-300">
{subtitle}
{tooltipInfo ? (
<Tooltip description={tooltipInfo} align="center">
<span>
<Icon
name="info-sign"
className="ml-2 text-zinc-400 dark:text-zinc-600"
/>
</span>
</Tooltip>
) : undefined}
</p>
</div>
);
};
@@ -1 +0,0 @@
export * from './info-panel';
@@ -1,81 +0,0 @@
import React, { useMemo } from 'react';
import type { ReactNode } from 'react';
import { Panel } from '../panel';
import {
CopyWithTooltip,
Icon,
Intent,
Lozenge,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { TruncateInline } from '../truncate/truncate';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
interface InfoPanelProps {
children?: ReactNode | ReactNode[];
title: string;
id: string;
type?: string;
copy?: boolean;
}
export const InfoPanel = ({
children,
title,
id,
type,
copy = true,
}: InfoPanelProps) => {
const { screenSize } = useScreenDimensions();
const isTruncated = useMemo(
() => ['xs', 'sm', 'md', 'lg'].includes(screenSize),
[screenSize]
);
const isDesktop = useMemo(() => ['lg'].includes(screenSize), [screenSize]);
const visibleChars = useMemo(() => (isDesktop ? 20 : 10), [isDesktop]);
return (
<Panel>
<section className="flex justify-between items-center items-center">
<div>
<div className="flex gap-3 items-center">
<h3 className="text-lg font-medium">{title}</h3>
{isTruncated ? (
<TruncateInline
text={id}
startChars={visibleChars}
endChars={visibleChars}
className="text-black dark:text-zinc-200"
/>
) : (
<p
title={id}
className="text-black dark:text-zinc-200 truncate ..."
>
{id}
</p>
)}
{type && (
<Lozenge
className="text-xs leading-relaxed cursor-auto"
variant={Intent.None}
>
<Tooltip side="top" description={type} align="center">
<span>{type}</span>
</Tooltip>
</Lozenge>
)}
</div>
<div>{children}</div>
</div>
{copy && (
<CopyWithTooltip text={id}>
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
<Icon name="duplicate" />
</button>
</CopyWithTooltip>
)}
</section>
</Panel>
);
};
@@ -1 +0,0 @@
export * from './nested-data-list';
@@ -1,110 +0,0 @@
import { render, waitFor } from '@testing-library/react';
import {
BORDER_COLOURS,
NestedDataList,
sortNestedDataByChildren,
} from './nested-data-list';
import userEvent from '@testing-library/user-event';
const mockData = {
nonce: '5980890939790185837',
validatorHeartbeat: {
nodeId: 'e07d2cd299659590c16ec1cc1c69936ad747083c379ea6b6cfeaa6e22c8af0cb',
ethereumSignature: {
value:
'8b157cb43a716ad541065f643e38cd92d7b1857c7beeb7d81e878be4f9a48f5a346be973e478eba3c18907888678625ba6700b086f65406d5ce0af0cae1d419300',
algo: 'eth',
version: 0,
},
vegaSignature: {
value:
'abffba6ddf07b3a732214dd780c35c94f7f0aeb9c5e9ce7d1a3ee641926a6ac568a2ecf0160c859633d327712c7a1b1590db4245d40646559a1ab2cc44d6fa01',
algo: 'vega/ed25519',
version: 0,
},
},
blockHeight: '174534',
};
describe('NestedDataList', () => {
it('should display the parent as a button', () => {
const tree = render(<NestedDataList data={mockData} />);
const { getAllByRole } = tree;
expect(getAllByRole('button')[0]).toHaveTextContent('Validator Heartbeat');
});
it('should display the children when a row is clicked', async () => {
const tree = render(<NestedDataList data={mockData} />);
const user = userEvent.setup();
const { getAllByRole } = tree;
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
const nestedContainer = parent[0].querySelector('[aria-hidden]');
const expandBtn = parent[0].querySelector('button');
expect(nestedContainer).toHaveAttribute('aria-hidden', 'true');
await user.click(expandBtn as HTMLButtonElement);
await waitFor(() => nestedContainer);
expect(nestedContainer).toHaveAttribute('aria-hidden', 'false');
});
it('add border to the title of the parent', () => {
const tree = render(<NestedDataList data={mockData} />);
const { getAllByRole } = tree;
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
expect(parent[0]).toHaveClass('pl-4 border-l-4');
});
it('add a border the children with the same colour', () => {
const tree = render(<NestedDataList data={mockData} />);
const { getAllByRole } = tree;
const parent = getAllByRole('listitem', { name: 'Validator Heartbeat' });
expect(parent[0].querySelector('li')).toHaveClass('pl-4 border-l-4 pt-2');
});
it('should repeat the border colours in the correct order', () => {
const colourMockData = {
t0: {
t1: {
t2: {
t3: {
t4: {
t5: {
t6: {
t7: {
t8: {
hello: 'world',
},
},
},
},
},
},
},
},
},
};
const tree = render(<NestedDataList data={colourMockData} />);
const { getByTestId } = tree;
for (let i = 0; i < 8; i++) {
const item = getByTestId(`T${i}`);
const expected = BORDER_COLOURS.dark[i % 5];
expect(item.style.borderColor.toUpperCase()).toBe(expected);
}
});
it('should sort the data by values with children', () => {
const mockData = {
nonce: '5980890939790185837',
validatorHeartbeat: {
nodeId:
'e07d2cd299659590c16ec1cc1c69936ad747083c379ea6b6cfeaa6e22c8af0cb',
someArray: ['0', '1', '2'],
},
blockHeight: '174534',
};
const expected = ['nonce', 'blockHeight', 'validatorHeartbeat'];
const result = sortNestedDataByChildren(mockData);
expect(result).toStrictEqual(expected);
});
});
@@ -1,178 +0,0 @@
import React, { useCallback, useContext, useMemo, useState } from 'react';
import classNames from 'classnames';
import isObject from 'lodash/isObject';
import { ThemeContext } from '@vegaprotocol/react-helpers';
import { Icon } from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import { VegaColours } from '@vegaprotocol/tailwindcss-config';
import isArray from 'lodash/isArray';
export type UnknownObject = Record<string, unknown>;
export type UnknownArray = unknown[];
interface NestedDataListProps {
data: UnknownObject | UnknownArray;
level?: number;
}
interface NestedDataListItemProps {
label: string;
value: unknown;
index: number;
}
export const BORDER_COLOURS = {
dark: [
VegaColours.pink.dark,
VegaColours.purple.dark,
VegaColours.green.dark,
VegaColours.blue.dark,
VegaColours.yellow.dark,
],
light: [
VegaColours.pink.DEFAULT,
VegaColours.purple.DEFAULT,
VegaColours.green.DEFAULT,
VegaColours.blue.DEFAULT,
VegaColours.yellow.DEFAULT,
],
};
const camelToTitle = (text: string) => {
const result = text.replace(/([A-Z])/g, ' $1');
return result.charAt(0).toUpperCase() + result.slice(1);
};
const getBorderColour = (index: number, theme: keyof typeof BORDER_COLOURS) => {
const colours = BORDER_COLOURS[theme];
const length = colours.length;
const modulo = index % length;
return colours[modulo];
};
const NestedDataListItem = ({
label,
value,
index,
}: NestedDataListItemProps) => {
const [isCollapsed, setCollapsed] = useState(true);
const toggleVisible = useCallback(
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.stopPropagation();
setCollapsed(!isCollapsed);
},
[isCollapsed]
);
const hasChildren = isObject(value) && !!Object.keys(value).length;
const title = useMemo(() => camelToTitle(label), [label]);
const theme = useContext(ThemeContext);
const currentLevelBorder = useMemo(
() => getBorderColour(index, theme),
[index, theme]
);
const nextLevelBorder = useMemo(
() => getBorderColour(index + 1, theme),
[index, theme]
);
const isArr = isArray(value);
const listItemClasses = classNames('pl-4 border-l-4', {
'pt-10 last:pb-0': hasChildren,
'first:pt-0': hasChildren && !index,
'pt-2': !hasChildren && index,
});
const titleClasses = classNames({
'text-xl pl-4 border-l-4 font-alpha': hasChildren,
'text-base font-medium whitespace-nowrap': !hasChildren,
});
return (
<li
data-testid={title}
title={title}
className={listItemClasses}
style={{ borderColor: currentLevelBorder }}
>
<div className="flex flex-wrap">
<h4 className={titleClasses} style={{ borderColor: nextLevelBorder }}>
{hasChildren ? (
<button
className="flex items-center gap-2"
type="button"
onClick={toggleVisible}
>
<span className="">{title}</span>
<small className="px-1 text-sm rounded bg-vega-light-200 dark:bg-vega-dark-200">
{isArr ? 'array' : typeof value}
</small>
<Icon
name={
isCollapsed ? IconNames.CHEVRON_DOWN : IconNames.CHEVRON_UP
}
/>
</button>
) : (
<span className="mr-2">{title}:</span>
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
</div>
{hasChildren && (
<div aria-hidden={isCollapsed} className={isCollapsed ? 'hidden' : ''}>
<NestedDataList
data={value as UnknownObject | UnknownArray}
level={index + 1}
/>
</div>
)}
</li>
);
};
export const sortNestedDataByChildren = (data: UnknownObject | UnknownArray) =>
Object.keys(data)
.filter((key) => key)
.sort((a, b) => {
const isArr = isArray(data);
const valA = isArr ? data[+a] : data[a];
const valB = isArr ? data[+b] : data[b];
const hasChildrenA = isObject(valA) && !!Object.keys(valA).length;
const hasChildrenB = isObject(valB) && !!Object.keys(valB).length;
if (hasChildrenA && !hasChildrenB) {
return 1;
}
if (!hasChildrenA && hasChildrenB) {
return -1;
}
return 0;
});
export const NestedDataList = ({ data, level = 0 }: NestedDataListProps) => {
const nestedItems = useMemo(() => {
const sortedData = sortNestedDataByChildren(data);
const isArr = isArray(data);
if (sortedData.length) {
return sortedData.map((key) => (
<NestedDataListItem
key={key}
label={key}
value={isArr ? data[Number(key)] : data[key]}
index={level}
/>
));
}
return null;
}, [data, level]);
return <ul className="list-none">{nestedItems}</ul>;
};
@@ -1 +0,0 @@
export * from './page-header';
@@ -1,49 +0,0 @@
import React from 'react';
import type { ReactNode } from 'react';
import { TruncateInline } from '../truncate/truncate';
import { CopyWithTooltip, Icon } from '@vegaprotocol/ui-toolkit';
interface PageHeaderProps {
title: string;
truncateStart?: number;
truncateEnd?: number;
copy?: boolean;
prefix?: string | ReactNode;
className?: string;
}
export const PageHeader = ({
prefix,
title,
truncateStart,
truncateEnd,
copy = false,
className,
}: PageHeaderProps) => {
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha';
return (
<header className={className}>
<span className={`${titleClasses} block`}>{prefix}</span>
<div className="flex items-center gap-x-4">
<h2 className={titleClasses}>
{truncateStart && truncateEnd ? (
<TruncateInline
text={title}
startChars={truncateStart}
endChars={truncateEnd}
/>
) : (
title
)}
</h2>
{copy && (
<CopyWithTooltip data-testid="copy-to-clipboard" text={title}>
<button className="bg-zinc-100 dark:bg-zinc-900 rounded-sm py-2 px-3">
<Icon name="duplicate" className="" />
</button>
</CopyWithTooltip>
)}
</div>
</header>
);
};
@@ -1 +0,0 @@
export * from './panel';
@@ -1,17 +0,0 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
interface PanelProps {
children: ReactNode;
className?: string;
}
export const Panel = ({ children, className }: PanelProps) => (
<div
className={classNames(
'border border-zinc-200 dark:border-zinc-800 rounded-md p-5 mb-5',
className
)}
>
{children}
</div>
);
@@ -14,7 +14,7 @@ export const RouteTitle = ({
}: RouteTitleProps) => {
const classes = classnames(
'font-alpha',
'text-4xl',
'text-2xl',
'uppercase',
'mb-8',
className
@@ -1,189 +0,0 @@
import {
detectTypeByFetching,
detectTypeFromQuery,
getSearchType,
isBlock,
isHexadecimal,
isNetworkParty,
isNonHex,
SearchTypes,
toHex,
toNonHex,
} from './detect-search';
import { DATA_SOURCES } from '../../config';
global.fetch = jest.fn();
describe('Detect Search', () => {
it("should detect that it's a hexadecimal", () => {
const expected = true;
const testString =
'0x073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
const actual = isHexadecimal(testString);
expect(actual).toBe(expected);
});
it("should detect that it's not hexadecimal", () => {
const expected = true;
const testString =
'073ceaab59e5f2dd0561dec4883e7ee5bc7165cd4de34717a3ab8f2cbe3007f9';
const actual = isNonHex(testString);
expect(actual).toBe(expected);
});
it("should detect that it's a network party", () => {
const expected = true;
const testString = 'network';
const actual = isNetworkParty(testString);
expect(actual).toBe(expected);
});
it("should detect that it's a block", () => {
const expected = true;
const testString = '3188';
const actual = isBlock(testString);
expect(actual).toBe(expected);
});
it('should convert from non-hex to hex', () => {
const expected = '0x123';
const testString = '123';
const actual = toHex(testString);
expect(actual).toBe(expected);
});
it('should convert from hex to non-hex', () => {
const expected = '123';
const testString = '0x123';
const actual = toNonHex(testString);
expect(actual).toBe(expected);
});
it("should detect type client side from query if it's a hexadecimal", () => {
const expected = [SearchTypes.Party, SearchTypes.Transaction];
const testString =
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a non hex", () => {
const expected = [SearchTypes.Party, SearchTypes.Transaction];
const testString =
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a network party", () => {
const expected = [SearchTypes.Party];
const testString = 'network';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("should detect type client side from query if it's a block (number)", () => {
const expected = [SearchTypes.Block];
const testString = '23432';
const actual = detectTypeFromQuery(testString);
expect(actual).toStrictEqual(expected);
});
it("detectTypeByFetching should call fetch with non-hex query it's a transaction", async () => {
const query = '0xabc';
const type = SearchTypes.Transaction;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
transaction: {
hash: query,
},
}),
})
)
);
const result = await detectTypeByFetching(query);
expect(fetch).toHaveBeenCalledWith(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(query)}`
);
expect(result).toBe(type);
});
it("detectTypeByFetching should call fetch with non-hex query it's a party", async () => {
const query = 'abc';
const type = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: false,
})
)
);
const result = await detectTypeByFetching(query);
expect(result).toBe(type);
});
it('getSearchType should return party from fetch response', async () => {
const query =
'0x4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = SearchTypes.Party;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: false,
})
)
);
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return transaction from fetch response', async () => {
const query =
'4624293CFE3D8B67A0AB448BAFF8FBCF1A1B770D9D5F263761D3D6CBEA94D97F';
const expected = SearchTypes.Transaction;
// @ts-ignore issue related to polyfill
fetch.mockImplementation(
jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
transaction: {
hash: query,
},
}),
})
)
);
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return undefined from transaction response', async () => {
const query = 'u';
const expected = undefined;
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return block if query is number', async () => {
const query = '123';
const expected = SearchTypes.Block;
const result = await getSearchType(query);
expect(result).toBe(expected);
});
it('getSearchType should return party if query is network', async () => {
const query = 'network';
const expected = SearchTypes.Party;
const result = await getSearchType(query);
expect(result).toBe(expected);
});
});
@@ -1,143 +0,0 @@
import { DATA_SOURCES } from '../../config';
import type { BlockExplorerTransaction } from '../../routes/types/block-explorer-response';
export enum SearchTypes {
Transaction = 'transaction',
Party = 'party',
Block = 'block',
Order = 'order',
}
export const TX_LENGTH = 64;
export const isHexadecimal = (search: string) =>
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
export const isNonHex = (search: string) =>
!search.startsWith('0x') && search.length === TX_LENGTH;
export const isBlock = (search: string) => !Number.isNaN(Number(search));
export const isNetworkParty = (search: string) => search === 'network';
export const toHex = (query: string) =>
isHexadecimal(query) ? query : `0x${query}`;
export const toNonHex = (query: string) =>
isNonHex(query) ? query : `${query.replace('0x', '')}`;
export const detectTypeFromQuery = (
query: string
): SearchTypes[] | undefined => {
const i = query.toLowerCase();
if (isHexadecimal(i) || isNonHex(i)) {
return [SearchTypes.Party, SearchTypes.Transaction];
} else if (isNetworkParty(i)) {
return [SearchTypes.Party];
} else if (isBlock(i)) {
return [SearchTypes.Block];
}
return undefined;
};
export const detectTypeByFetching = async (
query: string
): Promise<SearchTypes | undefined> => {
const hash = toNonHex(query);
const request = await fetch(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
);
if (request?.ok) {
const body: BlockExplorerTransaction = await request.json();
if (body?.transaction) {
return SearchTypes.Transaction;
}
}
return SearchTypes.Party;
};
// Code commented out because the current solution to detect a hex is temporary (by process of elimination)
// export const detectTypeByFetching = async (
// query: string,
// type: SearchTypes
// ): Promise<SearchTypes | undefined> => {
// const TYPES = [SearchTypes.Party, SearchTypes.Transaction];
//
// if (!TYPES.includes(type)) {
// throw new Error('Search type provided not recognised');
// }
//
// if (type === SearchTypes.Transaction) {
// const hash = toNonHex(query);
// const request = await fetch(
// `${DATA_SOURCES.blockExplorerUrl}/transactions/${hash}`
// );
//
// if (request?.ok) {
// const body: BlockExplorerTransaction = await request.json();
//
// if (body?.transaction) {
// return SearchTypes.Transaction;
// }
// }
// } else if (type === SearchTypes.Party) {
// const party = toNonHex(query);
//
// const request = await fetch(
// `${DATA_SOURCES.blockExplorerUrl}/transactions?limit=1&filters[tx.submitter]=${party}`
// );
//
// if (request.ok) {
// const body: BlockExplorerTransactions = await request.json();
//
// if (body?.transactions?.length) {
// return SearchTypes.Party;
// }
// }
// }
//
// return undefined;
// };
// export const getSearchType = async (
// query: string
// ): Promise<SearchTypes | undefined> => {
// const searchTypes = detectTypeFromQuery(query);
// const hasResults = searchTypes?.length;
//
// if (hasResults) {
// if (hasResults > 1) {
// const promises = searchTypes.map((type) =>
// detectTypeByFetching(query, type)
// );
// const results = await Promise.all(promises);
// return results.find((result) => result !== undefined);
// }
//
// return searchTypes[0];
// }
//
// return undefined;
// };
export const getSearchType = async (
query: string
): Promise<SearchTypes | undefined> => {
const searchTypes = detectTypeFromQuery(query);
const hasResults = searchTypes?.length;
if (hasResults) {
if (hasResults > 1) {
return await detectTypeByFetching(query);
}
return searchTypes[0];
}
return undefined;
};
@@ -2,23 +2,14 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Search } from './search';
import { MemoryRouter } from 'react-router-dom';
import { Routes } from '../../routes/route-names';
import { SearchTypes, getSearchType } from './detect-search';
const mockedNavigate = jest.fn();
const mockGetSearchType = getSearchType as jest.MockedFunction<
typeof getSearchType
>;
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedNavigate,
}));
jest.mock('./detect-search', () => ({
...jest.requireActual('./detect-search'),
getSearchType: jest.fn(),
}));
beforeEach(() => {
mockedNavigate.mockClear();
});
@@ -48,10 +39,9 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Transaction type is not recognised'
"Something doesn't look right"
);
});
it('should render error if no input is given', async () => {
render(renderComponent());
const { button } = getInputs();
@@ -59,14 +49,47 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Search query required'
'Search required'
);
});
it('should render error if transaction is not hex', async () => {
render(renderComponent());
const { button, input } = getInputs();
fireEvent.change(input, {
target: {
value:
'0x123456789012345678901234567890123456789012345678901234567890123Q',
},
});
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Transaction is not hexadecimal'
);
});
it('should render error if transaction is not hex and does not have leading 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
fireEvent.change(input, {
target: {
value:
'123456789012345678901234567890123456789012345678901234567890123Q',
},
});
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'Transaction is not hexadecimal'
);
});
it('should redirect to transactions page', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
fireEvent.change(input, {
target: {
value:
@@ -85,7 +108,6 @@ describe('Search', () => {
it('should redirect to transactions page without proceeding 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
fireEvent.change(input, {
target: {
value:
@@ -101,48 +123,9 @@ describe('Search', () => {
});
});
it('should redirect to parties page', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
fireEvent.change(input, {
target: {
value:
'0x1234567890123456789012345678901234567890123456789012345678901234',
},
});
fireEvent.click(button);
await waitFor(() => {
expect(mockedNavigate).toBeCalledWith(
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
);
});
});
it('should redirect to parties page without proceeding 0x', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Party);
fireEvent.change(input, {
target: {
value:
'1234567890123456789012345678901234567890123456789012345678901234',
},
});
fireEvent.click(button);
await waitFor(() => {
expect(mockedNavigate).toBeCalledWith(
`${Routes.PARTIES}/0x1234567890123456789012345678901234567890123456789012345678901234`
);
});
});
it('should redirect to blocks page if passed a number', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Block);
fireEvent.change(input, {
target: {
value: '123',
@@ -1,54 +1,55 @@
import React, { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { Button, Input, InputError } from '@vegaprotocol/ui-toolkit';
import { Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { getSearchType, SearchTypes, toHex } from './detect-search';
import { Routes } from '../../routes/route-names';
const TX_LENGTH = 64;
interface FormFields {
search: string;
}
const isPrependedTransaction = (search: string) =>
search.startsWith('0x') && search.length === 2 + TX_LENGTH;
const isTransaction = (search: string) =>
!search.startsWith('0x') && search.length === TX_LENGTH;
const isBlock = (search: string) => !Number.isNaN(Number(search));
export const Search = () => {
const { register, handleSubmit } = useForm<FormFields>();
const navigate = useNavigate();
const [error, setError] = useState<Error | null>(null);
const onSubmit = useCallback(
async (fields: FormFields) => {
const [error, setError] = React.useState<Error | null>(null);
const onSubmit = React.useCallback(
(fields: FormFields) => {
setError(null);
const query = fields.search;
if (!query) {
return setError(new Error(t('Search query required')));
}
const result = await getSearchType(query);
const urlAsHex = toHex(query);
const unrecognisedError = new Error(
t('Transaction type is not recognised')
);
if (result) {
switch (result) {
case SearchTypes.Party:
return navigate(`${Routes.PARTIES}/${urlAsHex}`);
case SearchTypes.Transaction:
return navigate(`${Routes.TX}/${urlAsHex}`);
case SearchTypes.Block:
return navigate(`${Routes.BLOCKS}/${Number(query)}`);
default:
return setError(unrecognisedError);
const search = fields.search;
if (!search) {
setError(new Error(t('Search required')));
} else if (isPrependedTransaction(search)) {
if (Number.isNaN(Number(search))) {
setError(new Error(t('Transaction is not hexadecimal')));
} else {
navigate(`${Routes.TX}/${search}`);
}
} else if (isTransaction(search)) {
if (Number.isNaN(Number(`0x${search}`))) {
setError(new Error(t('Transaction is not hexadecimal')));
} else {
navigate(`${Routes.TX}/0x${search}`);
}
} else if (isBlock(search)) {
navigate(`${Routes.BLOCKS}/${Number(search)}`);
} else {
setError(new Error(t("Something doesn't look right")));
}
return setError(unrecognisedError);
},
[navigate]
);
return (
<form
onSubmit={handleSubmit(onSubmit)}
@@ -58,24 +59,21 @@ export const Search = () => {
{t('Search by block number or transaction hash')}
</label>
<div className="flex items-stretch gap-2">
<div className="flex grow relative">
<Input
{...register('search')}
id="search"
data-testid="search"
className="text-white"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number, party id or transaction hash')}
/>
{error?.message && (
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b">
<InputError data-testid="search-error" intent="danger">
{error.message}
</InputError>
</div>
)}
</div>
<Input
{...register('search')}
id="search"
data-testid="search"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number or transaction hash')}
/>
{error?.message && (
<div className="absolute top-[100%] flex-1 w-full">
<InputError data-testid="search-error" intent="danger">
{error.message}
</InputError>
</div>
)}
<Button type="submit" size="sm" data-testid="search-button">
{t('Search')}
</Button>
@@ -20,7 +20,7 @@ export const TruncatedLink = ({
text={text}
startChars={startChars}
endChars={endChars}
className="underline uppercase underline-offset-2"
className="font-mono font-bold underline"
/>
</Link>
);
@@ -1,35 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: TxsStats
// ====================================================
export interface TxsStats_statistics {
__typename: "Statistics";
/**
* Average number of orders added per blocks
*/
averageOrdersPerBlock: string;
/**
* Number of orders per seconds
*/
ordersPerSecond: string;
/**
* Number of transaction processed per block
*/
txPerBlock: string;
/**
* Number of the trades per seconds
*/
tradesPerSecond: string;
}
export interface TxsStats {
/**
* Get statistics about the Vega node
*/
statistics: TxsStats_statistics;
}
@@ -3,4 +3,3 @@ export { BlockTxsData } from './block-txs-data';
export { TxOrderType } from './tx-order-type';
export { TxsInfiniteList } from './txs-infinite-list';
export { TxsInfiniteListItem } from './txs-infinite-list-item';
export { TxsStatsInfo } from './txs-stats-info';
@@ -1,3 +1,5 @@
import { Lozenge, Intent } from '@vegaprotocol/ui-toolkit';
interface TxOrderTypeProps {
orderType: string;
className?: string;
@@ -32,13 +34,10 @@ const displayString: StringMap = {
ValidatorHeartbeat: 'Validator Heartbeat',
};
export const TxOrderType = ({ orderType }: TxOrderTypeProps) => {
export const TxOrderType = ({ orderType, className }: TxOrderTypeProps) => {
return (
<div
data-testid="tx-type"
className="text-sm rounded-md leading-none px-2 py-2 inline-block text-white dark:text-white bg-zinc-800 dark:bg-zinc-800 "
>
<Lozenge data-testid="tx-type" variant={Intent.None} className={className}>
{displayString[orderType] || orderType}
</div>
</Lozenge>
);
};
@@ -1,89 +1,92 @@
import { TxsInfiniteListItem } from './txs-infinite-list-item';
import { render, screen } from '@testing-library/react';
import { render, screen, fireEvent, act } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
describe('Txs infinite list item', () => {
it('should display "missing vital data" if "type" data missing', () => {
it('should display "missing vital data" if "Type" data missing', () => {
render(
<TxsInfiniteListItem
type={undefined}
submitter="test"
hash=""
index={0}
block="1"
// @ts-ignore testing deliberate failure
Type={undefined}
Command={'test'}
Sig={'test'}
PubKey={'test'}
Nonce={1}
TxHash={'test'}
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "hash" data missing', () => {
it('should display "missing vital data" if "Command" data missing', () => {
render(
<TxsInfiniteListItem
type="test"
submitter="test"
hash={undefined}
index={0}
block="1"
Type={'test'}
// @ts-ignore testing deliberate failure
Command={undefined}
Sig={'test'}
PubKey={'test'}
Nonce={1}
TxHash={'test'}
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "submitter" data missing', () => {
it('should display "missing vital data" if "Pubkey" data missing', () => {
render(
<TxsInfiniteListItem
type="test"
submitter={undefined}
hash="test"
index={0}
block="1"
Type={'test'}
Command={'test'}
Sig={'test'}
// @ts-ignore testing deliberate failure
PubKey={undefined}
Nonce={1}
TxHash={'test'}
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "block" data missing', () => {
it('should display "missing vital data" if "TxHash" data missing', () => {
render(
<TxsInfiniteListItem
type="test"
submitter="test"
hash="test"
index={0}
block={undefined}
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "index" data missing', () => {
render(
<TxsInfiniteListItem
type="test"
submitter="test"
hash="test"
index={undefined}
block="1"
Type={'test'}
Command={'test'}
Sig={'test'}
PubKey={'test'}
Nonce={1}
// @ts-ignore testing deliberate failure
TxHash={undefined}
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('renders data correctly', () => {
const testCommandData = JSON.stringify({
test: 'test of command data',
});
render(
<MemoryRouter>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
index={1}
block="1"
Type={'testType'}
Command={testCommandData}
Sig={'testSig'}
PubKey={'testPubKey'}
Nonce={1}
TxHash={'testTxHash'}
/>
</MemoryRouter>
);
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-index')).toHaveTextContent('1');
expect(screen.getByTestId('type')).toHaveTextContent('testType');
const button = screen.getByTestId('command-details');
act(() => {
fireEvent.click(button);
});
expect(screen.getByText('"test of command data"')).toBeInTheDocument();
});
});
@@ -1,79 +1,69 @@
import React from 'react';
import React, { useState } from 'react';
import {
Dialog,
Icon,
Intent,
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
import { TruncatedLink } from '../truncate/truncated-link';
import { Routes } from '../../routes/route-names';
import { TxOrderType } from './tx-order-type';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import { toHex } from '../search/detect-search';
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
const TRUNCATE_LENGTH = 14;
export const TxsInfiniteListItem = ({
hash,
submitter,
type,
block,
index,
}: Partial<BlockExplorerTransactionResult>) => {
if (
!hash ||
!submitter ||
!type ||
block === undefined ||
index === undefined
) {
TxHash,
PubKey,
Type,
Command,
}: ChainExplorerTxResponse) => {
const [open, setOpen] = useState(false);
if (!TxHash || !PubKey || !Type || !Command) {
return <div>Missing vital data</div>;
}
return (
<div
data-testid="transaction-row"
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
className="grid grid-cols-[repeat(2,_1fr)_240px] gap-12 w-full border-t border-neutral-600 dark:border-neutral-800 py-8 txs-infinite-list-item"
>
<div
className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="tx-hash"
>
<span className="xl:hidden uppercase text-zinc-500">ID:&nbsp;</span>
<div className="whitespace-nowrap overflow-scroll" data-testid="tx-hash">
<TruncatedLink
to={`/${Routes.TX}/${toHex(hash)}`}
text={hash}
to={`/${Routes.TX}/${TxHash}`}
text={TxHash}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div className="whitespace-nowrap overflow-scroll" data-testid="pub-key">
<TruncatedLink
to={`/${Routes.PARTIES}/${PubKey}`}
text={PubKey}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div
className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="pub-key"
className="flex justify-between whitespace-nowrap overflow-scroll"
data-testid="type"
>
<span className="xl:hidden uppercase text-zinc-500">By:&nbsp;</span>
<TruncatedLink
to={`/${Routes.PARTIES}/${submitter}`}
text={submitter}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} />
</div>
<div
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
data-testid="tx-block"
>
<span className="xl:hidden uppercase text-zinc-500">Block:&nbsp;</span>
<TruncatedLink
to={`/${Routes.BLOCKS}/${block}`}
text={block}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
data-testid="tx-index"
>
<span className="xl:hidden uppercase text-zinc-500">Index:&nbsp;</span>
{index}
<TxOrderType orderType={Type} />
<button
title="More details"
onClick={() => setOpen(true)}
data-testid="command-details"
>
<Icon name="search-template" />
</button>
<Dialog
open={open}
onChange={(isOpen) => setOpen(false)}
intent={Intent.None}
>
<SyntaxHighlighter data={JSON.parse(Command)} />
</Dialog>
</div>
</div>
);
@@ -1,34 +1,18 @@
import { TxsInfiniteList } from './txs-infinite-list';
import { render, screen, fireEvent, act } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
const generateTxs = (number: number) => {
return Array.from(Array(number)).map((_) => ({
block: '87901',
index: 2,
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
submitter:
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
type: 'Submit Order',
code: 0,
cursor: '87901.2',
command: {
nonce: '4214037379192575529',
blockHeight: '87898',
orderSubmission: {
marketId:
'b4d0a070f5cc73a7d53b23d6f63f8cb52e937ed65d2469a3af4cc1e80e155fcf',
price: '14525946',
size: '54',
side: 'SIDE_SELL',
timeInForce: 'TIME_IN_FORCE_GTT',
expiresAt: '1664966445481288736',
type: 'TYPE_LIMIT',
reference: 'traderbot',
peggedOrder: null,
},
},
Type: 'ChainEvent',
Command:
'{"txId":"0xc8941ac4ea989988cb8f72e8fdab2e2009376fd17619491439d36b519d27bc93","nonce":"1494","stakingEvent":{"index":"263","block":"14805346","stakeDeposited":{"ethereumAddress":"0x2e5fe63e5d49c26998cf4bfa9b64de1cf9ae7ef2","vegaPublicKey":"657c2a8a5867c43c831e24820b7544e2fdcc1cf610cfe0ece940fe78137400fd","amount":"38471116086510047870875","blockTime":"1652968806"}}}',
Sig: 'fe7624ab742c492cf1e667e79de4777992aca8e093c8707e1f22685c3125c6082cd21b85cd966a61ad4ca0cca2f8bed3082565caa5915bc3b2f78c1ae35cac0b',
PubKey:
'0x7d69327393cdfaaae50e5e215feca65273eafabfb38f32b8124e66298af346d5',
Nonce: 18296387398179254000,
TxHash:
'0x9C753FA6325F7A40D9C4FA5C25E24476C54613E12B1FA2DD841E3BB00D088B77',
}));
};
@@ -57,15 +41,13 @@ describe('Txs infinite list', () => {
error={Error('test error!')}
/>
);
expect(
screen.getByText('Cannot fetch transaction: Error: test error!')
).toBeInTheDocument();
expect(screen.getByText('Error: test error!')).toBeInTheDocument();
});
it('item renders data of n length into list of n length', () => {
// Provided the number of items doesn't exceed the 30 it initially
// desires, all txs will initially render
const txs = generateTxs(7);
const txs = generateTxs(10);
render(
<MemoryRouter>
<TxsInfiniteList
@@ -82,7 +64,7 @@ describe('Txs infinite list', () => {
screen
.getByTestId('infinite-scroll-wrapper')
.querySelectorAll('.txs-infinite-list-item')
).toHaveLength(7);
).toHaveLength(10);
});
it('tries to load more items when required to initially fill the list', () => {
@@ -126,7 +108,7 @@ describe('Txs infinite list', () => {
});
it('loads more items is called when scrolled', () => {
const txs = generateTxs(14);
const txs = generateTxs(20);
const callback = jest.fn();
render(
@@ -143,7 +125,7 @@ describe('Txs infinite list', () => {
act(() => {
fireEvent.scroll(screen.getByTestId('infinite-scroll-wrapper'), {
target: { scrollY: 2000 },
target: { scrollY: 600 },
});
});
@@ -1,21 +1,21 @@
import React from 'react';
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { t, useScreenDimensions } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
interface TxsInfiniteListProps {
hasMoreTxs: boolean;
areTxsLoading: boolean | undefined;
txs: BlockExplorerTransactionResult[] | undefined;
txs: ChainExplorerTxResponse[] | undefined;
loadMoreTxs: () => void;
error: Error | undefined;
className?: string;
}
interface ItemProps {
index: BlockExplorerTransactionResult;
index: ChainExplorerTxResponse;
style: React.CSSProperties;
isLoading: boolean;
error: Error | undefined;
@@ -27,19 +27,19 @@ const NOOP = () => {};
const Item = ({ index, style, isLoading, error }: ItemProps) => {
let content;
if (error) {
content = t(`Cannot fetch transaction: ${error}`);
content = t(`${error}`);
} else if (isLoading) {
content = t('Loading...');
} else {
const { hash, submitter, type, command, block, index: blockIndex } = index;
const { TxHash, PubKey, Type, Command, Sig, Nonce } = index;
content = (
<TxsInfiniteListItem
type={type}
command={command}
submitter={submitter}
hash={hash}
block={block}
index={blockIndex}
Type={Type}
Command={Command}
Sig={Sig}
PubKey={PubKey}
Nonce={Nonce}
TxHash={TxHash}
/>
);
}
@@ -55,9 +55,6 @@ export const TxsInfiniteList = ({
error,
className,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
if (!txs) {
return <div>No items</div>;
}
@@ -74,15 +71,10 @@ export const TxsInfiniteList = ({
return (
<div className={className} data-testid="transactions-list">
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-zinc-500 uppercase">
<div className="col-span-3">
<span className="hidden xl:inline">Transaction &nbsp;</span>
<span>ID</span>
</div>
<div className="col-span-3">Submitted By</div>
<div className="col-span-2">Type</div>
<div className="col-span-1">Block</div>
<div className="col-span-1">Index</div>
<div className="grid grid-cols-[repeat(2,_1fr)_240px] gap-12 w-full mb-8">
<div className="text-lg font-bold">Txn hash</div>
<div className="text-lg font-bold">Party</div>
<div className="text-lg font-bold pl-2">Type</div>
</div>
<div data-testid="infinite-scroll-wrapper">
<InfiniteLoader
@@ -95,7 +87,7 @@ export const TxsInfiniteList = ({
className="List"
height={595}
itemCount={itemCount}
itemSize={isStacked ? 134 : 72}
itemSize={41}
onItemsRendered={onItemsRendered}
ref={ref}
width={'100%'}
@@ -1,80 +0,0 @@
import { gql, useQuery } from '@apollo/client';
import { t } from '@vegaprotocol/react-helpers';
import { useEffect } from 'react';
import { InfoBlock } from '../../components/info-block';
import { Panel } from '../../components/panel';
import type { TxsStats, TxsStats_statistics } from './__generated__/TxsStats';
const STATS_QUERY = gql`
query TxsStats {
statistics {
averageOrdersPerBlock
ordersPerSecond
txPerBlock
tradesPerSecond
}
}
`;
interface StatsMap {
field: keyof TxsStats_statistics;
label: string;
info: string;
}
export const TXS_STATS_MAP: StatsMap[] = [
{
field: 'averageOrdersPerBlock',
label: t('Orders per block'),
info: t(
'Number of new orders processed in the last block. All orders derived from pegged orders and liquidity commitments count as a single order'
),
},
{
field: 'txPerBlock',
label: t('Transactions per block'),
info: t('Number of transactions processed in the last block'),
},
{
field: 'tradesPerSecond',
label: t('Trades per second'),
info: t('Number of trades processed in the last second'),
},
{
field: 'ordersPerSecond',
label: t('Order per second'),
info: t(
'Number of orders processed in the last second. All orders derived from pegged orders and liquidity commitments count as a single order'
),
},
];
interface TxsStatsInfoProps {
className?: string;
}
export const TxsStatsInfo = ({ className }: TxsStatsInfoProps) => {
const { data, startPolling, stopPolling } = useQuery<TxsStats>(STATS_QUERY);
useEffect(() => {
startPolling(1000);
return () => stopPolling();
});
const gridStyles =
'grid grid-rows-2 gap-4 grid-cols-2 xl:gap-8 xl:grid-rows-1 xl:grid-cols-4';
return (
<Panel className={className}>
<section className={gridStyles}>
{TXS_STATS_MAP.map((field) => (
<InfoBlock
subtitle={field.label}
tooltipInfo={field.info}
title={data?.statistics[field.field] || ''}
/>
))}
</section>
</Panel>
);
};
-1
View File
@@ -13,7 +13,6 @@ export const ENV = {
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
dataSources: {
chainExplorerUrl: windowOrDefault('NX_CHAIN_EXPLORER_URL'),
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
tendermintUrl: windowOrDefault('NX_TENDERMINT_URL'),
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
},
@@ -1,91 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { useFetch } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
hasMoreTxs: boolean;
lastCursor: string;
}
export interface IUseTxsData {
limit?: number;
filters?: string;
}
interface IGetTxsDataUrl {
limit?: string;
filters?: string;
}
export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
const url = new URL(`${DATA_SOURCES.blockExplorerUrl}/transactions`);
if (limit) {
url.searchParams.append('limit', limit);
}
if (filters) {
url.searchParams.append('filters', filters);
}
return url;
};
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
useState<TxsStateProps>({
txsData: [],
hasMoreTxs: true,
lastCursor: '',
});
const url = getTxsDataUrl({ limit: limit?.toString(), filters });
const {
state: { data, error, loading },
refetch,
} = useFetch<BlockExplorerTransactions>(url.href, {}, false);
useEffect(() => {
if (data?.transactions?.length) {
setTxsState((prev) => ({
txsData: [...prev.txsData, ...data.transactions],
hasMoreTxs: true,
lastCursor:
data.transactions[data.transactions.length - 1].cursor || '',
}));
}
}, [setTxsState, data?.transactions]);
const loadTxs = useCallback(() => {
return refetch({
limit: limit,
before: lastCursor,
});
}, [lastCursor, limit, refetch]);
const refreshTxs = useCallback(async () => {
setTxsState((prev) => ({
...prev,
lastCursor: '',
hasMoreTxs: true,
txsData: [],
}));
}, [setTxsState]);
return {
data,
loading,
error,
txsData,
hasMoreTxs,
lastCursor,
refreshTxs,
loadTxs,
};
};
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: AssetsQuery
@@ -12,7 +12,7 @@ import { AccountType } from "./../../../../../../../libs/types/src/__generated__
export interface AssetsQuery_assetsConnection_edges_node_source_ERC20 {
__typename: "ERC20";
/**
* The address of the erc20 contract
* The address of the ERC20 contract
*/
contractAddress: string;
}
@@ -54,7 +54,7 @@ export interface AssetsQuery_assetsConnection_edges_node_infrastructureFeeAccoun
export interface AssetsQuery_assetsConnection_edges_node {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
@@ -66,11 +66,11 @@ export interface AssetsQuery_assetsConnection_edges_node {
*/
symbol: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
/**
* The origin source of the asset (e.g: an erc20 asset)
* The origin source of the asset (e.g: an ERC20 asset)
*/
source: AssetsQuery_assetsConnection_edges_node_source;
/**
@@ -94,7 +94,7 @@ export interface AssetsQuery_assetsConnection {
export interface AssetsQuery {
/**
* The list of all assets in use in the vega network or the specified asset if id is provided
* The list of all assets in use in the Vega network or the specified asset if ID is provided
*/
assetsConnection: AssetsQuery_assetsConnection;
assetsConnection: AssetsQuery_assetsConnection | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
export type AssetsQueryQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type AssetsQueryQuery = { __typename?: 'Query', assetsConnection: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount: { __typename?: 'Account', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } } } | null> | null } };
export type AssetsQueryQuery = { __typename?: 'Query', assetsConnection?: { __typename?: 'AssetsConnection', edges?: Array<{ __typename?: 'AssetEdge', node: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string }, infrastructureFeeAccount: { __typename?: 'Account', type: Types.AccountType, balance: string, market?: { __typename?: 'Market', id: string } | null } } } | null> | null } | null };
export const AssetsQueryDocument = gql`
@@ -1,5 +1,6 @@
import compact from 'lodash/compact';
import { gql, useQuery } from '@apollo/client';
import { getAssets, t } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
@@ -39,7 +40,7 @@ export const ASSETS_QUERY = gql`
const Assets = () => {
const { data } = useQuery<AssetsQuery>(ASSETS_QUERY);
const assets = getAssets(data);
const assets = compact(data?.assetsConnection?.edges).map((e) => e.node);
return (
<section>
@@ -3,13 +3,29 @@
// @generated
// This file was automatically generated and should not be edited.
import { ProposalState, ProposalRejectionReason, VoteValue } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: ProposalsQuery
// ====================================================
export interface ProposalsQuery_proposals_party {
export interface ProposalsQuery_proposalsConnection_edges_node_rationale {
__typename: "ProposalRationale";
/**
* Title to be used to give a short description of the proposal in lists.
* This is to be between 0 and 100 unicode characters.
* This is mandatory for all proposals.
*/
title: string;
/**
* Description to show a short title / something in case the link goes offline.
* This is to be between 0 and 20k unicode characters.
* This is mandatory for all proposals.
*/
description: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_party {
__typename: "Party";
/**
* Party identifier
@@ -17,11 +33,11 @@ export interface ProposalsQuery_proposals_party {
id: string;
}
export interface ProposalsQuery_proposals_terms_change_NewFreeform {
__typename: "NewFreeform";
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateAsset {
__typename: "UpdateAsset" | "NewFreeform";
}
export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument {
__typename: "InstrumentConfiguration";
/**
* Full and fairly descriptive name for the instrument
@@ -29,20 +45,20 @@ export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument {
name: string;
}
export interface ProposalsQuery_proposals_terms_change_NewMarket {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket {
__typename: "NewMarket";
/**
* New market instrument configuration
*/
instrument: ProposalsQuery_proposals_terms_change_NewMarket_instrument;
instrument: ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument;
}
export interface ProposalsQuery_proposals_terms_change_UpdateMarket {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateMarket {
__typename: "UpdateMarket";
marketId: string;
}
export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset {
__typename: "BuiltinAsset";
/**
* Maximum amount that can be requested by a party through the built-in asset faucet at a time
@@ -50,29 +66,29 @@ export interface ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAs
maxFaucetAmountMint: string;
}
export interface ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20 {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the erc20 contract
* The address of the ERC20 contract
*/
contractAddress: string;
}
export type ProposalsQuery_proposals_terms_change_NewAsset_source = ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20;
export type ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source = ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20;
export interface ProposalsQuery_proposals_terms_change_NewAsset {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset {
__typename: "NewAsset";
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* the source of the new Asset
* The source of the new asset
*/
source: ProposalsQuery_proposals_terms_change_NewAsset_source;
source: ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source;
}
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter {
__typename: "NetworkParameter";
/**
* The name of the network parameter
@@ -84,14 +100,14 @@ export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_ne
value: string;
}
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter {
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter {
__typename: "UpdateNetworkParameter";
networkParameter: ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter;
networkParameter: ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter;
}
export type ProposalsQuery_proposals_terms_change = ProposalsQuery_proposals_terms_change_NewFreeform | ProposalsQuery_proposals_terms_change_NewMarket | ProposalsQuery_proposals_terms_change_UpdateMarket | ProposalsQuery_proposals_terms_change_NewAsset | ProposalsQuery_proposals_terms_change_UpdateNetworkParameter;
export type ProposalsQuery_proposalsConnection_edges_node_terms_change = ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket | ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateMarket | ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset | ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter;
export interface ProposalsQuery_proposals_terms {
export interface ProposalsQuery_proposalsConnection_edges_node_terms {
__typename: "ProposalTerms";
/**
* RFC3339Nano time and date when voting closes for this proposal.
@@ -101,23 +117,24 @@ export interface ProposalsQuery_proposals_terms {
/**
* RFC3339Nano time and date when this proposal is executed (if passed). Note that it has to be after closing date time.
* Constrained by "minEnactInSeconds" and "maxEnactInSeconds" network parameters.
* Note: Optional as free form proposals do not require it.
*/
enactmentDatetime: string;
enactmentDatetime: string | null;
/**
* Actual change being introduced by the proposal - action the proposal triggers if passed and enacted.
*/
change: ProposalsQuery_proposals_terms_change;
change: ProposalsQuery_proposalsConnection_edges_node_terms_change;
}
export interface ProposalsQuery_proposals_votes_yes_votes_party_stake {
__typename: "PartyStake";
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposals_votes_yes_votes_party {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party {
__typename: "Party";
/**
* Party identifier
@@ -126,10 +143,10 @@ export interface ProposalsQuery_proposals_votes_yes_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalsQuery_proposals_votes_yes_votes_party_stake;
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary;
}
export interface ProposalsQuery_proposals_votes_yes_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes {
__typename: "Vote";
/**
* The vote value cast
@@ -138,38 +155,38 @@ export interface ProposalsQuery_proposals_votes_yes_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposals_votes_yes_votes_party;
party: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposals_votes_yes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes {
__typename: "ProposalVoteSide";
/**
* Total tokens of governance token from the votes casted for this side
* Total number of governance tokens from the votes cast for this side
*/
totalTokens: string;
/**
* Total number of votes casted for this side
* Total number of votes cast for this side
*/
totalNumber: string;
/**
* All votes casted for this side
* All votes cast for this side
*/
votes: ProposalsQuery_proposals_votes_yes_votes[] | null;
votes: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes[] | null;
}
export interface ProposalsQuery_proposals_votes_no_votes_party_stake {
__typename: "PartyStake";
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposals_votes_no_votes_party {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party {
__typename: "Party";
/**
* Party identifier
@@ -178,10 +195,10 @@ export interface ProposalsQuery_proposals_votes_no_votes_party {
/**
* The staking information for this Party
*/
stake: ProposalsQuery_proposals_votes_no_votes_party_stake;
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary;
}
export interface ProposalsQuery_proposals_votes_no_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes {
__typename: "Vote";
/**
* The vote value cast
@@ -190,49 +207,53 @@ export interface ProposalsQuery_proposals_votes_no_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposals_votes_no_votes_party;
party: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposals_votes_no {
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no {
__typename: "ProposalVoteSide";
/**
* Total tokens of governance token from the votes casted for this side
* Total number of governance tokens from the votes cast for this side
*/
totalTokens: string;
/**
* Total number of votes casted for this side
* Total number of votes cast for this side
*/
totalNumber: string;
/**
* All votes casted for this side
* All votes cast for this side
*/
votes: ProposalsQuery_proposals_votes_no_votes[] | null;
votes: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes[] | null;
}
export interface ProposalsQuery_proposals_votes {
export interface ProposalsQuery_proposalsConnection_edges_node_votes {
__typename: "ProposalVotes";
/**
* Yes votes cast for this proposal
*/
yes: ProposalsQuery_proposals_votes_yes;
yes: ProposalsQuery_proposalsConnection_edges_node_votes_yes;
/**
* No votes cast for this proposal
*/
no: ProposalsQuery_proposals_votes_no;
no: ProposalsQuery_proposalsConnection_edges_node_votes_no;
}
export interface ProposalsQuery_proposals {
export interface ProposalsQuery_proposalsConnection_edges_node {
__typename: "Proposal";
/**
* Proposal ID that is filled by VEGA once proposal reaches the network
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
/**
* A UUID reference to aid tracking proposals on VEGA
* Rationale behind the proposal
*/
rationale: ProposalsQuery_proposalsConnection_edges_node_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
*/
reference: string;
/**
@@ -250,20 +271,36 @@ export interface ProposalsQuery_proposals {
/**
* Party that prepared the proposal
*/
party: ProposalsQuery_proposals_party;
party: ProposalsQuery_proposalsConnection_edges_node_party;
/**
* Terms of the proposal
*/
terms: ProposalsQuery_proposals_terms;
terms: ProposalsQuery_proposalsConnection_edges_node_terms;
/**
* Votes cast for this proposal
*/
votes: ProposalsQuery_proposals_votes;
votes: ProposalsQuery_proposalsConnection_edges_node_votes;
}
export interface ProposalsQuery_proposalsConnection_edges {
__typename: "ProposalEdge";
/**
* The proposal data
*/
node: ProposalsQuery_proposalsConnection_edges_node;
}
export interface ProposalsQuery_proposalsConnection {
__typename: "ProposalsConnection";
/**
* List of proposals available for the connection
*/
edges: (ProposalsQuery_proposalsConnection_edges | null)[] | null;
}
export interface ProposalsQuery {
/**
* All governance proposals in the VEGA network
* All governance proposals in the Vega network
*/
proposals: ProposalsQuery_proposals[] | null;
proposalsConnection: ProposalsQuery_proposalsConnection | null;
}
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,98 +1,94 @@
import compact from 'lodash/compact';
import { gql, useQuery } from '@apollo/client';
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { RouteTitle } from '../../components/route-title';
import { SubHeading } from '../../components/sub-heading';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { getProposals } from '@vegaprotocol/governance';
import type {
ProposalsQuery,
ProposalsQuery_proposals_terms_change,
ProposalsQuery_proposalsConnection_edges_node,
} from './__generated__/ProposalsQuery';
export function getProposalName(change: ProposalsQuery_proposals_terms_change) {
if (change.__typename === 'NewAsset') {
return t(`New asset: ${change.symbol}`);
} else if (change.__typename === 'NewMarket') {
return t(`New market: ${change.instrument.name}`);
} else if (change.__typename === 'UpdateMarket') {
return t(`Update market: ${change.marketId}`);
} else if (change.__typename === 'UpdateNetworkParameter') {
return t(`Update network: ${change.networkParameter.key}`);
}
return t('Unknown proposal');
}
const PROPOSAL_QUERY = gql`
const PROPOSALS_QUERY = gql`
query ProposalsQuery {
proposals {
id
reference
state
datetime
rejectionReason
party {
id
}
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
instrument {
name
}
proposalsConnection {
edges {
node {
id
rationale {
title
description
}
... on UpdateMarket {
marketId
reference
state
datetime
rejectionReason
party {
id
}
... on NewAsset {
__typename
symbol
source {
... on BuiltinAsset {
maxFaucetAmountMint
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
instrument {
name
}
}
... on ERC20 {
contractAddress
... on UpdateMarket {
marketId
}
... on NewAsset {
__typename
symbol
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
}
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
}
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
}
}
}
votes {
yes {
totalTokens
totalNumber
votes {
value
party {
id
stake {
currentStakeAvailable
yes {
totalTokens
totalNumber
votes {
value
party {
id
stakingSummary {
currentStakeAvailable
}
}
datetime
}
}
datetime
}
}
no {
totalTokens
totalNumber
votes {
value
party {
id
stake {
currentStakeAvailable
no {
totalTokens
totalNumber
votes {
value
party {
id
stakingSummary {
currentStakeAvailable
}
}
datetime
}
}
datetime
}
}
}
@@ -101,19 +97,25 @@ const PROPOSAL_QUERY = gql`
`;
const Governance = () => {
const { data } = useQuery<ProposalsQuery>(PROPOSAL_QUERY, {
const { data } = useQuery<ProposalsQuery>(PROPOSALS_QUERY, {
errorPolicy: 'ignore',
});
const proposals = compact(data?.proposalsConnection?.edges).map(
(e) => e.node
);
if (!data) return null;
return (
<section>
<RouteTitle data-testid="governance-header">
{t('Governance Proposals')}
</RouteTitle>
{data.proposals?.map((p) => (
{proposals.map((p) => (
<React.Fragment key={p.id}>
<SubHeading>{getProposalName(p.terms.change)}</SubHeading>
<SubHeading>
{p.rationale.title || p.rationale.description}
</SubHeading>
<SyntaxHighlighter data={p} />
</React.Fragment>
))}
@@ -1,7 +1,6 @@
query MarketsQuery {
markets {
id
name
fees {
factors {
makerFee
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketTradingMode, MarketState, AccountType, AuctionTrigger } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { MarketTradingMode, MarketState, AccountType, AuctionTrigger } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: MarketsQuery
@@ -52,7 +52,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_product_sett
export interface MarketsQuery_markets_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
@@ -60,7 +60,7 @@ export interface MarketsQuery_markets_tradableInstrument_instrument_product_sett
*/
name: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
/**
@@ -108,11 +108,11 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRisk
*/
r: number;
/**
* sigma parameter
* sigma parameter, annualised volatility of the underlying asset, must be a strictly non-negative real number
*/
sigma: number;
/**
* mu parameter
* mu parameter, annualised growth rate of the underlying asset
*/
mu: number;
}
@@ -120,15 +120,15 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRisk
export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel {
__typename: "LogNormalRiskModel";
/**
* Tau parameter of the risk model
* Tau parameter of the risk model, projection horizon measured as a year fraction used in the expected shortfall calculation to obtain the maintenance margin, must be a strictly non-negative real number
*/
tau: number;
/**
* Lambda parameter of the risk model
* Lambda parameter of the risk model, probability confidence level used in expected shortfall calculation when obtaining the maintenance margin level, must be strictly greater than 0 and strictly smaller than 1
*/
riskAversionParameter: number;
/**
* Params for the log normal risk model
* Parameters for the log normal risk model
*/
params: MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRiskModel_params;
}
@@ -166,7 +166,7 @@ export interface MarketsQuery_markets_tradableInstrument_marginCalculator_scalin
*/
initialMargin: number;
/**
* The scaling factor that determines the overflow margin level
* the scaling factor that determines the overflow margin level
*/
collateralRelease: number;
}
@@ -182,7 +182,7 @@ export interface MarketsQuery_markets_tradableInstrument_marginCalculator {
export interface MarketsQuery_markets_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
* An instance of, or reference to, a fully specified instrument.
*/
instrument: MarketsQuery_markets_tradableInstrument_instrument;
/**
@@ -219,7 +219,7 @@ export interface MarketsQuery_markets_priceMonitoringSettings_parameters_trigger
probability: number;
/**
* Price monitoring auction extension duration in seconds should the price
* breach it's theoretical level over the specified horizon at the specified
* breach its theoretical level over the specified horizon at the specified
* probability level (> 0)
*/
auctionExtensionSecs: number;
@@ -268,7 +268,7 @@ export interface MarketsQuery_markets_liquidityMonitoringParameters {
export interface MarketsQuery_markets_proposal {
__typename: "Proposal";
/**
* Proposal ID that is filled by VEGA once proposal reaches the network
* Proposal ID that is filled by Vega once proposal reaches the network
*/
id: string | null;
}
@@ -276,7 +276,7 @@ export interface MarketsQuery_markets_proposal {
export interface MarketsQuery_markets_accounts_asset {
__typename: "Asset";
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
@@ -305,7 +305,7 @@ export interface MarketsQuery_markets_data_priceMonitoringBounds_trigger {
__typename: "PriceMonitoringTrigger";
/**
* Price monitoring auction extension duration in seconds should the price
* breach it's theoretical level over the specified horizon at the specified
* breach its theoretical level over the specified horizon at the specified
* probability level (> 0)
*/
auctionExtensionSecs: number;
@@ -346,15 +346,15 @@ export interface MarketsQuery_markets_data_liquidityProviderFeeShare_party {
export interface MarketsQuery_markets_data_liquidityProviderFeeShare {
__typename: "LiquidityProviderFeeShare";
/**
* The liquidity provider party id
* The liquidity provider party ID
*/
party: MarketsQuery_markets_data_liquidityProviderFeeShare_party;
/**
* The share own by this liquidity provider (float)
* The share owned by this liquidity provider (float)
*/
equityLikeShare: string;
/**
* the average entry valuation of the liquidity provider for the market
* The average entry valuation of the liquidity provider for the market
*/
averageEntryValuation: string;
}
@@ -362,7 +362,7 @@ export interface MarketsQuery_markets_data_liquidityProviderFeeShare {
export interface MarketsQuery_markets_data {
__typename: "MarketData";
/**
* the mark price (actually an unsigned int)
* the mark price (an unsigned integer)
*/
markPrice: string;
/**
@@ -446,7 +446,7 @@ export interface MarketsQuery_markets_data {
*/
suppliedStake: string | null;
/**
* A list of valid price ranges per associated trigger
* a list of valid price ranges per associated trigger
*/
priceMonitoringBounds: MarketsQuery_markets_data_priceMonitoringBounds[] | null;
/**
@@ -470,12 +470,12 @@ export interface MarketsQuery_markets {
*/
fees: MarketsQuery_markets_fees;
/**
* An instance of or reference to a tradable instrument.
* An instance of, or reference to, a tradable instrument.
*/
tradableInstrument: MarketsQuery_markets_tradableInstrument;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
* number denominated in the currency of the market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
@@ -512,7 +512,7 @@ export interface MarketsQuery_markets {
*/
state: MarketState;
/**
* The proposal which initiated this market
* The proposal that initiated this market
*/
proposal: MarketsQuery_markets_proposal | null;
/**
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -6,14 +6,13 @@ const defaultOptions = {} as const;
export type MarketsQueryQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type MarketsQueryQuery = { __typename?: 'Query', markets?: Array<{ __typename?: 'Market', id: string, name: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, id: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'Account', balance: string } | null } } }, 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 }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accounts?: Array<{ __typename?: 'Account', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } }> | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: string, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null }> | null };
export type MarketsQueryQuery = { __typename?: 'Query', markets?: Array<{ __typename?: 'Market', id: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, id: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'Account', balance: string } | null } } }, 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 }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accounts?: Array<{ __typename?: 'Account', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } }> | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: string, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null }> | null };
export const MarketsQueryDocument = gql`
query MarketsQuery {
markets {
id
name
fees {
factors {
makerFee
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: OracleSpecs
@@ -61,7 +61,7 @@ export interface OracleSpecs_oracleSpecs {
*/
status: OracleSpecStatus;
/**
* id is a hash generated from the OracleSpec data.
* ID is a hash generated from the OracleSpec data.
*/
id: string;
/**
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "./../../../../../../../../libs/types/src/__generated__/globalTypes";
import { AccountType } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: PartyAssetsQuery
@@ -12,7 +12,7 @@ import { AccountType } from "./../../../../../../../../libs/types/src/__generate
export interface PartyAssetsQuery_party_delegations_node {
__typename: "Node";
/**
* The node url eg n01.vega.xyz
* The node URL eg n01.vega.xyz
*/
id: string;
name: string;
@@ -49,7 +49,7 @@ export interface PartyAssetsQuery_party_accounts_asset_source_BuiltinAsset {
export interface PartyAssetsQuery_party_accounts_asset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the erc20 contract
* The address of the ERC20 contract
*/
contractAddress: string;
}
@@ -63,11 +63,11 @@ export interface PartyAssetsQuery_party_accounts_asset {
*/
name: string;
/**
* The id of the asset
* The ID of the asset
*/
id: string;
/**
* The precision of the asset
* The precision of the asset. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
*/
decimals: number;
/**
@@ -75,7 +75,7 @@ export interface PartyAssetsQuery_party_accounts_asset {
*/
symbol: string;
/**
* The origin source of the asset (e.g: an erc20 asset)
* The origin source of the asset (e.g: an ERC20 asset)
*/
source: PartyAssetsQuery_party_accounts_asset_source;
}
@@ -115,7 +115,7 @@ export interface PartyAssetsQuery_party {
export interface PartyAssetsQuery {
/**
* An entity that is trading on the VEGA network
* An entity that is trading on the Vega network
*/
party: PartyAssetsQuery_party | null;
}
+11 -113
View File
@@ -1,27 +1,17 @@
import { useQuery } from '@apollo/client';
import { gql } from '@apollo/client';
import {
t,
useFetch,
addDecimalsFormatNumber,
useScreenDimensions,
} from '@vegaprotocol/react-helpers';
import React, { useMemo } from 'react';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import React from 'react';
import { useParams } from 'react-router-dom';
import { RouteTitle } from '../../../components/route-title';
import { SubHeading } from '../../../components/sub-heading';
import { SyntaxHighlighter, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Panel } from '../../../components/panel';
import { InfoPanel } from '../../../components/info-panel';
import { toNonHex } from '../../../components/search/detect-search';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../../config';
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
import type {
PartyAssetsQuery,
PartyAssetsQueryVariables,
} from './__generated__/PartyAssetsQuery';
import type { TendermintSearchTransactionResponse } from '../tendermint-transaction-response';
import { useTxsData } from '../../../hooks/use-txs-data';
import { TxsInfiniteList } from '../../../components/txs';
import { PageHeader } from '../../../components/page-header';
const PARTY_ASSETS_QUERY = gql`
query PartyAssetsQuery($partyId: ID!) {
@@ -60,19 +50,13 @@ const PARTY_ASSETS_QUERY = gql`
const Party = () => {
const { party } = useParams<{ party: string }>();
const partyId = party ? toNonHex(party) : '';
const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
const filters = `filters[tx.submitter]=${partyId}`;
const { hasMoreTxs, loadTxs, error, txsData, loading } = useTxsData({
limit: 10,
filters,
});
const {
state: { data: partyData },
} = useFetch<TendermintSearchTransactionResponse>(
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${partyId}'"`
`${
DATA_SOURCES.tendermintUrl
}/tx_search?query="tx.submitter='${party?.replace('0x', '')}'"`
);
const { data } = useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(
@@ -80,104 +64,18 @@ const Party = () => {
{
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId },
variables: { partyId: party?.replace('0x', '') || '' },
skip: !party,
}
);
const header = data?.party?.id ? (
<PageHeader
title={data.party.id}
copy
truncateStart={visibleChars}
truncateEnd={visibleChars}
/>
) : (
<Panel>
<p>No party found for key {party}</p>
</Panel>
);
const accounts = (
<section>
{data?.party?.accounts?.length ? (
data.party.accounts.map((account) => {
return (
<InfoPanel title={account.asset.name} id={account.asset.id}>
<section>
<dl className="flex gap-2">
<dt className="text-zinc-500 dark:text-zinc-400 text-md">
{t('Balance')} ({account.asset.symbol})
</dt>
<dd className="text-md">
{addDecimalsFormatNumber(
account.balance,
account.asset.decimals
)}
</dd>
</dl>
</section>
</InfoPanel>
);
})
) : (
<Panel>
<p>No Data</p>
</Panel>
)}
</section>
);
const staking = (
<section>
{data?.party?.stake?.currentStakeAvailable ? (
<InfoPanel
title={t('Current Stake Available')}
id={data?.party?.stake?.currentStakeAvailable}
copy={false}
/>
) : (
<Panel>
<p>Nothing staked for {party}</p>
</Panel>
)}
</section>
);
return (
<section>
<h1
className="font-alpha uppercase font-xl mb-4 text-zinc-800 dark:text-zinc-200"
data-testid="parties-header"
>
{t('Party')}
</h1>
<RouteTitle data-testid="parties-header">{t('Party')}</RouteTitle>
{data ? (
<>
{header}
<SubHeading>{t('Asset data')}</SubHeading>
{accounts}
<SubHeading>{t('Staking')}</SubHeading>
{staking}
<SubHeading>{t('JSON')}</SubHeading>
<section data-testid="parties-json">
<SyntaxHighlighter data={data} />
</section>
<AsyncRenderer
loading={loading as boolean}
error={error}
data={txsData}
>
<SubHeading>{t('Transactions')}</SubHeading>
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={loadTxs}
error={error}
className="mb-28"
/>
</AsyncRenderer>
<SyntaxHighlighter data={data} />
</>
) : null}
@@ -17,6 +17,7 @@ const PendingTxs = () => {
<RouteTitle data-testid="unconfirmed-transactions-header">
{t('Unconfirmed transactions')}
</RouteTitle>
https://tm.n06.testnet.vega.xyz/tm/unconfirmed_txs
<br />
<div>{t(`Number: ${unconfirmedTransactions?.result?.n_txs || 0}`)}</div>
<br />
@@ -13,7 +13,7 @@ import Genesis from './genesis';
import { Block } from './blocks/id';
import { Blocks } from './blocks/home';
import { Tx } from './txs/id';
import { TxsList } from './txs/home';
import { TxsHome, TxsHomeFallback } from './txs/home';
import { PendingTxs } from './pending';
import flags from '../config/flags';
import { t } from '@vegaprotocol/react-helpers';
@@ -141,7 +141,7 @@ const routerConfig = [
},
{
index: true,
element: <TxsList />,
element: flags.txsList ? <TxsHome /> : <TxsHomeFallback />,
},
],
},
+113 -10
View File
@@ -1,19 +1,86 @@
import { t } from '@vegaprotocol/react-helpers';
import { DATA_SOURCES } from '../../../config';
import { useCallback, useState, useMemo } from 'react';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../../components/route-title';
import { BlocksRefetch } from '../../../components/blocks';
import { TxsInfiniteList, TxsStatsInfo } from '../../../components/txs';
import { useTxsData } from '../../../hooks/use-txs-data';
import { JumpToBlock } from '../../../components/jump-to-block';
import { TxsInfiniteList } from '../../../components/txs';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
import type { TendermintBlockchainResponse } from '../../blocks/tendermint-blockchain-response';
const BE_TXS_PER_REQUEST = 20;
interface TxsProps {
latestBlockHeight: string;
}
interface TxsStateProps {
txsData: ChainExplorerTxResponse[];
hasMoreTxs: boolean;
nextPage: number;
}
const Txs = ({ latestBlockHeight }: TxsProps) => {
const [{ txsData, hasMoreTxs, nextPage }, setTxsState] =
useState<TxsStateProps>({
txsData: [],
hasMoreTxs: true,
nextPage: 1,
});
const reusedBodyParams = useMemo(
() => ({
node_url: DATA_SOURCES.tendermintUrl,
transaction_height: parseInt(latestBlockHeight),
page_size: 30,
}),
[latestBlockHeight]
);
const {
state: { error, loading },
refetch,
} = useFetch(
DATA_SOURCES.chainExplorerUrl,
{
method: 'POST',
body: JSON.stringify(reusedBodyParams),
},
false
);
const loadTxs = useCallback(async () => {
const data = await refetch(
undefined,
JSON.stringify({
...reusedBodyParams,
page_number: nextPage,
})
);
if (data) {
setTxsState((prev) => ({
...prev,
nextPage: prev.nextPage + 1,
hasMoreTxs: true,
txsData: [...prev.txsData, ...(data as ChainExplorerTxResponse[])],
}));
}
}, [nextPage, refetch, reusedBodyParams]);
export const TxsList = () => {
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } =
useTxsData({ limit: BE_TXS_PER_REQUEST });
return (
<section className="md:p-2 lg:p-4 xl:p-6">
<section>
<RouteTitle>{t('Transactions')}</RouteTitle>
<BlocksRefetch refetch={refreshTxs} />
<TxsStatsInfo className="!my-12 py-8" />
<BlocksRefetch
refetch={() =>
refetch(
undefined,
JSON.stringify({
...reusedBodyParams,
page_number: 1,
})
)
}
/>
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
@@ -22,6 +89,42 @@ export const TxsList = () => {
error={error}
className="mb-28"
/>
<JumpToBlock />
</section>
);
};
export const TxsHome = () => {
const {
state: { data, error, loading },
} = useFetch<TendermintBlockchainResponse>(
`${DATA_SOURCES.tendermintUrl}/blockchain`
);
return (
<AsyncRenderer
loading={!!loading}
loadingMessage={t('Getting latest block height...')}
error={error}
data={data}
noDataMessage={t('Could not get latest block height')}
render={(data) => (
<Txs
latestBlockHeight={
data?.result?.block_metas?.[0]?.header?.height || ''
}
/>
)}
/>
);
};
export const TxsHomeFallback = () => (
<>
<RouteTitle>{t('Transactions')}</RouteTitle>
<div>
The transactions list is currently disabled. Please use the search bar to
discover transaction data
</div>
</>
);
+28 -38
View File
@@ -1,59 +1,49 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { useFetch } from '@vegaprotocol/react-helpers';
import type { TendermintTransactionResponse } from '../tendermint-transaction-response.d';
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
import { DATA_SOURCES } from '../../../config';
import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched';
import { TxContent } from './tx-content';
import { TxDetails } from './tx-details';
import type { BlockExplorerTransaction } from '../../../routes/types/block-explorer-response';
import { toNonHex } from '../../../components/search/detect-search';
import { PageHeader } from '../../../components/page-header';
import { Routes } from '../../../routes/route-names';
import { IconNames } from '@blueprintjs/icons';
import { Icon } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
const Tx = () => {
const { txHash } = useParams<{ txHash: string }>();
const hash = txHash ? toNonHex(txHash) : '';
const {
state: { data, loading: tTxLoading, error: tTxError },
} = useFetch<BlockExplorerTransaction>(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
state: { data: tTxData, loading: tTxLoading, error: tTxError },
} = useFetch<TendermintTransactionResponse>(
`${DATA_SOURCES.tendermintUrl}/tx?hash=${txHash}`
);
const {
state: { data: ceTxData, loading: ceTxLoading, error: ceTxError },
} = useFetch<ChainExplorerTxResponse>(DATA_SOURCES.chainExplorerUrl, {
method: 'POST',
body: JSON.stringify({
tx_hash: txHash,
node_url: `${DATA_SOURCES.tendermintUrl}/`,
}),
});
return (
<section>
<Link
className="font-normal underline underline-offset-4 block mb-5"
to={`/${Routes.TX}`}
>
<Icon
className="text-vega-light-300 dark:text-vega-light-300"
name={IconNames.CHEVRON_LEFT}
/>
All Transactions
</Link>
<PageHeader
title={hash}
prefix="Transaction"
copy
truncateStart={5}
truncateEnd={9}
className="mb-5"
/>
<RouteTitle>{t('Transaction details')}</RouteTitle>
<RenderFetched error={tTxError} loading={tTxLoading}>
<>
<TxDetails
className="mb-28"
txData={data?.transaction}
pubKey={data?.transaction.submitter}
/>
<TxDetails
className="mb-28"
txData={tTxData?.result}
pubKey={ceTxData?.PubKey}
/>
</RenderFetched>
<TxContent data={data?.transaction} />
</>
<h2 className="text-2xl uppercase mb-4">{t('Transaction content')}</h2>
<RenderFetched error={ceTxError} loading={ceTxLoading}>
<TxContent data={ceTxData} />
</RenderFetched>
</section>
);
@@ -1,14 +1,21 @@
import { t } from '@vegaprotocol/react-helpers';
import { StatusMessage } from '../../../components/status-message';
import { NestedDataList } from '../../../components/nested-data-list';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import {
TableWithTbody,
TableCell,
TableHeader,
TableRow,
} from '../../../components/table';
import { TxOrderType } from '../../../components/txs';
import type { ChainExplorerTxResponse } from '../../types/chain-explorer-response';
interface TxContentProps {
data: BlockExplorerTransactionResult | undefined;
data: ChainExplorerTxResponse | undefined;
}
export const TxContent = ({ data }: TxContentProps) => {
if (!data?.command) {
if (!data?.Command) {
return (
<StatusMessage>
{t('Could not retrieve transaction content')}
@@ -16,5 +23,21 @@ export const TxContent = ({ data }: TxContentProps) => {
);
}
return <NestedDataList data={data.command} />;
return (
<>
<TableWithTbody className="mb-12">
<TableRow modifier="bordered">
<TableHeader scope="row" className="w-[160px]">
{t('Type')}
</TableHeader>
<TableCell modifier="bordered">
<TxOrderType orderType={data.Type} />
</TableCell>
</TableRow>
</TableWithTbody>
<h3 className="font-mono mb-8">{t('Decoded transaction content')}</h3>
<SyntaxHighlighter data={JSON.parse(data.Command)} />
</>
);
};
@@ -1,30 +1,44 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { TxDetails } from './tx-details';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { truncateByChars } from '@vegaprotocol/react-helpers';
import { TxDetails, txDetailsTruncateLength } from './tx-details';
import type { Result } from '../tendermint-transaction-response.d';
const pubKey = 'test';
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
const height = '52987';
const tx =
'ClMIpeWjmKnn77FNEPedA5J9QhJAOGI3YjQzMWNlMmNhNzc4MWMzMTQ1M2IyYjc0MWYwMTJlNzQ1MzBhZDhjMDgzODVkMWQ1YjRiY2VkMTJiNDc1MhKTAQqAATM5NDFlNmExMzQ3MGVhNTlhNGExNmQzMjRiYzlkZjI5YWZkMzYxMDRiZjQ5MzEwZWMxM2ZiOTMxNTM2NGY3ZjU2ZTQyOTJmYTAyZDlhNTBlZDc0OWE0ZjExMzJiNjM2ZTZmMzQ3YzQ2NjdkYmM5OThmYzcyZjYzYzQxMzU4ZTAzEgx2ZWdhL2VkMjU1MTkYAYB9AtI+QDA5MzFhOGZkOGNjOTM1NDU4ZjQ3MGU0MzVhMDU0MTQzODdjZWE2ZjMyOWQ2NDhiZTg5NGZjZDQ0YmQ1MTdhMmI=';
const txData: BlockExplorerTransactionResult = {
const txData = {
hash,
block: height,
height,
tx,
index: 0,
submitter: pubKey,
code: 0,
cursor: `${height}.0`,
type: 'type',
command: {},
tx_result: {
code: 0,
data: null,
log: '',
info: '',
events: [],
gas_wanted: '0',
gas_used: '0',
codespace: '',
},
};
const renderComponent = (txData: BlockExplorerTransactionResult) => (
const renderComponent = (txData: Result) => (
<Router>
<TxDetails txData={txData} pubKey={pubKey} />
</Router>
);
describe('Transaction details', () => {
it('Renders the tx hash', () => {
render(renderComponent(txData));
expect(screen.getByText(hash)).toBeInTheDocument();
});
it('Renders the pubKey', () => {
render(renderComponent(txData));
expect(screen.getByText(pubKey)).toBeInTheDocument();
@@ -34,4 +48,18 @@ describe('Transaction details', () => {
render(renderComponent(txData));
expect(screen.getByText(height)).toBeInTheDocument();
});
it('Renders the truncated tx text', () => {
render(renderComponent(txData));
expect(
screen.getByText(
truncateByChars(tx, txDetailsTruncateLength, txDetailsTruncateLength)
)
).toBeInTheDocument();
});
it('Renders a copy button', () => {
render(renderComponent(txData));
expect(screen.getByTestId('copy-tx-to-clipboard')).toBeInTheDocument();
});
});
@@ -1,12 +1,18 @@
import { Routes } from '../../route-names';
import { t } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import React from 'react';
import { ButtonLink, CopyWithTooltip } from '@vegaprotocol/ui-toolkit';
import {
TableWithTbody,
TableCell,
TableHeader,
TableRow,
} from '../../../components/table';
import { TruncateInline } from '../../../components/truncate/truncate';
import { Link } from 'react-router-dom';
import { t } from '@vegaprotocol/react-helpers';
import { HighlightedLink } from '../../../components/highlighted-link';
import type { Result } from '../tendermint-transaction-response.d';
interface TxDetailsProps {
txData: BlockExplorerTransactionResult | undefined;
txData: Result | undefined;
pubKey: string | undefined;
className?: string;
}
@@ -15,35 +21,54 @@ export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
return <>{t('Awaiting Tendermint transaction details')}</>;
}
const truncatedSubmitter = (
<TruncateInline text={pubKey || ''} startChars={5} endChars={5} />
);
return (
<section className="mb-10">
<h3 className="text-3xl xl:text-4xl uppercase font-alpha mb-4">
{txData.type} by{' '}
<Link
className="font-bold underline"
to={`/${Routes.PARTIES}/${pubKey}`}
<TableWithTbody className={className}>
<TableRow modifier="bordered">
<TableCell>{t('Hash')}</TableCell>
<TableCell modifier="bordered" data-testid="hash">
{txData.hash}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableHeader scope="row" className="w-[160px]">
{t('Submitted by')}
</TableHeader>
<TableCell modifier="bordered" data-testid="submitted-by">
<HighlightedLink to={`/${Routes.PARTIES}/${pubKey}`} text={pubKey} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Block')}</TableCell>
<TableCell modifier="bordered" data-testid="block">
<HighlightedLink
to={`/${Routes.BLOCKS}/${txData.height}`}
text={txData.height}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Encoded txn')}</TableCell>
<TableCell
modifier="bordered"
data-testid="encoded-tnx"
className="flex justify-between"
>
{truncatedSubmitter}
</Link>
</h3>
<p className="text-xl xl:text-2xl uppercase font-alpha">
Block{' '}
<Link
className="font-bold underline"
to={`/${Routes.BLOCKS}/${txData.block}`}
>
{txData.block}
</Link>
{', '}
Index {txData.index}
</p>
</section>
<TruncateInline
text={txData.tx}
startChars={txDetailsTruncateLength}
endChars={txDetailsTruncateLength}
/>
<CopyWithTooltip text={txData.tx}>
<ButtonLink
title={t('Copy tx to clipboard')}
data-testid="copy-tx-to-clipboard"
/>
</CopyWithTooltip>
</TableCell>
</TableRow>
</TableWithTbody>
);
};
@@ -1,18 +0,0 @@
export interface BlockExplorerTransactionResult {
block: string;
index: number;
hash: string;
submitter: string;
type: string;
code: number;
cursor: string;
command: Record<string, unknown>;
}
export interface BlockExplorerTransactions {
transactions: BlockExplorerTransactionResult[];
}
export interface BlockExplorerTransaction {
transaction: BlockExplorerTransactionResult;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { NodeStatus } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
import { NodeStatus } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: NodesQuery
@@ -28,33 +28,33 @@ export interface NodesQuery_nodes_epochData {
export interface NodesQuery_nodes {
__typename: "Node";
/**
* The node url eg n01.vega.xyz
* The node URL eg n01.vega.xyz
*/
id: string;
name: string;
/**
* URL where I can find out more info on the node. Will this be possible?
* URL from which you can get more info about the node.
*/
infoUrl: string;
avatarUrl: string | null;
/**
* Pubkey of the node operator
* Public key of the node operator
*/
pubkey: string;
/**
* Public key of Tendermint
* Tendermint public key of the node
*/
tmPubkey: string;
/**
* Ethereum public key of the node
*/
ethereumAdddress: string;
ethereumAddress: string;
/**
* Country code for the location of the node
*/
location: string;
/**
* The amount the node has put up themselves
* The amount of stake the node has put up themselves
*/
stakedByOperator: string;
/**
@@ -1,4 +1,4 @@
import * as Types from '@vegaprotocol/types/types';
import { Schema as Types } from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
@@ -18,7 +18,7 @@ const NODES_QUERY = gql`
avatarUrl
pubkey
tmPubkey
ethereumAdddress
ethereumAddress
location
stakedByOperator
stakedByDelegates

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