Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77f084c5de | ||
|
|
3e06c9478c | ||
|
|
326a2e6412 | ||
|
|
5a1bd5a73f | ||
|
|
0416e916e1 | ||
|
|
af3bb2dcd5 | ||
|
|
6229e6f92b | ||
|
|
f03aa655c3 | ||
|
|
20dd1995ca | ||
|
|
266f2462bc | ||
|
|
14052c4088 | ||
|
|
d4231ea531 | ||
|
|
718c93f486 | ||
|
|
4ef5d7f19c | ||
|
|
51f341ea94 | ||
|
|
6d09e4ee64 | ||
|
|
63bfcb6e75 | ||
|
|
66046a9471 | ||
|
|
4263a6b07b | ||
|
|
389291785b | ||
|
|
a1746019b6 | ||
|
|
be38f630d8 | ||
|
|
c49c52a635 | ||
|
|
a94fc689b9 | ||
|
|
25f63d2fd2 | ||
|
|
1ac0cf554f | ||
|
|
48004d836d | ||
|
|
32e2e22c64 | ||
|
|
71c8b31ffe |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+4
-4
@@ -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,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,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
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=2
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: </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: </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: </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: </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 </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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
+22
-22
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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
@@ -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,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,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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+56
-36
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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!
|
||||
}
|
||||
+33
-46
@@ -3,28 +3,12 @@
|
||||
// @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 fragment: ProposalFields
|
||||
// ====================================================
|
||||
|
||||
export interface ProposalFields_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 ProposalFields_party {
|
||||
__typename: "Party";
|
||||
/**
|
||||
@@ -33,8 +17,8 @@ export interface ProposalFields_party {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ProposalFields_terms_change_UpdateAsset {
|
||||
__typename: "UpdateAsset" | "NewFreeform";
|
||||
export interface ProposalFields_terms_change_NewFreeform {
|
||||
__typename: "NewFreeform";
|
||||
}
|
||||
|
||||
export interface ProposalFields_terms_change_NewMarket_instrument_futureProduct_settlementAsset {
|
||||
@@ -71,6 +55,14 @@ export interface ProposalFields_terms_change_NewMarket_instrument {
|
||||
|
||||
export interface ProposalFields_terms_change_NewMarket {
|
||||
__typename: "NewMarket";
|
||||
/**
|
||||
* Decimal places used for the new market
|
||||
*/
|
||||
decimalPlaces: number;
|
||||
/**
|
||||
* Metadata for this instrument, tags
|
||||
*/
|
||||
metadata: string[] | null;
|
||||
/**
|
||||
* New market instrument configuration
|
||||
*/
|
||||
@@ -93,7 +85,7 @@ export interface ProposalFields_terms_change_NewAsset_source_BuiltinAsset {
|
||||
export interface ProposalFields_terms_change_NewAsset_source_ERC20 {
|
||||
__typename: "ERC20";
|
||||
/**
|
||||
* The address of the ERC20 contract
|
||||
* The address of the erc20 contract
|
||||
*/
|
||||
contractAddress: string;
|
||||
}
|
||||
@@ -111,7 +103,7 @@ export interface ProposalFields_terms_change_NewAsset {
|
||||
*/
|
||||
symbol: string;
|
||||
/**
|
||||
* The source of the new asset
|
||||
* the source of the new Asset
|
||||
*/
|
||||
source: ProposalFields_terms_change_NewAsset_source;
|
||||
}
|
||||
@@ -133,7 +125,7 @@ export interface ProposalFields_terms_change_UpdateNetworkParameter {
|
||||
networkParameter: ProposalFields_terms_change_UpdateNetworkParameter_networkParameter;
|
||||
}
|
||||
|
||||
export type ProposalFields_terms_change = ProposalFields_terms_change_UpdateAsset | ProposalFields_terms_change_NewMarket | ProposalFields_terms_change_UpdateMarket | ProposalFields_terms_change_NewAsset | ProposalFields_terms_change_UpdateNetworkParameter;
|
||||
export type ProposalFields_terms_change = ProposalFields_terms_change_NewFreeform | ProposalFields_terms_change_NewMarket | ProposalFields_terms_change_UpdateMarket | ProposalFields_terms_change_NewAsset | ProposalFields_terms_change_UpdateNetworkParameter;
|
||||
|
||||
export interface ProposalFields_terms {
|
||||
__typename: "ProposalTerms";
|
||||
@@ -145,17 +137,16 @@ export interface ProposalFields_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: ProposalFields_terms_change;
|
||||
}
|
||||
|
||||
export interface ProposalFields_votes_yes_votes_party_stakingSummary {
|
||||
__typename: "StakingSummary";
|
||||
export interface ProposalFields_votes_yes_votes_party_stake {
|
||||
__typename: "PartyStake";
|
||||
/**
|
||||
* The stake currently available for the party
|
||||
*/
|
||||
@@ -171,7 +162,7 @@ export interface ProposalFields_votes_yes_votes_party {
|
||||
/**
|
||||
* The staking information for this Party
|
||||
*/
|
||||
stakingSummary: ProposalFields_votes_yes_votes_party_stakingSummary;
|
||||
stake: ProposalFields_votes_yes_votes_party_stake;
|
||||
}
|
||||
|
||||
export interface ProposalFields_votes_yes_votes {
|
||||
@@ -193,21 +184,21 @@ export interface ProposalFields_votes_yes_votes {
|
||||
export interface ProposalFields_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: ProposalFields_votes_yes_votes[] | null;
|
||||
}
|
||||
|
||||
export interface ProposalFields_votes_no_votes_party_stakingSummary {
|
||||
__typename: "StakingSummary";
|
||||
export interface ProposalFields_votes_no_votes_party_stake {
|
||||
__typename: "PartyStake";
|
||||
/**
|
||||
* The stake currently available for the party
|
||||
*/
|
||||
@@ -223,7 +214,7 @@ export interface ProposalFields_votes_no_votes_party {
|
||||
/**
|
||||
* The staking information for this Party
|
||||
*/
|
||||
stakingSummary: ProposalFields_votes_no_votes_party_stakingSummary;
|
||||
stake: ProposalFields_votes_no_votes_party_stake;
|
||||
}
|
||||
|
||||
export interface ProposalFields_votes_no_votes {
|
||||
@@ -245,15 +236,15 @@ export interface ProposalFields_votes_no_votes {
|
||||
export interface ProposalFields_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: ProposalFields_votes_no_votes[] | null;
|
||||
}
|
||||
@@ -273,15 +264,11 @@ export interface ProposalFields_votes {
|
||||
export interface ProposalFields {
|
||||
__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: ProposalFields_rationale;
|
||||
/**
|
||||
* A UUID reference to aid tracking proposals on Vega
|
||||
* A UUID reference to aid tracking proposals on VEGA
|
||||
*/
|
||||
reference: string;
|
||||
/**
|
||||
@@ -296,14 +283,14 @@ export interface ProposalFields {
|
||||
* Reason for the proposal to be rejected by the core
|
||||
*/
|
||||
rejectionReason: ProposalRejectionReason | null;
|
||||
/**
|
||||
* Party that prepared the proposal
|
||||
*/
|
||||
party: ProposalFields_party;
|
||||
/**
|
||||
* Error details of the rejectionReason
|
||||
*/
|
||||
errorDetails: string | null;
|
||||
/**
|
||||
* Party that prepared the proposal
|
||||
*/
|
||||
party: ProposalFields_party;
|
||||
/**
|
||||
* Terms of the proposal
|
||||
*/
|
||||
|
||||
+7
-7
@@ -1,23 +1,23 @@
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import type { ProposalFields } from '../../__generated__/ProposalFields';
|
||||
import type { Proposals_proposals } from '../../proposals/__generated__/Proposals';
|
||||
|
||||
export const CurrentProposalState = ({
|
||||
proposal,
|
||||
}: {
|
||||
proposal: ProposalFields;
|
||||
proposal: Proposals_proposals;
|
||||
}) => {
|
||||
const { state } = proposal;
|
||||
let className = 'text-white';
|
||||
|
||||
if (
|
||||
state === ProposalState.STATE_DECLINED ||
|
||||
state === ProposalState.STATE_FAILED ||
|
||||
state === ProposalState.STATE_REJECTED
|
||||
state === ProposalState.Declined ||
|
||||
state === ProposalState.Failed ||
|
||||
state === ProposalState.Rejected
|
||||
) {
|
||||
className = 'text-danger';
|
||||
} else if (
|
||||
state === ProposalState.STATE_ENACTED ||
|
||||
state === ProposalState.STATE_PASSED
|
||||
state === ProposalState.Enacted ||
|
||||
state === ProposalState.Passed
|
||||
) {
|
||||
className = 'text-white';
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user