Compare commits

...
Author SHA1 Message Date
sam-keen 3fd8205587 ci-make-deploy-preview 2023-03-31 11:57:34 +01:00
Matthew Russell 2b77421600 fix: add gas estimates with a buffer for all token app tx 2023-03-30 14:27:17 -07:00
Sam Keen 26d27c1487 fix(governance): validators list comparing stringified numbers (#3170) 2023-03-13 15:12:15 +00:00
Dexter Edwards fbb1b55e41 fix(token): router incorrectly configured, causing query spamming (#3042) 2023-03-01 12:46:26 +00:00
Sam Keen 81cdbd8140 updated leaving validator list to include Nala (#2937) 2023-02-17 18:15:09 +00:00
Edd 21c1f95172 Update .env fix for block explorer 2023-02-17 18:06:32 +00:00
Dexter Edwards c376a50587 Chore/validators leaving (#2769) 2023-01-30 09:18:07 +00:00
Dexter Edwards d71159eed1 chore: add logo map override for incorrectly specified validator logos (#2686) 2023-01-25 10:01:22 +00:00
Edd bb236d0445 fix(explorer): add workaround for v53 misreporting total node count (#2671) 2023-01-19 12:53:46 +00:00
Dexter Edwards 3e06c9478c Hack/mainnet leaving validators (#2576)
* feat: hacky thing to tell people the validators that are leaving

* chore: put back empty state

* style: lint
2023-01-11 14:58:07 +00:00
Joe Tsang 326a2e6412 chore: pull capsule from v0.2.3 (#2520) 2023-01-04 17:14:45 +00:00
Edd 5a1bd5a73f chore(explorer): add mirror env (#2517)
* chore(explorer): add mirror env
* chore: bump nx sha3 in extra workflows
2023-01-04 17:03:32 +00:00
Edd 0416e916e1 chore: bump nx sha3 in workflows 2023-01-04 16:13:09 +00:00
Dexter af3bb2dcd5 chore: update mirror url 2023-01-04 14:33:48 +00:00
Dexter 6229e6f92b chore: adjust mirror env vars 2023-01-04 13:28:29 +00:00
Dexter Edwards f03aa655c3 chore: fix percetnages (#2501) 2023-01-03 12:43:20 +00:00
Dexter EdwardsandMatthew Russell 20dd1995ca chore: remove validating nodes (#2454)
* chore: remove validating nodes

* fix: explorer home

* chore: remove stray log

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2022-12-21 17:26:19 +00:00
Dexter 266f2462bc fix: rounding error 2022-12-14 09:17:59 +00:00
Dexter Edwards 14052c4088 fix: rewards page (#2393) 2022-12-13 17:01:37 +00:00
Dexter Edwards d4231ea531 Fix/rest delegations (#2392)
* chore: create use delegations hook

* chore: add polling to use delegations

* chore: remove polling logic from hook

* chore: remove delegations from staking form

* chore: fix more delegations queries

* chore: rename file

* fix: spelling issues
2022-12-13 15:37:11 +00:00
Dexter Edwards 718c93f486 chore: poll delegations less frequently (#2390) 2022-12-13 10:45:24 +00:00
Dexter Edwards 4ef5d7f19c feat: mirror environment (#2342)
* feat: mirror environment

* chore: update networks to have correct data node

* chore: update netowrks to point to correct data nodes
2022-12-07 10:11:23 +00:00
Edd 51f341ea94 fix(#2012): fix mainnet block explorer (#2020)
* chore(explorer): add urls for mainnet explorer api
2022-11-10 16:28:09 +00:00
Elmar 6d09e4ee64 chore(#1976): block explorer txs list (#1996)
* Feat/1334 replace json (#1459)

* feat(explorer): add panel component

* feat(explorer): add info panel component

* feat(explorer): change layout styles

* feat(explorer): render json data in component for parties

* feat(explorer): change testnet env

* feat(explorer): change eslint json

* feat(explorer): render staking json in info panel component

* fix(explorer): pr comments

* fix(explorer): theme dark nav (#1498)

* fix(explorer): add party to consolidated search (#1588)

* fix(explorer): add party to consolidated search

* chore(explorer): add missing tests for detect-search.spec.ts

* feat(explorer): add txs infinite scroll list (#1625)

* Feat/1682 fix api endpoints (#1690)

* chore(explorer): replace tendermint api with block explorer api for transaction related searches

* feat(explorer): replace detect search type to be by process of elimination as temp measure

* fix(explorer): fix broken tests

* Feat/397 list transactions (#1770)

* chore(explorer): add stagnet 1 env vars

* feat(explorer): add info block component

* feat(explorer): change tx order type lozenge

* feat(explorer): change truncated-link.tsx style

* feat(explorer): add stats info component for txs

* feat(explorer): change page title size

* feat(explorer): update txs list and add txs for party

* fix(explorer): remove unused var

* fix(explorer): change copy and remove unused vars

* fix(explorer): pr review fixes

* Feat/1501 improve party details (#1785)

* feat(explorer): change styling party details page

* feat(explorer): use panel component in txs-stats-info.tsx

* feat(explorer): change order for party details sections

* chore(explorer): add stagnet-1 env vars (#1846)

* chore(explorer): add sandbox env vars (#1867)

* chore(explorer): update BE api urls (#1886)

* Feat/1416 tx detail (#1953)

* chore(explorer): add new vega colours to tw config

* feat(explorer): add new nested data list component

* feat(explorer): abstract page header with copy to new component

* feat(explorer): update styles tx details page

* fix(explorer): linting errors

* fix(explorer): fix txs type

* fix(explorer): fix strong typing

* fix(explorer): fix styling error for e2e test

* fix(explorer): reduce limit of txs from 100 to 20
2022-11-09 10:03:02 +00:00
Elmar 63bfcb6e75 fix(explorer): fix uptime to reference genesisTime field (#1819) 2022-10-21 13:11:56 +01:00
Elmar 66046a9471 fix(stats-manager): fix uptime pointing to incorrect field (#1328) (#1725) 2022-10-21 12:16:35 +01:00
Dexter Edwards 4263a6b07b fix: do no allow min or max fraction digits to go out of range (#1806)
* fix: do no allow min or max fraction digits to go out of range

* style: lint

* chore: assign variable to remove magic number
2022-10-20 14:41:19 +01:00
Dexter Edwards 389291785b fix: wallet connect (#1706) 2022-10-11 12:30:05 +01:00
Dexter Edwards a1746019b6 Chore/api endpoints (#1392)
* ci: correct api endpoints

* ci: force build
2022-09-20 11:32:17 +01:00
Dexter Edwards be38f630d8 Chore/more env config (#1363)
* chore: remove vega URL as no longer needed

* chore: remove bad env config

* chore: remove config caching

* chore: remove silly text

* style: lint

* chore: style lint

* test: remove test for removed functionality
2022-09-16 18:23:02 +01:00
Matthew Russell c49c52a635 fix: supporting text content on withdrawals page 2022-09-08 15:13:37 -07:00
Matthew Russell a94fc689b9 chore: appease nx import path 2022-09-08 12:42:05 -07:00
Matthew Russell 25f63d2fd2 fix: regen types from mainnet and fix errors 2022-09-08 12:31:10 -07:00
Dexter 1ac0cf554f ci: force build 2022-09-08 15:08:02 +01:00
Dexter 48004d836d Revert "chore: proposal schema change (1057) (#1169)"
This reverts commit d2791f2e59.
2022-09-08 15:03:26 +01:00
Dexter 32e2e22c64 chore: revert ethereumAddress changes which are not on mainnet yet 2022-09-08 15:02:23 +01:00
Dexter 71c8b31ffe fix: tranches styles look terrible 2022-09-08 14:59:35 +01:00
226 changed files with 4457 additions and 2863 deletions
+3 -3
View File
@@ -51,7 +51,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: vegaprotocol/vegacapsule
ref: main
ref: v0.2.3
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
path: './capsule'
@@ -120,10 +120,10 @@ jobs:
######
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
uses: nrwl/nx-set-shas@v3
with:
working-directory: frontend-monorepo
main-branch-name: master
main-branch-name: main
- name: Install root dependencies
run: yarn install
+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@v2
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: master
main-branch-name: main
- 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@v2
uses: nrwl/nx-set-shas@v3
with:
main-branch-name: master
main-branch-name: main
- 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.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.token.vega.xyz
NX_VEGA_REST=https://api.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 "@vegaprotocol/types";
import { AccountType } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: PartyBalanceQuery
@@ -12,7 +12,7 @@ import { AccountType } from "@vegaprotocol/types";
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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
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;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AssetStatus } from "@vegaprotocol/types";
import { AssetStatus } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// 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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
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;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketState } from "@vegaprotocol/types";
import { MarketState } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL subscription operation: SimpleMarketDataSub
@@ -24,7 +24,7 @@ export interface SimpleMarketDataSub_marketData_market {
export interface SimpleMarketDataSub_marketData {
__typename: "MarketData";
/**
* market ID of the associated mark price
* market id of the associated mark price
*/
market: SimpleMarketDataSub_marketData_market;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketState } from "@vegaprotocol/types";
import { MarketState } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// 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 parameters
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
*/
candles: (SimpleMarkets_markets_candles | null)[] | null;
}
@@ -33,7 +33,7 @@ export const MARKETS_QUERY = gql`
}
}
}
candles(interval: INTERVAL_I1H, since: $CandleSince) {
candles(interval: I1H, since: $CandleSince) {
open
close
}
@@ -54,7 +54,7 @@ const MARKET_DATA_SUB = gql`
export const CANDLE_SUB = gql`
subscription CandleLive($marketId: ID!) {
candles(marketId: $marketId, interval: INTERVAL_I1H) {
candles(marketId: $marketId, interval: I1H) {
close
}
}
@@ -10,7 +10,7 @@
export interface MarketMarkPrice_market_data {
__typename: "MarketData";
/**
* the mark price (an unsigned integer)
* the mark price (actually an unsigned int)
*/
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 "@vegaprotocol/types";
import { AccountType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: PartyMarketData
@@ -12,11 +12,11 @@ import { AccountType } from "@vegaprotocol/types";
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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
decimals: number;
}
@@ -64,15 +64,15 @@ export interface PartyMarketData_party_marginsConnection_edges_node {
*/
market: PartyMarketData_party_marginsConnection_edges_node_market;
/**
* this is the minimum margin required for a party to place a new order on the network (unsigned integer)
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
*/
initialLevel: string;
/**
* minimal margin for the position to be maintained in the network (unsigned integer)
* minimal margin for the position to be maintained in the network (unsigned int actually)
*/
maintenanceLevel: string;
/**
* if the margin is between maintenance and search, the network will initiate a collateral search (unsigned integer)
* if the margin is between maintenance and search, the network will initiate a collateral search (unsigned int actually)
*/
searchLevel: string;
}
@@ -101,14 +101,14 @@ export interface PartyMarketData_party {
*/
accounts: PartyMarketData_party_accounts[] | null;
/**
* Margin levels for a market
* Margin level for a market
*/
marginsConnection: PartyMarketData_party_marginsConnection;
}
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 "@vegaprotocol/types";
import { Side, OrderTimeInForce, OrderType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: EstimateOrder
@@ -12,15 +12,15 @@ import { Side, OrderTimeInForce, OrderType } from "@vegaprotocol/types";
export interface EstimateOrder_estimateOrder_fee {
__typename: "TradeFee";
/**
* The maker fee, paid by the aggressive party to the other party (the one who had an order in the book)
* The maker fee, 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 validators to maintain the Vega network
* The infrastructure fee, a fee paid to the node runner to maintain the vega network
*/
infrastructureFee: string;
/**
* The fee paid to the liquidity providers that committed liquidity to the market
* The fee paid to the market makers to provide liquidity in the market
*/
liquidityFee: string;
}
@@ -28,7 +28,7 @@ export interface EstimateOrder_estimateOrder_fee {
export interface EstimateOrder_estimateOrder_marginLevels {
__typename: "MarginLevels";
/**
* this is the minimum margin required for a party to place a new order on the network (unsigned integer)
* this is the minimal margin required for a party to place a new order on the network (unsigned int actually)
*/
initialLevel: string;
}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "@vegaprotocol/types";
import { AccountType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: MarketPositions
@@ -12,7 +12,7 @@ import { AccountType } from "@vegaprotocol/types";
export interface MarketPositions_party_accounts_asset {
__typename: "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
* The precision of the asset
*/
decimals: number;
}
@@ -96,7 +96,7 @@ export interface MarketPositions_party {
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;
}
+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.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.token.vega.xyz/
NX_VEGA_REST=https://api.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.n06.testnet.vega.xyz/tm
NX_TENDERMINT_URL=https://tm.n07.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
+134 -6
View File
@@ -1,20 +1,148 @@
import '../support/common.functions';
context('Asset page', { tags: '@regression' }, function () {
before('gather system asset information', function () {
cy.get_asset_information().as('assetsInfo');
});
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';
it('Assets page is displayed', function () {
before('Navigate to assets page', function () {
cy.visit('/');
cy.get(assetsNavigation).click();
cy.common_validate_blocks_data_displayed(assetHeader);
// 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'
);
});
it('Assets page displayed in mobile', function () {
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 () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(assetsNavigation).click();
cy.common_validate_blocks_data_displayed(assetHeader);
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');
});
});
});
});
});
+252 -20
View File
@@ -1,35 +1,267 @@
import '../support/common.functions';
context('Network parameters page', function () {
before('visit token home page', function () {
cy.visit('/');
context('Network parameters page', { tags: '@smoke' }, function () {
before('navigate to network parameter page', function () {
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
});
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"]';
beforeEach('Navigate to network parameter page', function () {
before('navigate to network parameter page', function () {
cy.visit('/');
cy.get(networkParametersNavigation).click();
});
it('Network paremeter page is displayed', function () {
verifyNetworkParametersPageDisplayed();
it('should show network parameter heading at top of page', function () {
cy.get(networkParametersHeader)
.should('have.text', 'Network Parameters')
.and('be.visible');
});
it('Network parameter page displayed on mobile', function () {
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 () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(networkParametersNavigation).click();
verifyNetworkParametersPageDisplayed();
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');
});
});
});
});
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);
}
});
@@ -0,0 +1,264 @@
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;
}
);
});
});
+295 -22
View File
@@ -1,27 +1,300 @@
import '../support/common.functions';
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');
});
context('Validator page', function () {
describe('Verify elements on page', function () {
const validatorNavigation = 'a[href="/validators"]';
it('Validator page is displayed', function () {
cy.visit('/');
cy.get(validatorNavigation).click();
validateValidatorsDisplayed();
});
it('Validator page is displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(validatorNavigation).click();
validateValidatorsDisplayed();
});
function validateValidatorsDisplayed() {
cy.get('[data-testid="tendermint-header"]').should(
'have.text',
'Tendermint data'
before('Ensure at least two validators are present', function () {
assert.isAtLeast(
this.validators.length,
2,
'Ensuring at least two validators exist'
);
cy.get('[data-testid="tendermint-data"]').should('not.be.empty');
}
});
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 () {
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');
});
});
});
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;
});
});
});
});
+4 -9
View File
@@ -1,17 +1,12 @@
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_URL=https://api.n01.stagnet3.vega.xyz/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
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
@@ -22,4 +17,4 @@ NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
NX_EXPLORER_MARKETS=1
NX_EXPLORER_ORACLES=1
NX_EXPLORER_TXS_LIST=1
NX_EXPLORER_TXS_LIST=0
+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='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=CUSTOM
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
+2 -1
View File
@@ -4,6 +4,7 @@ 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='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
+8 -4
View File
@@ -1,9 +1,13 @@
# App configuration variables
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_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
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
@@ -0,0 +1,20 @@
# 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
@@ -0,0 +1,11 @@
# 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
@@ -0,0 +1,13 @@
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
+2 -1
View File
@@ -4,6 +4,7 @@ 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='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://blockexplorer.n01.stagnet3.vega.xyz/rest
+5 -4
View File
@@ -1,9 +1,10 @@
# App configuration variables
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://tm.n06.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n06.testnet.vega.xyz/websocket
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_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
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_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
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,3 +5,4 @@ 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=
+4 -1
View File
@@ -14,5 +14,8 @@
"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="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">
<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">
<Header
theme={theme}
toggleTheme={toggleTheme}
@@ -14,7 +14,12 @@ jest.mock('../search', () => ({
const renderComponent = () => (
<MemoryRouter>
<Header />
<Header
theme="dark"
toggleTheme={jest.fn()}
menuOpen={false}
setMenuOpen={jest.fn()}
/>
</MemoryRouter>
);
@@ -24,14 +24,15 @@ 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'
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
'dark text-white'
);
return (
<header className={headerClasses}>
<div className="flex h-full items-center sm:items-stretch gap-4">
<Link to={Routes.HOME}>
<h1
className="text-3xl font-alpha uppercase calt mb-0"
className="text-white text-3xl font-alpha uppercase calt mb-0"
data-testid="explorer-header"
>
{t('Vega Explorer')}
@@ -41,13 +42,13 @@ export const Header = ({
</div>
<button
data-testid="open-menu"
className="md:hidden"
className="md:hidden text-white"
onClick={() => setMenuOpen(!menuOpen)}
>
<Icon name={menuOpen ? 'cross' : 'menu'} />
</button>
<Search />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} className="-my-4" />
</header>
);
};
@@ -0,0 +1 @@
export * from './info-block';
@@ -0,0 +1,29 @@
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>
);
};
@@ -0,0 +1 @@
export * from './info-panel';
@@ -0,0 +1,81 @@
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>
);
};
@@ -0,0 +1 @@
export * from './nested-data-list';
@@ -0,0 +1,110 @@
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);
});
});
@@ -0,0 +1,178 @@
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>;
};
@@ -0,0 +1 @@
export * from './page-header';
@@ -0,0 +1,49 @@
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>
);
};
@@ -0,0 +1 @@
export * from './panel';
@@ -0,0 +1,17 @@
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-2xl',
'text-4xl',
'uppercase',
'mb-8',
className
@@ -0,0 +1,189 @@
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);
});
});
@@ -0,0 +1,143 @@
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,14 +2,23 @@ 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();
});
@@ -39,9 +48,10 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
"Something doesn't look right"
'Transaction type is not recognised'
);
});
it('should render error if no input is given', async () => {
render(renderComponent());
const { button } = getInputs();
@@ -49,47 +59,14 @@ describe('Search', () => {
fireEvent.click(button);
expect(await screen.findByTestId('search-error')).toHaveTextContent(
'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'
'Search query required'
);
});
it('should redirect to transactions page', async () => {
render(renderComponent());
const { button, input } = getInputs();
mockGetSearchType.mockResolvedValue(SearchTypes.Transaction);
fireEvent.change(input, {
target: {
value:
@@ -108,6 +85,7 @@ 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:
@@ -123,9 +101,48 @@ 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,55 +1,54 @@
import React, { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { Input, InputError, Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { Button, Input, InputError } from '@vegaprotocol/ui-toolkit';
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] = React.useState<Error | null>(null);
const onSubmit = React.useCallback(
(fields: FormFields) => {
const [error, setError] = useState<Error | null>(null);
const onSubmit = useCallback(
async (fields: FormFields) => {
setError(null);
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")));
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);
}
}
return setError(unrecognisedError);
},
[navigate]
);
return (
<form
onSubmit={handleSubmit(onSubmit)}
@@ -59,21 +58,24 @@ export const Search = () => {
{t('Search by block number or transaction hash')}
</label>
<div className="flex items-stretch gap-2">
<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>
)}
<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>
<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="font-mono font-bold underline"
className="underline uppercase underline-offset-2"
/>
</Link>
);
@@ -0,0 +1,35 @@
/* 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,3 +3,4 @@ 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,5 +1,3 @@
import { Lozenge, Intent } from '@vegaprotocol/ui-toolkit';
interface TxOrderTypeProps {
orderType: string;
className?: string;
@@ -34,10 +32,13 @@ const displayString: StringMap = {
ValidatorHeartbeat: 'Validator Heartbeat',
};
export const TxOrderType = ({ orderType, className }: TxOrderTypeProps) => {
export const TxOrderType = ({ orderType }: TxOrderTypeProps) => {
return (
<Lozenge data-testid="tx-type" variant={Intent.None} className={className}>
<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 "
>
{displayString[orderType] || orderType}
</Lozenge>
</div>
);
};
@@ -1,92 +1,89 @@
import { TxsInfiniteListItem } from './txs-infinite-list-item';
import { render, screen, fireEvent, act } from '@testing-library/react';
import { render, screen } 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
// @ts-ignore testing deliberate failure
Type={undefined}
Command={'test'}
Sig={'test'}
PubKey={'test'}
Nonce={1}
TxHash={'test'}
type={undefined}
submitter="test"
hash=""
index={0}
block="1"
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "Command" data missing', () => {
it('should display "missing vital data" if "hash" data missing', () => {
render(
<TxsInfiniteListItem
Type={'test'}
// @ts-ignore testing deliberate failure
Command={undefined}
Sig={'test'}
PubKey={'test'}
Nonce={1}
TxHash={'test'}
type="test"
submitter="test"
hash={undefined}
index={0}
block="1"
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "Pubkey" data missing', () => {
it('should display "missing vital data" if "submitter" data missing', () => {
render(
<TxsInfiniteListItem
Type={'test'}
Command={'test'}
Sig={'test'}
// @ts-ignore testing deliberate failure
PubKey={undefined}
Nonce={1}
TxHash={'test'}
type="test"
submitter={undefined}
hash="test"
index={0}
block="1"
/>
);
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
});
it('should display "missing vital data" if "TxHash" data missing', () => {
it('should display "missing vital data" if "block" data missing', () => {
render(
<TxsInfiniteListItem
Type={'test'}
Command={'test'}
Sig={'test'}
PubKey={'test'}
Nonce={1}
// @ts-ignore testing deliberate failure
TxHash={undefined}
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"
/>
);
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'}
Command={testCommandData}
Sig={'testSig'}
PubKey={'testPubKey'}
Nonce={1}
TxHash={'testTxHash'}
type="testType"
submitter="testPubKey"
hash="testTxHash"
index={1}
block="1"
/>
</MemoryRouter>
);
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('type')).toHaveTextContent('testType');
const button = screen.getByTestId('command-details');
act(() => {
fireEvent.click(button);
});
expect(screen.getByText('"test of command data"')).toBeInTheDocument();
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-index')).toHaveTextContent('1');
});
});
@@ -1,69 +1,79 @@
import React, { useState } from 'react';
import {
Dialog,
Icon,
Intent,
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { TruncatedLink } from '../truncate/truncated-link';
import { Routes } from '../../routes/route-names';
import { TxOrderType } from './tx-order-type';
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import { toHex } from '../search/detect-search';
const TRUNCATE_LENGTH = 14;
export const TxsInfiniteListItem = ({
TxHash,
PubKey,
Type,
Command,
}: ChainExplorerTxResponse) => {
const [open, setOpen] = useState(false);
if (!TxHash || !PubKey || !Type || !Command) {
hash,
submitter,
type,
block,
index,
}: Partial<BlockExplorerTransactionResult>) => {
if (
!hash ||
!submitter ||
!type ||
block === undefined ||
index === undefined
) {
return <div>Missing vital data</div>;
}
return (
<div
data-testid="transaction-row"
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"
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"
>
<div className="whitespace-nowrap overflow-scroll" data-testid="tx-hash">
<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>
<TruncatedLink
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}
to={`/${Routes.TX}/${toHex(hash)}`}
text={hash}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div
className="flex justify-between whitespace-nowrap overflow-scroll"
data-testid="type"
className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="pub-key"
>
<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>
<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}
</div>
</div>
);
@@ -1,18 +1,34 @@
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) => {
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
return Array.from(Array(number)).map((_) => ({
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',
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,
},
},
}));
};
@@ -41,13 +57,15 @@ describe('Txs infinite list', () => {
error={Error('test error!')}
/>
);
expect(screen.getByText('Error: test error!')).toBeInTheDocument();
expect(
screen.getByText('Cannot fetch transaction: 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(10);
const txs = generateTxs(7);
render(
<MemoryRouter>
<TxsInfiniteList
@@ -64,7 +82,7 @@ describe('Txs infinite list', () => {
screen
.getByTestId('infinite-scroll-wrapper')
.querySelectorAll('.txs-infinite-list-item')
).toHaveLength(10);
).toHaveLength(7);
});
it('tries to load more items when required to initially fill the list', () => {
@@ -108,7 +126,7 @@ describe('Txs infinite list', () => {
});
it('loads more items is called when scrolled', () => {
const txs = generateTxs(20);
const txs = generateTxs(14);
const callback = jest.fn();
render(
@@ -125,7 +143,7 @@ describe('Txs infinite list', () => {
act(() => {
fireEvent.scroll(screen.getByTestId('infinite-scroll-wrapper'), {
target: { scrollY: 600 },
target: { scrollY: 2000 },
});
});
@@ -1,21 +1,21 @@
import React from 'react';
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { t } from '@vegaprotocol/react-helpers';
import { t, useScreenDimensions } from '@vegaprotocol/react-helpers';
import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
interface TxsInfiniteListProps {
hasMoreTxs: boolean;
areTxsLoading: boolean | undefined;
txs: ChainExplorerTxResponse[] | undefined;
txs: BlockExplorerTransactionResult[] | undefined;
loadMoreTxs: () => void;
error: Error | undefined;
className?: string;
}
interface ItemProps {
index: ChainExplorerTxResponse;
index: BlockExplorerTransactionResult;
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(`${error}`);
content = t(`Cannot fetch transaction: ${error}`);
} else if (isLoading) {
content = t('Loading...');
} else {
const { TxHash, PubKey, Type, Command, Sig, Nonce } = index;
const { hash, submitter, type, command, block, index: blockIndex } = index;
content = (
<TxsInfiniteListItem
Type={Type}
Command={Command}
Sig={Sig}
PubKey={PubKey}
Nonce={Nonce}
TxHash={TxHash}
type={type}
command={command}
submitter={submitter}
hash={hash}
block={block}
index={blockIndex}
/>
);
}
@@ -55,6 +55,9 @@ export const TxsInfiniteList = ({
error,
className,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
if (!txs) {
return <div>No items</div>;
}
@@ -71,10 +74,15 @@ export const TxsInfiniteList = ({
return (
<div className={className} data-testid="transactions-list">
<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 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>
<div data-testid="infinite-scroll-wrapper">
<InfiniteLoader
@@ -87,7 +95,7 @@ export const TxsInfiniteList = ({
className="List"
height={595}
itemCount={itemCount}
itemSize={41}
itemSize={isStacked ? 134 : 72}
onItemsRendered={onItemsRendered}
ref={ref}
width={'100%'}
@@ -0,0 +1,80 @@
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,6 +13,7 @@ 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'),
},
@@ -0,0 +1,91 @@
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 "@vegaprotocol/types";
import { AccountType } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: AssetsQuery
@@ -12,7 +12,7 @@ import { AccountType } from "@vegaprotocol/types";
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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
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;
}
@@ -3,29 +3,13 @@
// @generated
// This file was automatically generated and should not be edited.
import { ProposalState, ProposalRejectionReason, VoteValue } from "@vegaprotocol/types";
import { ProposalState, ProposalRejectionReason, VoteValue } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: ProposalsQuery
// ====================================================
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 {
export interface ProposalsQuery_proposals_party {
__typename: "Party";
/**
* Party identifier
@@ -33,11 +17,11 @@ export interface ProposalsQuery_proposalsConnection_edges_node_party {
id: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateAsset {
__typename: "UpdateAsset" | "NewFreeform";
export interface ProposalsQuery_proposals_terms_change_NewFreeform {
__typename: "NewFreeform";
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument {
export interface ProposalsQuery_proposals_terms_change_NewMarket_instrument {
__typename: "InstrumentConfiguration";
/**
* Full and fairly descriptive name for the instrument
@@ -45,20 +29,20 @@ export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewM
name: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket {
export interface ProposalsQuery_proposals_terms_change_NewMarket {
__typename: "NewMarket";
/**
* New market instrument configuration
*/
instrument: ProposalsQuery_proposalsConnection_edges_node_terms_change_NewMarket_instrument;
instrument: ProposalsQuery_proposals_terms_change_NewMarket_instrument;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateMarket {
export interface ProposalsQuery_proposals_terms_change_UpdateMarket {
__typename: "UpdateMarket";
marketId: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_BuiltinAsset {
export interface ProposalsQuery_proposals_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
@@ -66,29 +50,29 @@ export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewA
maxFaucetAmountMint: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset_source_ERC20 {
export interface ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
* The address of the erc20 contract
*/
contractAddress: string;
}
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 type ProposalsQuery_proposals_terms_change_NewAsset_source = ProposalsQuery_proposals_terms_change_NewAsset_source_BuiltinAsset | ProposalsQuery_proposals_terms_change_NewAsset_source_ERC20;
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_NewAsset {
export interface ProposalsQuery_proposals_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_proposalsConnection_edges_node_terms_change_NewAsset_source;
source: ProposalsQuery_proposals_terms_change_NewAsset_source;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter {
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter {
__typename: "NetworkParameter";
/**
* The name of the network parameter
@@ -100,14 +84,14 @@ export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_Upda
value: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter {
export interface ProposalsQuery_proposals_terms_change_UpdateNetworkParameter {
__typename: "UpdateNetworkParameter";
networkParameter: ProposalsQuery_proposalsConnection_edges_node_terms_change_UpdateNetworkParameter_networkParameter;
networkParameter: ProposalsQuery_proposals_terms_change_UpdateNetworkParameter_networkParameter;
}
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 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 interface ProposalsQuery_proposalsConnection_edges_node_terms {
export interface ProposalsQuery_proposals_terms {
__typename: "ProposalTerms";
/**
* RFC3339Nano time and date when voting closes for this proposal.
@@ -117,24 +101,23 @@ export interface ProposalsQuery_proposalsConnection_edges_node_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 | null;
enactmentDatetime: string;
/**
* Actual change being introduced by the proposal - action the proposal triggers if passed and enacted.
*/
change: ProposalsQuery_proposalsConnection_edges_node_terms_change;
change: ProposalsQuery_proposals_terms_change;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary {
__typename: "StakingSummary";
export interface ProposalsQuery_proposals_votes_yes_votes_party_stake {
__typename: "PartyStake";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party {
export interface ProposalsQuery_proposals_votes_yes_votes_party {
__typename: "Party";
/**
* Party identifier
@@ -143,10 +126,10 @@ export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_p
/**
* The staking information for this Party
*/
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party_stakingSummary;
stake: ProposalsQuery_proposals_votes_yes_votes_party_stake;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes {
export interface ProposalsQuery_proposals_votes_yes_votes {
__typename: "Vote";
/**
* The vote value cast
@@ -155,38 +138,38 @@ export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes_party;
party: ProposalsQuery_proposals_votes_yes_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_yes {
export interface ProposalsQuery_proposals_votes_yes {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
* Total tokens of governance token from the votes casted for this side
*/
totalTokens: string;
/**
* Total number of votes cast for this side
* Total number of votes casted for this side
*/
totalNumber: string;
/**
* All votes cast for this side
* All votes casted for this side
*/
votes: ProposalsQuery_proposalsConnection_edges_node_votes_yes_votes[] | null;
votes: ProposalsQuery_proposals_votes_yes_votes[] | null;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary {
__typename: "StakingSummary";
export interface ProposalsQuery_proposals_votes_no_votes_party_stake {
__typename: "PartyStake";
/**
* The stake currently available for the party
*/
currentStakeAvailable: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party {
export interface ProposalsQuery_proposals_votes_no_votes_party {
__typename: "Party";
/**
* Party identifier
@@ -195,10 +178,10 @@ export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_pa
/**
* The staking information for this Party
*/
stakingSummary: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party_stakingSummary;
stake: ProposalsQuery_proposals_votes_no_votes_party_stake;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes {
export interface ProposalsQuery_proposals_votes_no_votes {
__typename: "Vote";
/**
* The vote value cast
@@ -207,53 +190,49 @@ export interface ProposalsQuery_proposalsConnection_edges_node_votes_no_votes {
/**
* The party casting the vote
*/
party: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes_party;
party: ProposalsQuery_proposals_votes_no_votes_party;
/**
* RFC3339Nano time and date when the vote reached Vega network
*/
datetime: string;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes_no {
export interface ProposalsQuery_proposals_votes_no {
__typename: "ProposalVoteSide";
/**
* Total number of governance tokens from the votes cast for this side
* Total tokens of governance token from the votes casted for this side
*/
totalTokens: string;
/**
* Total number of votes cast for this side
* Total number of votes casted for this side
*/
totalNumber: string;
/**
* All votes cast for this side
* All votes casted for this side
*/
votes: ProposalsQuery_proposalsConnection_edges_node_votes_no_votes[] | null;
votes: ProposalsQuery_proposals_votes_no_votes[] | null;
}
export interface ProposalsQuery_proposalsConnection_edges_node_votes {
export interface ProposalsQuery_proposals_votes {
__typename: "ProposalVotes";
/**
* Yes votes cast for this proposal
*/
yes: ProposalsQuery_proposalsConnection_edges_node_votes_yes;
yes: ProposalsQuery_proposals_votes_yes;
/**
* No votes cast for this proposal
*/
no: ProposalsQuery_proposalsConnection_edges_node_votes_no;
no: ProposalsQuery_proposals_votes_no;
}
export interface ProposalsQuery_proposalsConnection_edges_node {
export interface ProposalsQuery_proposals {
__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;
/**
* Rationale behind the proposal
*/
rationale: ProposalsQuery_proposalsConnection_edges_node_rationale;
/**
* A UUID reference to aid tracking proposals on Vega
* A UUID reference to aid tracking proposals on VEGA
*/
reference: string;
/**
@@ -271,36 +250,20 @@ export interface ProposalsQuery_proposalsConnection_edges_node {
/**
* Party that prepared the proposal
*/
party: ProposalsQuery_proposalsConnection_edges_node_party;
party: ProposalsQuery_proposals_party;
/**
* Terms of the proposal
*/
terms: ProposalsQuery_proposalsConnection_edges_node_terms;
terms: ProposalsQuery_proposals_terms;
/**
* Votes cast for this proposal
*/
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;
votes: ProposalsQuery_proposals_votes;
}
export interface ProposalsQuery {
/**
* All governance proposals in the Vega network
* All governance proposals in the VEGA network
*/
proposalsConnection: ProposalsQuery_proposalsConnection;
proposals: ProposalsQuery_proposals[] | null;
}
@@ -4,90 +4,95 @@ 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_proposalsConnection_edges_node,
ProposalsQuery_proposals_terms_change,
} from './__generated__/ProposalsQuery';
const PROPOSALS_QUERY = gql`
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`
query ProposalsQuery {
proposalsConnection {
edges {
node {
id
rationale {
title
description
proposals {
id
reference
state
datetime
rejectionReason
party {
id
}
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
instrument {
name
}
}
reference
state
datetime
rejectionReason
party {
id
... on UpdateMarket {
marketId
}
terms {
closingDatetime
enactmentDatetime
change {
... on NewMarket {
instrument {
name
}
... on NewAsset {
__typename
symbol
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on UpdateMarket {
marketId
}
... on NewAsset {
__typename
symbol
source {
... on BuiltinAsset {
maxFaucetAmountMint
}
... on ERC20 {
contractAddress
}
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
... on ERC20 {
contractAddress
}
}
}
... on UpdateNetworkParameter {
networkParameter {
key
value
}
}
}
}
votes {
yes {
totalTokens
totalNumber
votes {
yes {
totalTokens
totalNumber
votes {
value
party {
id
stakingSummary {
currentStakeAvailable
}
}
datetime
value
party {
id
stake {
currentStakeAvailable
}
}
no {
totalTokens
totalNumber
votes {
value
party {
id
stakingSummary {
currentStakeAvailable
}
}
datetime
datetime
}
}
no {
totalTokens
totalNumber
votes {
value
party {
id
stake {
currentStakeAvailable
}
}
datetime
}
}
}
@@ -96,12 +101,9 @@ const PROPOSALS_QUERY = gql`
`;
const Governance = () => {
const { data } = useQuery<ProposalsQuery>(PROPOSALS_QUERY, {
const { data } = useQuery<ProposalsQuery>(PROPOSAL_QUERY, {
errorPolicy: 'ignore',
});
const proposals = getProposals(
data
) as ProposalsQuery_proposalsConnection_edges_node[];
if (!data) return null;
return (
@@ -109,11 +111,9 @@ const Governance = () => {
<RouteTitle data-testid="governance-header">
{t('Governance Proposals')}
</RouteTitle>
{proposals.map((p) => (
{data.proposals?.map((p) => (
<React.Fragment key={p.id}>
<SubHeading>
{p.rationale.title || p.rationale.description}
</SubHeading>
<SubHeading>{getProposalName(p.terms.change)}</SubHeading>
<SyntaxHighlighter data={p} />
</React.Fragment>
))}
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { MarketTradingMode, MarketState, AccountType, AuctionTrigger } from "@vegaprotocol/types";
import { MarketTradingMode, MarketState, AccountType, AuctionTrigger } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// 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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
decimals: number;
/**
@@ -108,11 +108,11 @@ export interface MarketsQuery_markets_tradableInstrument_riskModel_LogNormalRisk
*/
r: number;
/**
* sigma parameter, annualised volatility of the underlying asset, must be a strictly non-negative real number
* sigma parameter
*/
sigma: number;
/**
* mu parameter, annualised growth rate of the underlying asset
* mu parameter
*/
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, 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 parameter of the risk model
*/
tau: number;
/**
* 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
* Lambda parameter of the risk model
*/
riskAversionParameter: number;
/**
* Parameters for the log normal risk model
* Params 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 its theoretical level over the specified horizon at the specified
* breach it's 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 its theoretical level over the specified horizon at the specified
* breach it's 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 owned by this liquidity provider (float)
* The share own 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 (an unsigned integer)
* the mark price (actually an unsigned int)
*/
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 that initiated this market
* The proposal which initiated this market
*/
proposal: MarketsQuery_markets_proposal | null;
/**
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "@vegaprotocol/types";
import { OracleSpecStatus, PropertyKeyType, ConditionOperator } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// 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;
/**
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "@vegaprotocol/types";
import { AccountType } from "./../../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: PartyAssetsQuery
@@ -12,7 +12,7 @@ import { AccountType } from "@vegaprotocol/types";
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. Should match the decimal precision of the asset on its native chain, e.g: for ERC20 assets, it is often 18
* The precision of the asset
*/
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;
}
+113 -11
View File
@@ -1,17 +1,27 @@
import { useQuery } from '@apollo/client';
import { gql } from '@apollo/client';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import React from 'react';
import {
t,
useFetch,
addDecimalsFormatNumber,
useScreenDimensions,
} from '@vegaprotocol/react-helpers';
import React, { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { RouteTitle } from '../../../components/route-title';
import { SubHeading } from '../../../components/sub-heading';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
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 { 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!) {
@@ -50,13 +60,19 @@ 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='${party?.replace('0x', '')}'"`
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${partyId}'"`
);
const { data } = useQuery<PartyAssetsQuery, PartyAssetsQueryVariables>(
@@ -64,18 +80,104 @@ const Party = () => {
{
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId: party?.replace('0x', '') || '' },
variables: { partyId },
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>
<RouteTitle data-testid="parties-header">{t('Party')}</RouteTitle>
<h1
className="font-alpha uppercase font-xl mb-4 text-zinc-800 dark:text-zinc-200"
data-testid="parties-header"
>
{t('Party')}
</h1>
{data ? (
<>
{header}
<SubHeading>{t('Asset data')}</SubHeading>
<SyntaxHighlighter data={data} />
{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>
</>
) : null}
@@ -17,7 +17,6 @@ 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 { TxsHome, TxsHomeFallback } from './txs/home';
import { TxsList } 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: flags.txsList ? <TxsHome /> : <TxsHomeFallback />,
element: <TxsList />,
},
],
},
+10 -113
View File
@@ -1,86 +1,19 @@
import { DATA_SOURCES } from '../../../config';
import { useCallback, useState, useMemo } from 'react';
import { t, useFetch } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../../components/route-title';
import { BlocksRefetch } from '../../../components/blocks';
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';
import { TxsInfiniteList, TxsStatsInfo } from '../../../components/txs';
import { useTxsData } from '../../../hooks/use-txs-data';
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]);
const BE_TXS_PER_REQUEST = 20;
export const TxsList = () => {
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } =
useTxsData({ limit: BE_TXS_PER_REQUEST });
return (
<section>
<section className="md:p-2 lg:p-4 xl:p-6">
<RouteTitle>{t('Transactions')}</RouteTitle>
<BlocksRefetch
refetch={() =>
refetch(
undefined,
JSON.stringify({
...reusedBodyParams,
page_number: 1,
})
)
}
/>
<BlocksRefetch refetch={refreshTxs} />
<TxsStatsInfo className="!my-12 py-8" />
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
@@ -89,42 +22,6 @@ const Txs = ({ latestBlockHeight }: TxsProps) => {
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>
</>
);
+38 -28
View File
@@ -1,49 +1,59 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { Link, 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 { t } from '@vegaprotocol/react-helpers';
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';
const Tx = () => {
const { txHash } = useParams<{ txHash: string }>();
const hash = txHash ? toNonHex(txHash) : '';
const {
state: { data: tTxData, loading: tTxLoading, error: tTxError },
} = useFetch<TendermintTransactionResponse>(
`${DATA_SOURCES.tendermintUrl}/tx?hash=${txHash}`
state: { data, loading: tTxLoading, error: tTxError },
} = useFetch<BlockExplorerTransaction>(
`${DATA_SOURCES.blockExplorerUrl}/transactions/${toNonHex(hash)}`
);
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>
<RouteTitle>{t('Transaction details')}</RouteTitle>
<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"
/>
<RenderFetched error={tTxError} loading={tTxLoading}>
<TxDetails
className="mb-28"
txData={tTxData?.result}
pubKey={ceTxData?.PubKey}
/>
</RenderFetched>
<>
<TxDetails
className="mb-28"
txData={data?.transaction}
pubKey={data?.transaction.submitter}
/>
<h2 className="text-2xl uppercase mb-4">{t('Transaction content')}</h2>
<RenderFetched error={ceTxError} loading={ceTxLoading}>
<TxContent data={ceTxData} />
<TxContent data={data?.transaction} />
</>
</RenderFetched>
</section>
);
@@ -1,21 +1,14 @@
import { t } from '@vegaprotocol/react-helpers';
import { StatusMessage } from '../../../components/status-message';
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';
import { NestedDataList } from '../../../components/nested-data-list';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
interface TxContentProps {
data: ChainExplorerTxResponse | undefined;
data: BlockExplorerTransactionResult | undefined;
}
export const TxContent = ({ data }: TxContentProps) => {
if (!data?.Command) {
if (!data?.command) {
return (
<StatusMessage>
{t('Could not retrieve transaction content')}
@@ -23,21 +16,5 @@ export const TxContent = ({ data }: TxContentProps) => {
);
}
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)} />
</>
);
return <NestedDataList data={data.command} />;
};
@@ -1,44 +1,30 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { truncateByChars } from '@vegaprotocol/react-helpers';
import { TxDetails, txDetailsTruncateLength } from './tx-details';
import type { Result } from '../tendermint-transaction-response.d';
import { TxDetails } from './tx-details';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
const pubKey = 'test';
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
const height = '52987';
const tx =
'ClMIpeWjmKnn77FNEPedA5J9QhJAOGI3YjQzMWNlMmNhNzc4MWMzMTQ1M2IyYjc0MWYwMTJlNzQ1MzBhZDhjMDgzODVkMWQ1YjRiY2VkMTJiNDc1MhKTAQqAATM5NDFlNmExMzQ3MGVhNTlhNGExNmQzMjRiYzlkZjI5YWZkMzYxMDRiZjQ5MzEwZWMxM2ZiOTMxNTM2NGY3ZjU2ZTQyOTJmYTAyZDlhNTBlZDc0OWE0ZjExMzJiNjM2ZTZmMzQ3YzQ2NjdkYmM5OThmYzcyZjYzYzQxMzU4ZTAzEgx2ZWdhL2VkMjU1MTkYAYB9AtI+QDA5MzFhOGZkOGNjOTM1NDU4ZjQ3MGU0MzVhMDU0MTQzODdjZWE2ZjMyOWQ2NDhiZTg5NGZjZDQ0YmQ1MTdhMmI=';
const txData = {
const txData: BlockExplorerTransactionResult = {
hash,
height,
tx,
block: height,
index: 0,
tx_result: {
code: 0,
data: null,
log: '',
info: '',
events: [],
gas_wanted: '0',
gas_used: '0',
codespace: '',
},
submitter: pubKey,
code: 0,
cursor: `${height}.0`,
type: 'type',
command: {},
};
const renderComponent = (txData: Result) => (
const renderComponent = (txData: BlockExplorerTransactionResult) => (
<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();
@@ -48,18 +34,4 @@ 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,18 +1,12 @@
import { Routes } from '../../route-names';
import { ButtonLink, CopyWithTooltip } from '@vegaprotocol/ui-toolkit';
import {
TableWithTbody,
TableCell,
TableHeader,
TableRow,
} from '../../../components/table';
import { TruncateInline } from '../../../components/truncate/truncate';
import { t } from '@vegaprotocol/react-helpers';
import { HighlightedLink } from '../../../components/highlighted-link';
import type { Result } from '../tendermint-transaction-response.d';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import React from 'react';
import { TruncateInline } from '../../../components/truncate/truncate';
import { Link } from 'react-router-dom';
interface TxDetailsProps {
txData: Result | undefined;
txData: BlockExplorerTransactionResult | undefined;
pubKey: string | undefined;
className?: string;
}
@@ -21,54 +15,35 @@ export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
if (!txData) {
return <>{t('Awaiting Tendermint transaction details')}</>;
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const truncatedSubmitter = (
<TruncateInline text={pubKey || ''} startChars={5} endChars={5} />
);
return (
<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"
<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}`}
>
<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>
{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>
);
};
@@ -0,0 +1,18 @@
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 "@vegaprotocol/types";
import { NodeStatus } from "./../../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// 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 from which you can get more info about the node.
* URL where I can find out more info on the node. Will this be possible?
*/
infoUrl: string;
avatarUrl: string | null;
/**
* Public key of the node operator
* Pubkey of the node operator
*/
pubkey: string;
/**
* Tendermint public key of the node
* Public key of Tendermint
*/
tmPubkey: string;
/**
* Ethereum public key of the node
*/
ethereumAddress: string;
ethereumAdddress: string;
/**
* Country code for the location of the node
*/
location: string;
/**
* The amount of stake the node has put up themselves
* The amount the node has put up themselves
*/
stakedByOperator: string;
/**
@@ -18,7 +18,7 @@ const NODES_QUERY = gql`
avatarUrl
pubkey
tmPubkey
ethereumAddress
ethereumAdddress
location
stakedByOperator
stakedByDelegates
+2 -2
View File
@@ -20,7 +20,7 @@
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
<script src="./assets/env-config.js"></script>
</head>
<body>
<div id="root"></div>
<body class="dark:bg-black h-full w-full">
<div id="root" class="h-full w-full"></div>
</body>
</html>
+9 -1
View File
@@ -1,6 +1,9 @@
const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme');
const {
VegaColours,
} = require('../../libs/tailwindcss-config/src/vega-colours');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
module.exports = {
@@ -11,7 +14,12 @@ module.exports = {
],
darkMode: 'class',
theme: {
extend: theme,
extend: {
...theme,
colors: {
vega: VegaColours,
},
},
},
plugins: [vegaCustomClasses],
};
@@ -0,0 +1,3 @@
{
"hosts": ["https://api.n00.mainnet-mirror.vega.xyz/graphql"]
}
@@ -0,0 +1 @@
[]
+1 -1
View File
@@ -1,4 +1,4 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
CYPRESS_VEGA_ENV=MAINNET
NX_VEGA_REST=https://api.token.vega.xyz/
NX_VEGA_REST=https://api.vega.xyz/
+1 -1
View File
@@ -1,3 +1,3 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
+1 -1
View File
@@ -1,3 +1,3 @@
# App configuration variables. No overrides for default urls
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
+1 -1
View File
@@ -4,7 +4,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_REST=https://api.n07.testnet.vega.xyz
NX_VEGA_REST=https://api.vega.xyz
NX_FAIRGROUND=false
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_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
+2 -2
View File
@@ -1,10 +1,10 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
NX_VEGA_URL=https://api.token.vega.xyz/query
NX_VEGA_URL=https://api.vega.xyz/query
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_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_REST=https://api.token.vega.xyz/
NX_VEGA_REST=https://api.vega.xyz/
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
+10
View File
@@ -0,0 +1,10 @@
# App configuration variables
NX_VEGA_ENV=MIRROR
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
NX_VEGA_URL=https://api.n00.mainnet-mirror.vega.xyz/query
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_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_EXPLORER_URL=https://mirror.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
+1 -1
View File
@@ -2,7 +2,7 @@
**_Control panel for your VEGA tokens_**
<img width="1438" alt="Screenshot 2021-12-11 at 06 32 51" src="https://user-images.githubusercontent.com/13255539/145666935-563fc1ff-35bc-4cd9-ae6d-cf711cc23454.png">
<img width="1437" alt="Screenshot 2021-12-11 at 06 32 51" src="https://user-images.githubusercontent.com/13255539/145666935-563fc1ff-35bc-4cd9-ae6d-cf711cc23454.png">
## Features
+56 -36
View File
@@ -1,6 +1,6 @@
import './i18n';
import React, { useMemo } from 'react';
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -25,49 +25,69 @@ import {
} from '@vegaprotocol/environment';
import { createClient } from './lib/apollo-client';
import { createConnectors } from './lib/web3-connectors';
import type { EthereumConfig } from '@vegaprotocol/web3';
const Web3Container = ({
chainId,
providerUrl,
}: {
chainId: number;
providerUrl: string;
}) => {
const sideBar = React.useMemo(() => {
return [<EthWallet />, <VegaWallet />];
}, []);
const Connectors = React.useMemo(() => {
return createConnectors(providerUrl, Number(chainId));
}, [chainId, providerUrl]);
return (
<Web3Provider connectors={Connectors}>
<Web3Connector connectors={Connectors} chainId={Number(chainId)}>
<VegaWalletProvider>
<ContractsProvider>
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1300px] mx-auto grid grid-rows-[1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700">
<NetworkInfo />
</footer>
</div>
<VegaWalletDialogs />
<TransactionModal />
</>
</BalanceManager>
</AppLoader>
</ContractsProvider>
</VegaWalletProvider>
</Web3Connector>
</Web3Provider>
);
};
const AppContainer = () => {
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
const { config, loading, error } = useEthereumConfig();
const { ETHEREUM_PROVIDER_URL } = useEnvironment();
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id));
}
return undefined;
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
return (
<Router>
<AppStateProvider>
<div className="grid min-h-full text-white">
<AsyncRenderer loading={loading} data={config} error={error}>
{Connectors && (
<Web3Provider connectors={Connectors}>
<Web3Connector>
<VegaWalletProvider>
<ContractsProvider>
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1300px] mx-auto grid grid-rows-[1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 border-t border-neutral-700">
<NetworkInfo />
</footer>
</div>
<VegaWalletDialogs />
<TransactionModal />
</>
</BalanceManager>
</AppLoader>
</ContractsProvider>
</VegaWalletProvider>
</Web3Connector>
</Web3Provider>
)}
</AsyncRenderer>
<AsyncRenderer<EthereumConfig | null>
loading={loading}
data={config}
error={error}
render={(cnf) =>
cnf && (
<Web3Container
chainId={Number(cnf.chain_id)}
providerUrl={ETHEREUM_PROVIDER_URL}
/>
)
}
/>
</div>
</AppStateProvider>
</Router>
@@ -3,7 +3,7 @@
// @generated
// This file was automatically generated and should not be edited.
import { AccountType } from "@vegaprotocol/types";
import { AccountType } from "./../../../../../../libs/types/src/__generated__/globalTypes";
// ====================================================
// GraphQL query operation: Delegations
@@ -17,35 +17,6 @@ export interface Delegations_epoch {
id: string;
}
export interface Delegations_party_delegations_node {
__typename: "Node";
/**
* The node URL eg n01.vega.xyz
*/
id: string;
name: string;
}
export interface Delegations_party_delegations {
__typename: "Delegation";
/**
* The amount field formatted by the client
*/
amountFormatted: string;
/**
* Amount delegated
*/
amount: string;
/**
* URL of node you are delegating to
*/
node: Delegations_party_delegations_node;
/**
* Epoch of delegation
*/
epoch: number;
}
export interface Delegations_party_stake {
__typename: "PartyStake";
/**
@@ -65,7 +36,7 @@ export interface Delegations_party_accounts_asset_source_BuiltinAsset {
export interface Delegations_party_accounts_asset_source_ERC20 {
__typename: "ERC20";
/**
* The address of the ERC20 contract
* The address of the erc20 contract
*/
contractAddress: string;
}
@@ -79,11 +50,11 @@ export interface Delegations_party_accounts_asset {
*/
name: string;
/**
* The ID of the asset
* The id of the asset
*/
id: string;
/**
* 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
* The precision of the asset
*/
decimals: number;
/**
@@ -91,7 +62,7 @@ export interface Delegations_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: Delegations_party_accounts_asset_source;
}
@@ -118,7 +89,6 @@ export interface Delegations_party {
* Party identifier
*/
id: string;
delegations: Delegations_party_delegations[] | null;
/**
* The staking information for this Party
*/
@@ -131,11 +101,11 @@ export interface Delegations_party {
export interface Delegations {
/**
* get data for a specific epoch, if ID omitted it gets the current epoch. If the string is 'next', fetch the next epoch
* get data for a specific epoch, if id omitted it gets the current epoch. If the string is 'next', fetch the next epoch
*/
epoch: Delegations_epoch;
/**
* An entity that is trading on the Vega network
* An entity that is trading on the VEGA network
*/
party: Delegations_party | null;
}
+125 -177
View File
@@ -1,24 +1,22 @@
import { gql, useApolloClient } from '@apollo/client';
import * as Sentry from '@sentry/react';
import { gql, useQuery } from '@apollo/client';
import keyBy from 'lodash/keyBy';
import uniq from 'lodash/uniq';
import React from 'react';
import { useTranslation } from 'react-i18next';
import noIcon from '../../images/token-no-icon.png';
import vegaBlack from '../../images/vega_black.png';
import { BigNumber } from '../../lib/bignumber';
import { addDecimal } from '../../lib/decimals';
import type { WalletCardAssetProps } from '../wallet-card';
import type {
Delegations,
Delegations_party_delegations,
DelegationsVariables,
} from './__generated__/Delegations';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useContracts } from '../../contexts/contracts/contracts-context';
import { isAssetTypeERC20 } from '@vegaprotocol/react-helpers';
import { isAssetTypeERC20, toBigNum } from '@vegaprotocol/react-helpers';
import { AccountType } from '@vegaprotocol/types';
import { usePartyDelegations } from './use-party-delegations';
import { useAppState } from '../../contexts/app-state/app-state-context';
const DELEGATIONS_QUERY = gql`
query Delegations($partyId: ID!) {
@@ -27,15 +25,6 @@ const DELEGATIONS_QUERY = gql`
}
party(id: $partyId) {
id
delegations {
amountFormatted @client
amount
node {
id
name
}
epoch
}
stake {
currentStakeAvailable
currentStakeAvailableFormatted @client
@@ -61,173 +50,132 @@ const DELEGATIONS_QUERY = gql`
`;
export const usePollForDelegations = () => {
const {
appState: { decimals },
} = useAppState();
const { token: vegaToken } = useContracts();
const { t } = useTranslation();
const { keypair } = useVegaWallet();
const client = useApolloClient();
const [delegations, setDelegations] = React.useState<
Delegations_party_delegations[]
>([]);
const [delegatedNodes, setDelegatedNodes] = React.useState<
const delegations = usePartyDelegations(keypair?.pub);
const { data } = useQuery<Delegations, DelegationsVariables>(
DELEGATIONS_QUERY,
{
nodeId: string;
name: string;
hasStakePending: boolean;
currentEpochStake?: BigNumber;
nextEpochStake?: BigNumber;
}[]
>([]);
const [accounts, setAccounts] = React.useState<WalletCardAssetProps[]>([]);
const [currentStakeAvailable, setCurrentStakeAvailable] =
React.useState<BigNumber>(new BigNumber(0));
React.useEffect(() => {
// eslint-disable-next-line
let interval: any;
let mounted = true;
if (keypair?.pub) {
// start polling for delegation
interval = setInterval(() => {
client
.query<Delegations, DelegationsVariables>({
query: DELEGATIONS_QUERY,
variables: { partyId: keypair.pub },
fetchPolicy: 'network-only',
})
.then((res) => {
if (!mounted) return;
const filter =
res.data.party?.delegations?.filter((d) => {
return d.epoch.toString() === res.data.epoch.id;
}) || [];
const sortedDelegations = [...filter].sort((a, b) => {
return new BigNumber(b.amountFormatted)
.minus(a.amountFormatted)
.toNumber();
});
setDelegations(sortedDelegations);
setCurrentStakeAvailable(
new BigNumber(
res.data.party?.stake.currentStakeAvailableFormatted || 0
)
);
const accounts = res.data.party?.accounts || [];
setAccounts(
accounts
.filter((a) => a.type === AccountType.ACCOUNT_TYPE_GENERAL)
.map((a) => {
const isVega =
isAssetTypeERC20(a.asset) &&
a.asset.source.contractAddress === vegaToken.address;
return {
isVega,
name: a.asset.name,
subheading: isVega ? t('collateral') : a.asset.symbol,
symbol: a.asset.symbol,
decimals: a.asset.decimals,
balance: new BigNumber(
addDecimal(new BigNumber(a.balance), a.asset.decimals)
),
image: isVega ? vegaBlack : noIcon,
border: isVega,
address: isAssetTypeERC20(a.asset)
? a.asset.source.contractAddress
: undefined,
};
})
.sort((a, b) => {
// Put VEGA at the top of the list
if (a.isVega) {
return -1;
}
if (b.isVega) {
return 1;
}
// Secondary sort by name
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
})
);
const delegatedNextEpoch = keyBy(
res.data.party?.delegations?.filter((d) => {
return d.epoch === Number(res.data.epoch.id) + 1;
}) || [],
'node.id'
);
const delegatedThisEpoch = keyBy(
res.data.party?.delegations?.filter((d) => {
return d.epoch === Number(res.data.epoch.id);
}) || [],
'node.id'
);
const nodesDelegated = uniq([
...Object.keys(delegatedNextEpoch),
...Object.keys(delegatedThisEpoch),
]);
const delegatedAmounts = nodesDelegated
.map((d) => ({
nodeId: d,
name:
delegatedThisEpoch[d]?.node?.name ||
delegatedNextEpoch[d]?.node?.name,
hasStakePending: !!(
(delegatedThisEpoch[d]?.amountFormatted ||
delegatedNextEpoch[d]?.amountFormatted) &&
delegatedThisEpoch[d]?.amountFormatted !==
delegatedNextEpoch[d]?.amountFormatted &&
delegatedNextEpoch[d] !== undefined
),
currentEpochStake:
delegatedThisEpoch[d] &&
new BigNumber(delegatedThisEpoch[d].amountFormatted),
nextEpochStake:
delegatedNextEpoch[d] &&
new BigNumber(delegatedNextEpoch[d].amountFormatted),
}))
.sort((a, b) => {
if (
new BigNumber(a.currentEpochStake || 0).isLessThan(
b.currentEpochStake || 0
)
)
return 1;
if (
new BigNumber(a.currentEpochStake || 0).isGreaterThan(
b.currentEpochStake || 0
)
)
return -1;
if ((!a.name && b.name) || a.name < b.name) return 1;
if ((!b.name && a.name) || a.name > b.name) return -1;
if (a.nodeId > b.nodeId) return 1;
if (a.nodeId < b.nodeId) return -1;
return 0;
});
setDelegatedNodes(delegatedAmounts);
})
.catch((err: Error) => {
Sentry.captureException(err);
// If query fails stop interval. Its almost certain that the query
// will just continue to fail
clearInterval(interval);
});
}, 1000);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
variables: { partyId: keypair?.pub! },
skip: !keypair?.pub,
}
);
return () => {
clearInterval(interval);
mounted = false;
};
}, [client, keypair?.pub, t, vegaToken.address]);
const filter =
delegations?.filter((d) => {
return d.epochSeq === data?.epoch.id;
}) || [];
const sortedDelegations = [...filter].sort((a, b) => {
return new BigNumber(b.amount).minus(a.amount).toNumber();
});
return { delegations, currentStakeAvailable, delegatedNodes, accounts };
const partyAccounts = data?.party?.accounts || [];
const accounts = partyAccounts
.filter((a) => a.type === AccountType.General)
.map((a) => {
const isVega =
isAssetTypeERC20(a.asset) &&
a.asset.source.contractAddress === vegaToken.address;
return {
isVega,
name: a.asset.name,
subheading: isVega ? t('collateral') : a.asset.symbol,
symbol: a.asset.symbol,
decimals: a.asset.decimals,
balance: new BigNumber(
addDecimal(new BigNumber(a.balance), a.asset.decimals)
),
image: isVega ? vegaBlack : noIcon,
border: isVega,
address: isAssetTypeERC20(a.asset)
? a.asset.source.contractAddress
: undefined,
};
})
.sort((a, b) => {
// Put VEGA at the top of the list
if (a.isVega) {
return -1;
}
if (b.isVega) {
return 1;
}
// Secondary sort by name
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
const delegatedNextEpoch = keyBy(
delegations?.filter((d) => {
return Number(d.epochSeq) === Number(data?.epoch.id) + 1;
}) || [],
'nodeId'
);
const delegatedThisEpoch = keyBy(
delegations?.filter((d) => {
return d.epochSeq === data?.epoch.id;
}) || [],
'nodeId'
);
const nodesDelegated = uniq([
...Object.keys(delegatedNextEpoch),
...Object.keys(delegatedThisEpoch),
]);
const delegatedAmounts = nodesDelegated
.map((d) => ({
nodeId: d,
name:
delegatedThisEpoch[d]?.node?.name || delegatedNextEpoch[d]?.node?.name,
hasStakePending: !!(
(delegatedThisEpoch[d]?.amount || delegatedNextEpoch[d]?.amount) &&
delegatedThisEpoch[d]?.amount !== delegatedNextEpoch[d]?.amount &&
delegatedNextEpoch[d] !== undefined
),
currentEpochStake:
delegatedThisEpoch[d] &&
toBigNum(delegatedThisEpoch[d].amount, decimals),
nextEpochStake:
delegatedNextEpoch[d] &&
toBigNum(delegatedNextEpoch[d].amount, decimals),
}))
.sort((a, b) => {
if (
new BigNumber(a.currentEpochStake || 0).isLessThan(
b.currentEpochStake || 0
)
)
return 1;
if (
new BigNumber(a.currentEpochStake || 0).isGreaterThan(
b.currentEpochStake || 0
)
)
return -1;
if ((!a.name && b.name) || a.name || '' < (b.name || '')) return 1;
if ((!b.name && a.name) || a.name || '' > (b.name || '')) return -1;
if (a.nodeId > b.nodeId) return 1;
if (a.nodeId < b.nodeId) return -1;
return 0;
});
return {
delegations: sortedDelegations,
currentStakeAvailable: new BigNumber(
data?.party?.stake.currentStakeAvailableFormatted || 0
),
delegatedNodes: delegatedAmounts,
accounts,
};
};
@@ -0,0 +1,79 @@
import { useFetch } from '@vegaprotocol/react-helpers';
import { useEffect, useMemo } from 'react';
export interface Delegation {
party: string;
nodeId: string;
amount: string;
epochSeq: string;
}
export interface RankingScore {
stakeScore: string;
performanceScore: string;
previousStatus: string;
status: string;
votingPower: number;
rankingScore: string;
}
export interface Node {
id: string;
pubKey: string;
tmPubKey: string;
ethereumAdddress: string;
infoUrl: string;
location: string;
stakedByOperator: string;
stakedByDelegates: string;
stakedTotal: string;
maxIntendedStake: string;
pendingStake: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
epochData?: any;
status: string;
delegations: Delegation[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rewardScore?: any;
rankingScore: RankingScore;
name: string;
avatarUrl: string;
}
export interface NodesQuery {
nodes: Node[];
}
export interface DelegationsQuery {
delegations: Delegation[];
}
export interface DelegationsNode extends Delegation {
node: Node | undefined;
}
export const usePartyDelegations = (partyId: string | undefined) => {
const delegationsUrl = `${process.env['NX_VEGA_REST']}delegations?party=${partyId}`;
const { state: delegationsData, refetch: refetchDelegations } =
useFetch<DelegationsQuery>(delegationsUrl);
const { state: nodesData } = useFetch<NodesQuery>(
`${process.env['NX_VEGA_REST']}nodes`
);
useEffect(() => {
const interval = setInterval(() => refetchDelegations(), 10000);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [refetchDelegations]);
const delegations = useMemo<DelegationsNode[] | undefined>(() => {
if (!delegationsData.data) return [];
return delegationsData.data?.delegations.map((d) => ({
...d,
node: nodesData.data?.nodes.find(({ id }) => d.nodeId === id),
}));
}, [delegationsData.data, nodesData.data?.nodes]);
return delegations;
};
@@ -25,6 +25,7 @@ import { usePollForDelegations } from './hooks';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/react-helpers';
export const VegaWallet = () => {
const { t } = useTranslation();
@@ -122,12 +123,13 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
usePollForDelegations();
const unstaked = React.useMemo(() => {
const totalDelegated = delegations.reduce<BigNumber>(
(acc, cur) => acc.plus(cur.amountFormatted),
new BigNumber(0)
);
const totalDelegated =
delegations?.reduce<BigNumber>(
(acc, cur) => acc.plus(toBigNum(cur.amount, decimals)),
new BigNumber(0)
) || new BigNumber(0);
return BigNumber.max(currentStakeAvailable.minus(totalDelegated), 0);
}, [currentStakeAvailable, delegations]);
}, [currentStakeAvailable, decimals, delegations]);
const footer = (
<div className="flex justify-end">
@@ -62,7 +62,7 @@ export const WalletCardRow = ({
ref={ref}
>
{link ? (
<Link to={link} className="max-w-[200px]">
<Link to={link} className="max-w-[200px] underline">
{label}
</Link>
) : (
@@ -1,51 +1,46 @@
import { useEnvironment } from '@vegaprotocol/environment';
import { useEthereumConfig } from '@vegaprotocol/web3';
import { Button, Splash, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { Web3ConnectDialog } from '@vegaprotocol/web3';
import { useWeb3React } from '@web3-react/core';
import type { ReactElement } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect } from 'react';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { createConnectors } from '../../lib/web3-connectors';
import type { Web3ReactHooks } from '@web3-react/core';
import type { Connector } from '@web3-react/types';
interface Web3ConnectorProps {
children: ReactElement;
connectors: [Connector, Web3ReactHooks][];
chainId: number;
}
export function Web3Connector({ children }: Web3ConnectorProps) {
export function Web3Connector({
children,
connectors,
chainId,
}: Web3ConnectorProps) {
const { appState, appDispatch } = useAppState();
const { ETHEREUM_PROVIDER_URL } = useEnvironment();
const { config, loading, error } = useEthereumConfig();
const Connectors = useMemo(() => {
if (config?.chain_id) {
return createConnectors(ETHEREUM_PROVIDER_URL, Number(config.chain_id));
}
return undefined;
}, [config?.chain_id, ETHEREUM_PROVIDER_URL]);
const setDialogOpen = useCallback(
(isOpen: boolean) => {
appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
},
[appDispatch]
);
const appChainId = Number(config?.chain_id);
const appChainId = Number(chainId);
return (
<AsyncRenderer loading={loading} error={error} data={config}>
<>
<Web3Content appChainId={appChainId} setDialogOpen={setDialogOpen}>
{children}
</Web3Content>
{Connectors && (
<Web3ConnectDialog
connectors={Connectors}
dialogOpen={appState.ethConnectOverlay}
setDialogOpen={setDialogOpen}
desiredChainId={appChainId}
/>
)}
</AsyncRenderer>
<Web3ConnectDialog
connectors={connectors}
dialogOpen={appState.ethConnectOverlay}
setDialogOpen={setDialogOpen}
desiredChainId={appChainId}
/>
</>
);
}
@@ -65,7 +60,7 @@ export const Web3Content = ({ children, appChainId }: Web3ContentProps) => {
// wallet connect doesnt handle connectEagerly being called when connector is also in the
// deps array.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connector]);
}, []);
if (error) {
return (
+4
View File
@@ -36,6 +36,10 @@ export const ContractAddresses: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MIRROR: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
MAINNET: {
claimAddress: '0x0ee1fb382caf98e86e97e51f9f42f8b4654020f3',
lockedAddress: '0x78344c7305d73a7a0ac3c94cd9960f4449a1814e',
+1
View File
@@ -8,6 +8,7 @@ import { BigNumber } from '../lib/bignumber';
const TRANCHES_URLS: { [N in Networks]: string } = {
MAINNET: 'https://static.vega.xyz/assets/mainnet-tranches.json',
MIRROR: 'https://static.vega.xyz/assets/mirror-tranches.json',
TESTNET: 'https://static.vega.xyz/assets/testnet-tranches.json',
STAGNET3: 'https://static.vega.xyz/assets/stagnet3-tranches.json',
DEVNET: 'https://static.vega.xyz/assets/devnet-tranches.json',
+4 -5
View File
@@ -438,7 +438,6 @@
"withdrawPreparedWarningHeading": "Only start a withdrawal when you are ready to pay the gas to release on Ethereum",
"withdrawPreparedWarningText1": "If you proceed beyond this stage, but do not complete the withdrawal on Ethereum, it will not be possible to cancel or alter your withdrawal request.",
"withdrawPreparedWarningText2": "To ensure your assets are not lost, you must pay gas on Ethereum to complete the final step of the withdrawal process. Gas costs per withdrawal have been between $100 and $200.",
"withdrawalsPreparedWarningHeading": "Complete these withdrawals before the next checkpoint restore",
"withdrawalsPreparedWarningText": "Prepared withdrawals are not stored between network resets meaning you will not have the information required to complete a withdrawal.",
"withdrawPageHeading": "Withdraw",
"withdrawPageText": "Use this form to withdraw/release assets from your Vega wallet to their native chain.",
@@ -453,7 +452,7 @@
"withdrawFormSubmitButtonIdle": "Withdraw {{amount}} {{symbol}} tokens",
"withdrawFormSubmitButtonPending": "Preparing",
"withdrawalsTitle": "Withdrawals",
"withdrawalsText": "These withdrawals need to be completed with an Ethereum transaction.",
"withdrawalsText": "Incomplete withdrawals need to be completed with an Ethereum transaction. Be sure to complete withdrawals before the next checkpoint restore.",
"withdrawalsNone": "You don't have any pending withdrawals.",
"withdrawalsCompleteButton": "Finish withdrawal",
"withdrawalTransaction": "Transaction ({{foreignChain}})",
@@ -565,13 +564,12 @@
"proposalTerms": "Proposal terms",
"currentlySetTo": "Currently set to ",
"rankingScore": "Ranking score",
"stakeScore": "Stake score",
"stakeScore": "Share",
"performanceScore": "Performance",
"votingPower": "Voting power",
"noValidators": "No validators",
"validator": "Validator",
"totalStakeThisEpoch": "Total stake this epoch",
"share": "Share",
"validatorStake": "Validator stake",
"Nodes": "Nodes",
"status-tendermint": "Consensus",
@@ -633,5 +631,6 @@
"MinProposalRequirements": "You must have at least 1 VEGA associated to make a proposal",
"totalSupply": "Total Supply",
"viaWallet": "via wallet",
"viaContract": "via vesting"
"viaContract": "via vesting",
"validatorLeavingWarning": "This validator has elected to leave the network and tokens staked to them will no longer earn rewards. You can remove your stake below."
}
-37
View File
@@ -1,37 +0,0 @@
extend type Proposal {
"Whether or the not the proposal is processing on the blockchain"
pending: Boolean!
"Generated name for the proposal"
name: String!
}
extend type Node {
pendingStakeFormatted: String!
stakedByOperatorFormatted: String!
stakedByDelegatesFormatted: String!
stakedTotalFormatted: String!
}
extend type Delegation {
amountFormatted: String!
}
extend type NodeData {
stakedTotalFormatted: String!
}
extend type PartyStake {
currentStakeAvailableFormatted: String!
}
extend type Reward {
amountFormatted: String!
}
extend type RewardPerAssetDetail {
totalAmountFormatted: String!
}
extend type Account {
balanceFormatted: String!
}

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