Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c419a818 | ||
|
|
d5fef8729a | ||
|
|
8b74e63195 | ||
|
|
8639cf0ff6 | ||
|
|
3d760b8f15 | ||
|
|
8d08ede22d | ||
|
|
6352400598 | ||
|
|
30c440439e | ||
|
|
9ddedd0b78 | ||
|
|
9b356e53b4 | ||
|
|
4db5d37578 | ||
|
|
65564f1afc | ||
|
|
13d4c463cd | ||
|
|
7ba90904d8 | ||
|
|
3e75d64e0d | ||
|
|
3baad78bb7 | ||
|
|
a30f2227c4 | ||
|
|
cf2cfad746 | ||
|
|
972325b04a | ||
|
|
17736cc985 | ||
|
|
910a3e7a7f | ||
|
|
ac17bc4b64 | ||
|
|
399a3daa4c | ||
|
|
afd42f8a9a | ||
|
|
5d3a3a6f11 | ||
|
|
077700910b | ||
|
|
84814f3476 | ||
|
|
c450f57e8d | ||
|
|
6ba7ce5e7f | ||
|
|
d823daeea4 | ||
|
|
7c8a54b35a | ||
|
|
5bc861b5ec | ||
|
|
2f600207f7 | ||
|
|
b67bc33c49 | ||
|
|
03f659ebb3 | ||
|
|
96843cd2be | ||
|
|
af7a5630ac | ||
|
|
512149e491 | ||
|
|
aada0a805a | ||
|
|
9e935449ae | ||
|
|
d5b4ae7e53 | ||
|
|
995f1f1eaf | ||
|
|
02410df2d7 | ||
|
|
0d8f14e050 | ||
|
|
c2d2985e92 | ||
|
|
02f56dd0ec | ||
|
|
88263df9c2 | ||
|
|
71890d662a | ||
|
|
081c9f44ae | ||
|
|
c60e5841f7 | ||
|
|
96e082cb5d | ||
|
|
152574ebb3 | ||
|
|
b5f42ad715 | ||
|
|
86b0c6db3e | ||
|
|
3aed0b2e5f | ||
|
|
9c228c082f | ||
|
|
aa7bed137b | ||
|
|
1b4ffb6f1f | ||
|
|
e2e6ec40b7 | ||
|
|
8a9fa1efdc | ||
|
|
60d1434c40 | ||
|
|
d5efae6419 | ||
|
|
8489d50024 | ||
|
|
7af2580512 |
@@ -67,4 +67,4 @@ jobs:
|
||||
project: ${{ needs.pr.outputs.projects }}
|
||||
vega-version: ${{needs.pr.outputs.vega-version}}
|
||||
gobin: ${{needs.pr.outputs.gobin}}
|
||||
tags: "--env.grepTags='@smoke'"
|
||||
tags: "--env.grepTags='[ @smoke, @regression ]'"
|
||||
|
||||
@@ -4,7 +4,7 @@ name: Publish docker containers
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-pre[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-*'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
@@ -88,12 +88,12 @@ Run `nx test my-app` to execute the unit tests with [Jest](https://jestjs.io), o
|
||||
|
||||
### Using wallet
|
||||
|
||||
To run tests locally using your own wallets you can add the following environment variables to `cypress.json`
|
||||
To run tests locally using your own wallets make sure you have generated at least two public keys and update the following environment variables in `cypress.config.js` to match your wallet. You should also use [vegawallet-dummy](https://github.com/vegaprotocol/vegawallet-dummy) to avoid being prompted in CLI during test execution.
|
||||
|
||||
1. Change `TRADING_TEST_VEGA_WALLET_NAME` to your Vega wallet name
|
||||
2. Add `TRADING_TEST_VEGA_WALLET_PASSPHRASE` as your wallet passphrase
|
||||
3. Add `ETH_WALLET_MNEMONIC` as your Ethereum wallet mnemonic
|
||||
4. Use [vegawallet-dummy](https://github.com/vegaprotocol/vegawallet-dummy) to avoid being prompted in CLI during test execution.
|
||||
1. Set `VEGA_PUBLIC_KEY` and `TRUNCATED_VEGA_PUBLIC_KEY` to your first public key.
|
||||
2. Set `VEGA_PUBLIC_KEY2` and `TRUNCATED_VEGA_PUBLIC_KEY2` to your second public key.
|
||||
3. Set `TRADING_TEST_VEGA_WALLET_PASSPHRASE` as your wallet passphrase
|
||||
4. Add `ETH_WALLET_MNEMONIC` as your Ethereum wallet mnemonic
|
||||
|
||||
### Formatting
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ module.exports = defineConfig({
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-grep/src/plugin')(config);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:4200',
|
||||
@@ -23,7 +23,6 @@ module.exports = defineConfig({
|
||||
viewportHeight: 900,
|
||||
},
|
||||
env: {
|
||||
TRADING_TEST_VEGA_WALLET_NAME: 'UI_Trading_Test',
|
||||
ETHEREUM_PROVIDER_URL:
|
||||
'https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8',
|
||||
VEGA_PUBLIC_KEY:
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
declare namespace Cypress {
|
||||
// specify additional properties in the TestConfig object
|
||||
// in our case we will add "tags" property
|
||||
interface SuiteConfigOverrides {
|
||||
/**
|
||||
* List of tags for this test
|
||||
* @example a single tag
|
||||
* it('logs in', { tags: '@smoke' }, () => { ... })
|
||||
* @example multiple tags
|
||||
* it('works', { tags: ['@smoke', '@slow'] }, () => { ... })
|
||||
*/
|
||||
tags?: string | string[];
|
||||
}
|
||||
|
||||
interface Cypress {
|
||||
grep?: (grep?: string, tags?: string, burn?: string) => void;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { connectVegaWallet } from '../support/vega-wallet';
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
generateMarket,
|
||||
@@ -52,7 +51,7 @@ describe('market selector', { tags: '@smoke' }, () => {
|
||||
it('should be properly rendered', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('input[placeholder="Search"]').should(
|
||||
'have.value',
|
||||
markets[0].tradableInstrument.instrument.name
|
||||
@@ -76,7 +75,7 @@ describe('market selector', { tags: '@smoke' }, () => {
|
||||
it('typing should change list', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('input[placeholder="Search"]').type('{backspace}');
|
||||
cy.getByTestId('market-pane')
|
||||
.children()
|
||||
@@ -112,7 +111,7 @@ describe('market selector', { tags: '@smoke' }, () => {
|
||||
it.skip('keyboard navigation should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('input[placeholder="Search"]').type('{backspace}');
|
||||
cy.get('input[placeholder="Search"]').clear();
|
||||
cy.focused().realPress('ArrowDown');
|
||||
@@ -134,7 +133,7 @@ describe('market selector', { tags: '@smoke' }, () => {
|
||||
if (markets?.length) {
|
||||
cy.viewport('iphone-xr');
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('[role="dialog"]').should('not.exist');
|
||||
cy.getByTestId('arrow-button').click();
|
||||
cy.get('[role="dialog"]').should('be.visible');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { connectVegaWallet } from '../support/vega-wallet';
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
generateSimpleMarkets,
|
||||
@@ -63,7 +62,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('side selector should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').should(
|
||||
'have.text',
|
||||
'Long'
|
||||
@@ -82,7 +81,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
if (markets?.length) {
|
||||
cy.viewport('iphone-xr');
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.getByTestId('next-button').scrollIntoView().click();
|
||||
|
||||
cy.get('button[aria-label="Open long position"]').should(
|
||||
@@ -114,7 +113,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('size slider should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -136,7 +135,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('percentage selection should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -163,7 +162,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('size input should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -191,7 +190,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('slippage value should be displayed', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -207,7 +206,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('allow slippage value to be adjusted', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -235,7 +234,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('notional position size should be present', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -262,7 +261,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('total fees should be displayed', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
@@ -280,7 +279,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
it('order review should display proper calculations', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-3-control').click();
|
||||
|
||||
cy.getByTestId('review-trade')
|
||||
@@ -310,7 +309,7 @@ describe('Market trade', { tags: '@smoke' }, () => {
|
||||
if (markets?.length) {
|
||||
cy.viewport('iphone-xr');
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('#step-3-control').click();
|
||||
|
||||
// Start from the bottom tooltip to ensure the tooltip above
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { connectVegaWallet } from '../support/vega-wallet';
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
import {
|
||||
generatePositions,
|
||||
@@ -22,7 +21,7 @@ describe('Portfolio page - wallet', { tags: '@smoke' }, () => {
|
||||
cy.visit('/');
|
||||
cy.get('[href="/portfolio"]').eq(0).click();
|
||||
cy.getByTestId('trading-connect-wallet').should('be.visible');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
cy.getByTestId('trading-connect-wallet').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -43,7 +42,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
|
||||
it('certain tabs should exist', () => {
|
||||
cy.visit('/portfolio');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
|
||||
cy.getByTestId('assets').click();
|
||||
cy.location('pathname').should('eq', '/portfolio/assets');
|
||||
@@ -74,7 +73,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'Assets', generateAssets());
|
||||
});
|
||||
cy.visit('/portfolio/assets');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('data should be properly rendered', () => {
|
||||
@@ -105,7 +104,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'Assets', generateAssets());
|
||||
});
|
||||
cy.visit('/portfolio/positions');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('data should be properly rendered', () => {
|
||||
@@ -122,7 +121,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'Markets', generateFillsMarkets());
|
||||
});
|
||||
cy.visit('/portfolio/orders');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('data should be properly rendered', () => {
|
||||
@@ -139,7 +138,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'Markets', generateFillsMarkets());
|
||||
});
|
||||
cy.visit('/portfolio/fills');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('data should be properly rendered', () => {
|
||||
@@ -166,7 +165,7 @@ describe('Portfolio page tabs', { tags: '@smoke' }, () => {
|
||||
aliasQuery(req, 'MarketsData', generateMarketsData());
|
||||
});
|
||||
cy.visit('/portfolio');
|
||||
connectVegaWallet();
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('"No data to display" should be always displayed', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import '@vegaprotocol/cypress';
|
||||
import 'cypress-real-events/support';
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
import registerCypressGrep from 'cypress-grep';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
registerCypressGrep();
|
||||
|
||||
|
||||
@@ -1413,6 +1413,7 @@ export const generateMarketData = (): MarketDataQuery => {
|
||||
},
|
||||
marketTradingMode:
|
||||
Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
marketState: Schema.MarketState.STATE_ACTIVE,
|
||||
staticMidPrice: '0',
|
||||
indicativePrice: '0',
|
||||
bestStaticBidPrice: '0',
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
export const connectVegaWallet = () => {
|
||||
const form = 'rest-connector-form';
|
||||
const walletName = Cypress.env('TRADING_TEST_VEGA_WALLET_NAME');
|
||||
const walletPassphrase = Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE');
|
||||
cy.getByTestId('connect-vega-wallet').click();
|
||||
cy.getByTestId('connectors-list')
|
||||
.find('[data-testid="connector-gui"]')
|
||||
.click();
|
||||
cy.getByTestId(form).find('#wallet').click().type(walletName);
|
||||
cy.getByTestId(form).find('#passphrase').click().type(walletPassphrase);
|
||||
cy.getByTestId('rest-connector-form').find('button[type=submit]').click();
|
||||
};
|
||||
@@ -7,7 +7,7 @@
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"allowJs": true,
|
||||
"types": ["cypress", "node", "cypress-real-events", "cypress-grep"]
|
||||
"types": ["cypress", "node", "cypress-real-events", "@cypress/grep"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"]
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts", "index.d.ts"]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import compact from 'lodash/compact';
|
||||
import {
|
||||
DealTicketManager,
|
||||
usePartyBalanceQuery,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
t,
|
||||
useDataProvider,
|
||||
removePaginationWrapper,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { DealTicketSteps } from './deal-ticket-steps';
|
||||
import { DealTicketBalance } from './deal-ticket-balance';
|
||||
@@ -46,8 +49,8 @@ export const DealTicketContainer = () => {
|
||||
skip: !marketId,
|
||||
});
|
||||
|
||||
const accounts = compact(partyData?.party?.accountsConnection?.edges).map(
|
||||
(e) => e.node
|
||||
const accounts = removePaginationWrapper(
|
||||
partyData?.party?.accountsConnection?.edges
|
||||
);
|
||||
|
||||
const loader = <Loader />;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import compact from 'lodash/compact';
|
||||
import { Stepper } from '../stepper';
|
||||
import {
|
||||
getDefaultOrder,
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
removePaginationWrapper,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
useOrderSubmit,
|
||||
@@ -82,8 +82,8 @@ export const DealTicketSteps = ({ market }: DealTicketMarketProps) => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const accounts = compact(partyBalance?.party?.accountsConnection?.edges).map(
|
||||
(e) => e.node
|
||||
const accounts = removePaginationWrapper(
|
||||
partyBalance?.party?.accountsConnection?.edges
|
||||
);
|
||||
const maxTrade = useMaximumPositionSize({
|
||||
partyId: pubKey || '',
|
||||
|
||||
@@ -81,6 +81,7 @@ const market: MarketDealTicket = {
|
||||
trigger: Types.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||
staticMidPrice: '1606156850',
|
||||
marketTradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
marketState: Schema.MarketState.STATE_ACTIVE,
|
||||
indicativeVolume: '0',
|
||||
indicativePrice: '0',
|
||||
bestStaticBidPrice: '1605489971',
|
||||
@@ -91,7 +92,11 @@ const market: MarketDealTicket = {
|
||||
auctionEnd: null,
|
||||
market: { __typename: 'Market', id: 'market-id' },
|
||||
},
|
||||
marketTimestamps: {},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
close: null,
|
||||
open: null,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultWalletContext = {
|
||||
|
||||
@@ -5,7 +5,7 @@ module.exports = defineConfig({
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-grep/src/plugin')(config);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
|
||||
@@ -14,7 +14,9 @@ let assetData = {
|
||||
|
||||
const assetsInTest = Object.keys(assetData);
|
||||
|
||||
context('Parties page', { tags: '@regression' }, function () {
|
||||
// skipped due to existing issue
|
||||
// https://github.com/vegaprotocol/frontend-monorepo/issues/2243
|
||||
context.skip('Parties page', { tags: '@regression' }, function () {
|
||||
before('send-faucet assets to connected vega wallet', function () {
|
||||
assetsInTest.forEach((asset) => {
|
||||
cy.vega_wallet_receive_fauceted_asset(
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
|
||||
import '@vegaprotocol/cypress';
|
||||
import './common.functions.js';
|
||||
import registerCypressGrep from 'cypress-grep';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
registerCypressGrep();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"sourceMap": false,
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["cypress", "node", "cypress-grep"],
|
||||
"types": ["cypress", "node", "@cypress/grep"],
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
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_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_BLOCK_EXPLORER=
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
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_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://be.stagnet3.vega.xyz/rest
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
@@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
|
||||
NX_TENDERMINT_URL=http://localhost:26607/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
|
||||
+14
-15
@@ -45,21 +45,20 @@ yarn nx run explorer:serve --env={env} # e.g. stagnet3
|
||||
|
||||
There are a few different configuration options offered for this app:
|
||||
|
||||
| **Flag** | **Purpose** |
|
||||
| -------------------------------- | --------------------------------------------------------------- | --- |
|
||||
| `NX_CHAIN_EXPLORER_URL` | The URL of the chain explorer service for decoding transactions |
|
||||
| `NX_TENDERMINT_URL` | The Tendermint REST URL for the Vega consesus engine |
|
||||
| `NX_TENDERMINT_WEBSOCKET_URL` | The Tendermint Websocket URL for the Vega consensus engine |
|
||||
| `NX_VEGA_ENV` | The name of the currently connected vega environment | |
|
||||
| `NX_EXPLORER_ASSETS` | Enable the assets page for the explorer |
|
||||
| `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer |
|
||||
| `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer |
|
||||
| `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer |
|
||||
| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer |
|
||||
| `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer |
|
||||
| `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer |
|
||||
| `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer |
|
||||
| `NX_EXPLORER_VALIDATORS` | Enable the validators page for the explorer |
|
||||
| **Flag** | **Purpose** |
|
||||
| -------------------------------- | ---------------------------------------------------------- | --- |
|
||||
| `NX_TENDERMINT_URL` | The Tendermint REST URL for the Vega consesus engine |
|
||||
| `NX_TENDERMINT_WEBSOCKET_URL` | The Tendermint Websocket URL for the Vega consensus engine |
|
||||
| `NX_VEGA_ENV` | The name of the currently connected vega environment | |
|
||||
| `NX_EXPLORER_ASSETS` | Enable the assets page for the explorer |
|
||||
| `NX_EXPLORER_GENESIS` | Enable the genesis page for the explorer |
|
||||
| `NX_EXPLORER_GOVERNANCE` | Enable the governance page for the explorer |
|
||||
| `NX_EXPLORER_MARKETS` | Enable the markets page for the explorer |
|
||||
| `NX_EXPLORER_ORACLES` | Enable the oracles page for the explorer |
|
||||
| `NX_EXPLORER_TXS_LIST` | Enable the transactions list page for the explorer |
|
||||
| `NX_EXPLORER_NETWORK_PARAMETERS` | Enable the network parameters page for the explorer |
|
||||
| `NX_EXPLORER_PARTIES` | Enable the parties page for the explorer |
|
||||
| `NX_EXPLORER_VALIDATORS` | Enable the validators page for the explorer |
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
fragment ExplorerDeterministicOrderFields on Order {
|
||||
id
|
||||
type
|
||||
reference
|
||||
status
|
||||
version
|
||||
createdAt
|
||||
expiresAt
|
||||
timeInForce
|
||||
price
|
||||
side
|
||||
remaining
|
||||
size
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerDeterministicOrder($orderId: ID!) {
|
||||
orderByID(id: $orderId) {
|
||||
...ExplorerDeterministicOrderFields
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { Schema as Types } from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } };
|
||||
|
||||
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
||||
orderId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } } };
|
||||
|
||||
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
fragment ExplorerDeterministicOrderFields on Order {
|
||||
id
|
||||
type
|
||||
reference
|
||||
status
|
||||
version
|
||||
createdAt
|
||||
expiresAt
|
||||
timeInForce
|
||||
price
|
||||
side
|
||||
remaining
|
||||
size
|
||||
rejectionReason
|
||||
party {
|
||||
id
|
||||
}
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerDeterministicOrderDocument = gql`
|
||||
query ExplorerDeterministicOrder($orderId: ID!) {
|
||||
orderByID(id: $orderId) {
|
||||
...ExplorerDeterministicOrderFields
|
||||
}
|
||||
}
|
||||
${ExplorerDeterministicOrderFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerDeterministicOrderQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerDeterministicOrderQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerDeterministicOrderQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerDeterministicOrderQuery({
|
||||
* variables: {
|
||||
* orderId: // value for 'orderId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerDeterministicOrderQuery(baseOptions: Apollo.QueryHookOptions<ExplorerDeterministicOrderQuery, ExplorerDeterministicOrderQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerDeterministicOrderQuery, ExplorerDeterministicOrderQueryVariables>(ExplorerDeterministicOrderDocument, options);
|
||||
}
|
||||
export function useExplorerDeterministicOrderLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerDeterministicOrderQuery, ExplorerDeterministicOrderQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerDeterministicOrderQuery, ExplorerDeterministicOrderQueryVariables>(ExplorerDeterministicOrderDocument, options);
|
||||
}
|
||||
export type ExplorerDeterministicOrderQueryHookResult = ReturnType<typeof useExplorerDeterministicOrderQuery>;
|
||||
export type ExplorerDeterministicOrderLazyQueryHookResult = ReturnType<typeof useExplorerDeterministicOrderLazyQuery>;
|
||||
export type ExplorerDeterministicOrderQueryResult = Apollo.QueryResult<ExplorerDeterministicOrderQuery, ExplorerDeterministicOrderQueryVariables>;
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerDeterministicOrderQuery } from './__generated__/Order';
|
||||
import type { Schema } from '@vegaprotocol/types';
|
||||
import { MarketLink } from '../links';
|
||||
import PriceInMarket from '../price-in-market/price-in-market';
|
||||
import { Time } from '../time';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
// Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0
|
||||
version?: number;
|
||||
}
|
||||
|
||||
const statusText: Record<Schema.OrderStatus, string> = {
|
||||
STATUS_ACTIVE: t('Active'),
|
||||
STATUS_CANCELLED: t('Cancelled'),
|
||||
STATUS_EXPIRED: t('Expired'),
|
||||
STATUS_FILLED: t('Filled'),
|
||||
STATUS_PARKED: t('Parked'),
|
||||
// Intentionally vague - table shows partial fills
|
||||
STATUS_PARTIALLY_FILLED: t('Active'),
|
||||
STATUS_REJECTED: t('Rejected'),
|
||||
STATUS_STOPPED: t('Stopped'),
|
||||
};
|
||||
|
||||
const sideText: Record<Schema.Side, string> = {
|
||||
SIDE_BUY: t('Buy'),
|
||||
SIDE_SELL: t('Sell'),
|
||||
};
|
||||
|
||||
const tifShort: Record<Schema.OrderTimeInForce, string> = {
|
||||
TIME_IN_FORCE_FOK: t('FOK'),
|
||||
TIME_IN_FORCE_GFA: t('GFA'),
|
||||
TIME_IN_FORCE_GFN: t('GFN'),
|
||||
TIME_IN_FORCE_GTC: t('GTC'),
|
||||
TIME_IN_FORCE_GTT: t('GTT'),
|
||||
TIME_IN_FORCE_IOC: t('IOC'),
|
||||
};
|
||||
|
||||
const tifFull: Record<Schema.OrderTimeInForce, string> = {
|
||||
TIME_IN_FORCE_FOK: t('Fill or Kill'),
|
||||
TIME_IN_FORCE_GFA: t('Good for Auction'),
|
||||
TIME_IN_FORCE_GFN: t('Good for Normal'),
|
||||
TIME_IN_FORCE_GTC: t("Good 'til Cancel"),
|
||||
TIME_IN_FORCE_GTT: t("Good 'til Time"),
|
||||
TIME_IN_FORCE_IOC: t('Immediate or Cancel'),
|
||||
};
|
||||
const wrapperClasses =
|
||||
'grid lg:grid-cols-1 flex items-center max-w-xl border border-zinc-200 dark:border-zinc-800 rounded-md pv-2 ph-5 mb-5';
|
||||
|
||||
/**
|
||||
* This component renders the *current* details for an order
|
||||
*
|
||||
* An important part of this component is that unlike most of the rest of the Explorer,
|
||||
* it is displaying 'live' data. With the current APIs it's impossible to get the state
|
||||
* of the order at a specific point in time. While one day that might be possible, this
|
||||
* order component is not built with that in mind.
|
||||
*
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
const DeterministicOrderDetails = ({
|
||||
id,
|
||||
version = 0,
|
||||
}: DeterministicOrderDetailsProps) => {
|
||||
const { data, error } = useExplorerDeterministicOrderQuery({
|
||||
variables: { orderId: id },
|
||||
});
|
||||
|
||||
if (error || (data && !data.orderByID)) {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="relative block rounded-lg px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
{t('Order not found')}
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-12">
|
||||
{t('No order created from this transaction')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || !data.orderByID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const o = data.orderByID;
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
<abbr title={tifFull[o.timeInForce]} className="bb-dotted mr-2">
|
||||
{tifShort[o.timeInForce]}
|
||||
</abbr>
|
||||
{sideText[o.side]}
|
||||
<span className="mx-5 text-base">@</span>
|
||||
<PriceInMarket price={o.price} marketId={o.market.id} />
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-4">
|
||||
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
|
||||
</p>
|
||||
{o.reference ? (
|
||||
<p className="text-gray-500 mb-4">
|
||||
<span>{t('Reference')}</span>: {o.reference}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid md:grid-cols-4 gap-x-6">
|
||||
{version !== 0 ? null : (
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Status')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
|
||||
{statusText[o.status]}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{o.size}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{version !== 0 ? null : (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Remaining')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{o.remaining}
|
||||
</h5>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
{t('Version')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
{o.version}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeterministicOrderDetails;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Panel } from '../panel';
|
||||
import {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
query ExplorerMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state
|
||||
|
||||
+7
-1
@@ -8,16 +8,22 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null };
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
|
||||
|
||||
|
||||
export const ExplorerMarketDocument = gql`
|
||||
query ExplorerMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
product {
|
||||
... on Future {
|
||||
quoteName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state
|
||||
|
||||
@@ -4,10 +4,11 @@ import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import MarketLink from './market-link';
|
||||
import { ExplorerMarketDocument } from './__generated__/Market';
|
||||
import { GraphQLError } from 'graphql';
|
||||
|
||||
function renderComponent(id: string, mock: MockedResponse[]) {
|
||||
function renderComponent(id: string, mocks: MockedResponse[]) {
|
||||
return (
|
||||
<MockedProvider mocks={mock}>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<MarketLink id={id} />
|
||||
</MemoryRouter>
|
||||
@@ -21,6 +22,26 @@ describe('Market link component', () => {
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the ID with an emoji on error', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '456',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
errors: [new GraphQLError('No such market')],
|
||||
},
|
||||
};
|
||||
const res = render(renderComponent('456', [mock]));
|
||||
// The ID
|
||||
expect(res.getByText('456')).toBeInTheDocument();
|
||||
|
||||
// The emoji
|
||||
expect(await res.findByRole('img')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the market name when the query returns a result', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
@@ -33,10 +54,14 @@ describe('Market link component', () => {
|
||||
data: {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 5,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test-label',
|
||||
product: {
|
||||
quoteName: 'dai',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useExplorerMarketQuery } from './__generated__/Market';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
id: string;
|
||||
@@ -15,14 +16,25 @@ export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
* it will use the ID instead
|
||||
*/
|
||||
const MarketLink = ({ id, ...props }: MarketLinkProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
const { data, error, loading } = useExplorerMarketQuery({
|
||||
variables: { id },
|
||||
});
|
||||
|
||||
let label = id;
|
||||
let label = <span>{id}</span>;
|
||||
|
||||
if (data?.market?.tradableInstrument.instrument.name) {
|
||||
label = data.market.tradableInstrument.instrument.name;
|
||||
if (!loading) {
|
||||
if (data?.market?.tradableInstrument.instrument.name) {
|
||||
label = <span>{data.market.tradableInstrument.instrument.name}</span>;
|
||||
} else if (error) {
|
||||
label = (
|
||||
<div title={t('Unknown market')}>
|
||||
<span role="img" aria-label="Unknown market" className="img">
|
||||
⚠️
|
||||
</span>
|
||||
{id}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,7 +22,7 @@ const NodeLink = ({ id, ...props }: NodeLinkProps) => {
|
||||
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.VALIDATORS}#${id}`}>
|
||||
{label}
|
||||
<code>{label}</code>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,11 @@ export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||
|
||||
const PartyLink = ({ id, ...props }: PartyLinkProps) => {
|
||||
return (
|
||||
<Link className="underline" {...props} to={`/${Routes.PARTIES}/${id}`}>
|
||||
<Link
|
||||
className="underline font-mono"
|
||||
{...props}
|
||||
to={`/${Routes.PARTIES}/${id}`}
|
||||
>
|
||||
{id}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import PriceInMarket from './price-in-market';
|
||||
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
|
||||
|
||||
function renderComponent(
|
||||
price: string,
|
||||
marketId: string,
|
||||
mocks: MockedResponse[]
|
||||
) {
|
||||
return (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<PriceInMarket marketId={marketId} price={price} />
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Price in Market component', () => {
|
||||
it('Renders the raw price when there is no market data', () => {
|
||||
const res = render(renderComponent('100', '123', []));
|
||||
expect(res.getByText('100')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the formatted price when market data is fetched', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test dai',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const res = render(renderComponent('100', '123', [mock]));
|
||||
expect(await res.findByText('1.00')).toBeInTheDocument();
|
||||
expect(await res.findByText('dai')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Leaves the market id when the market is not found', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
error: new Error('No such market'),
|
||||
};
|
||||
|
||||
const res = render(renderComponent('100', '123', [mock]));
|
||||
expect(await res.findByText('100')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders `Market` instead of a price for market orders: 0 price', () => {
|
||||
const res = render(renderComponent('0', '123', []));
|
||||
expect(res.getByText('Market')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders `Market` instead of a price for market orders: undefined price', () => {
|
||||
const res = render(
|
||||
renderComponent(undefined as unknown as string, '123', [])
|
||||
);
|
||||
expect(res.getByText('Market')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders `Market` instead of a price for market orders: empty price', () => {
|
||||
const res = render(renderComponent('', '123', []));
|
||||
expect(res.getByText('Market')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import isUndefined from 'lodash/isUndefined';
|
||||
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
|
||||
import get from 'lodash/get';
|
||||
|
||||
export type PriceInMarketProps = {
|
||||
marketId: string;
|
||||
price: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a market ID and a price it will fetch the market
|
||||
* and format the price in that market's decimal places.
|
||||
*/
|
||||
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
});
|
||||
|
||||
let label = price;
|
||||
|
||||
if (data && data.market?.decimalPlaces) {
|
||||
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
|
||||
}
|
||||
|
||||
const suffix = get(
|
||||
data,
|
||||
'market.tradableInstrument.instrument.product.quoteName',
|
||||
''
|
||||
);
|
||||
|
||||
if (isUndefined(price) || price === '' || price === '0') {
|
||||
return (
|
||||
<span>
|
||||
<abbr title={'Best available price'}>{t('Market')}</abbr> {suffix}
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="inline-block">
|
||||
<span>{label}</span> <span>{suffix}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default PriceInMarket;
|
||||
@@ -1,37 +0,0 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import type { TendermintBlockchainResponse } from '../../routes/blocks/tendermint-blockchain-response';
|
||||
import { BlockData } from '../blocks';
|
||||
import { TxsPerBlock } from './txs-per-block';
|
||||
|
||||
interface TxsProps {
|
||||
data: TendermintBlockchainResponse | undefined;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const BlockTxsData = ({ data, className }: TxsProps) => {
|
||||
if (!data?.result) {
|
||||
// Data for the block has already been fetched at this point, so no errors
|
||||
// or loading to deal with. This is specifically the case
|
||||
// where the data object is not undefined, but lacks a result.
|
||||
return <div className={className}>{t('No data')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul
|
||||
aria-label={t(
|
||||
`Showing ${data.result?.block_metas.length} most recently loaded blocks and transactions`
|
||||
)}
|
||||
className={className}
|
||||
>
|
||||
{data.result?.block_metas?.map((block, index) => {
|
||||
return (
|
||||
<li key={index} data-testid="transactions-list">
|
||||
<BlockData block={block} className="mb-12" />
|
||||
<TxsPerBlock blockHeight={block.header.height} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export type ExplorerNodeVoteQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerNodeVoteQuery = { __typename?: 'Query', withdrawal?: { __typename?: 'Withdrawal', id: string, status: Types.WithdrawalStatus, createdTimestamp: string, withdrawnTimestamp?: string | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null, deposit?: { __typename?: 'Deposit', id: string, status: Types.DepositStatus, createdTimestamp: string, creditedTimestamp?: string | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null };
|
||||
export type ExplorerNodeVoteQuery = { __typename?: 'Query', withdrawal?: { __typename?: 'Withdrawal', id: string, status: Types.WithdrawalStatus, createdTimestamp: any, withdrawnTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null, deposit?: { __typename?: 'Deposit', id: string, status: Types.DepositStatus, createdTimestamp: any, creditedTimestamp?: any | null, txHash?: string | null, asset: { __typename?: 'Asset', id: string, name: string, decimals: number }, party: { __typename?: 'Party', id: string } } | null };
|
||||
|
||||
|
||||
export const ExplorerNodeVoteDocument = gql`
|
||||
|
||||
@@ -0,0 +1,453 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
|
||||
|
||||
import { ChainEvent } from '.';
|
||||
|
||||
const baseMock: Partial<BlockExplorerTransactionResult> = {
|
||||
block: '1',
|
||||
index: 1,
|
||||
hash: '123',
|
||||
submitter: '123',
|
||||
type: '1',
|
||||
code: 1,
|
||||
cursor: '1',
|
||||
};
|
||||
|
||||
jest.mock('./tx-builtin-deposit', () => ({
|
||||
TxDetailsChainEventBuiltinDeposit: () => (
|
||||
<span>TxDetailsChainEventBuiltinDeposit</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-builtin-withdrawal', () => ({
|
||||
TxDetailsChainEventBuiltinWithdrawal: () => (
|
||||
<span>TxDetailsChainEventBuiltinWithdrawal</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-erc20-deposit', () => ({
|
||||
TxDetailsChainEventDeposit: () => <span>TxDetailsChainEventDeposit</span>,
|
||||
}));
|
||||
jest.mock('./tx-erc20-withdrawal', () => ({
|
||||
TxDetailsChainEventWithdrawal: () => (
|
||||
<span>TxDetailsChainEventWithdrawal</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-erc20-asset-list', () => ({
|
||||
TxDetailsChainEventErc20AssetList: () => (
|
||||
<span>TxDetailsChainEventErc20AssetList</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-erc20-asset-delist', () => ({
|
||||
TxDetailsChainEventErc20AssetDelist: () => (
|
||||
<span>TxDetailsChainEventErc20AssetDelist</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-erc20-asset-limits-updated', () => ({
|
||||
TxDetailsChainEventErc20AssetLimitsUpdated: () => (
|
||||
<span>TxDetailsChainEventErc20LimitsUpdated</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-erc20-bridge-pause', () => ({
|
||||
TxDetailsChainEventErc20BridgePause: () => (
|
||||
<span>TxDetailsChainEventErc20BridgeEvent</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-multisig-signer', () => ({
|
||||
TxDetailsChainMultisigSigner: () => <span>TxDetailsChainMultisigSigner</span>,
|
||||
}));
|
||||
jest.mock('./tx-multisig-threshold', () => ({
|
||||
TxDetailsChainMultisigThreshold: () => (
|
||||
<span>TxDetailsChainMultisigThreshold</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-stake-deposit', () => ({
|
||||
TxDetailsChainEventStakeDeposit: () => (
|
||||
<span>TxDetailsChainStakeDeposit</span>
|
||||
),
|
||||
}));
|
||||
jest.mock('./tx-stake-remove', () => ({
|
||||
TxDetailsChainEventStakeRemove: () => <span>TxDetailsChainStakeRemove</span>,
|
||||
}));
|
||||
jest.mock('./tx-stake-totalsupply', () => ({
|
||||
TxDetailsChainEventStakeTotalSupply: () => (
|
||||
<span>TxDetailsChainStakeTotalSupply</span>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('Chain Event: Chain Event selects the right component for the event', () => {
|
||||
it('Returns a Built In Deposit event for a built in deposit', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
builtin: {
|
||||
deposit: {
|
||||
amount: '',
|
||||
partyId: '',
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainEventBuiltinDeposit')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Built In Deposit event for a built in withdrawal', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
builtin: {
|
||||
withdrawal: {
|
||||
amount: '',
|
||||
partyId: '',
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(
|
||||
screen.getByText('TxDetailsChainEventBuiltinWithdrawal')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Built In Deposit event for an ERC20 deposit', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
deposit: {
|
||||
amount: '',
|
||||
targetPartyId: '',
|
||||
sourceEthereumAddress: '',
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainEventDeposit')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Built In Deposit event for an ERC20 withdrawal', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
withdrawal: {
|
||||
referenceNonce: '',
|
||||
targetEthereumAddress: '',
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainEventWithdrawal')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Asset List event view for a list', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
assetList: {
|
||||
assetSource: '',
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainEventErc20AssetList')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Asset Delist event view for a delist', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
assetDelist: {
|
||||
vegaAssetId: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(
|
||||
screen.getByText('TxDetailsChainEventErc20AssetDelist')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Asset Limits Update event view for a update', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
assetLimitsUpdated: {
|
||||
lifetimeLimits: '100',
|
||||
withdrawThreshold: '100',
|
||||
vegaAssetId: '',
|
||||
sourceEthereumAddress: '0x000',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(
|
||||
screen.getByText('TxDetailsChainEventErc20LimitsUpdated')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Bridge Pause event view when a pause event happens', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
bridgeStopped: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(
|
||||
screen.getByText('TxDetailsChainEventErc20BridgeEvent')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a Bridge Pause event view when a resume event happens', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20: {
|
||||
bridgeResumed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(
|
||||
screen.getByText('TxDetailsChainEventErc20BridgeEvent')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a multsig signer view when a multisig signer is added', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20Multisig: {
|
||||
signerAdded: {
|
||||
blockTime: '100',
|
||||
newSigner: '0x000',
|
||||
nonce: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainMultisigSigner')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a multsig signer view when a multisig signer is removed', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20Multisig: {
|
||||
signerRemoved: {
|
||||
blockTime: '100',
|
||||
oldSigner: '0x000',
|
||||
nonce: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainMultisigSigner')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a multsig signer view when a multisig threshold change event occurs', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
erc20Multisig: {
|
||||
thresholdSet: {
|
||||
blockTime: '100',
|
||||
newThreshold: 100,
|
||||
nonce: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainMultisigThreshold')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a stake deposit view when a stake arrives', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
stakingEvent: {
|
||||
stakeDeposited: {
|
||||
blockTime: '100',
|
||||
amount: '100',
|
||||
vegaPublicKey: '12345',
|
||||
ethereumAddress: '0x123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainStakeDeposit')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a stake removed view when a stake goes', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
stakingEvent: {
|
||||
stakeRemoved: {
|
||||
blockTime: '100',
|
||||
amount: '100',
|
||||
vegaPublicKey: '12345',
|
||||
ethereumAddress: '0x123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainStakeRemove')).toBeVisible();
|
||||
});
|
||||
|
||||
it('Returns a stake total supply view when the supply of the staking asset changes', () => {
|
||||
const commandMock: Partial<BlockExplorerTransactionResult> = {
|
||||
command: {
|
||||
chainEvent: {
|
||||
stakingEvent: {
|
||||
totalSupply: {
|
||||
totalSupply: '12345',
|
||||
tokenAddress: '0x123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = Object.assign(
|
||||
{},
|
||||
baseMock,
|
||||
commandMock
|
||||
) as BlockExplorerTransactionResult;
|
||||
|
||||
const screen = render(<ChainEvent txData={mock} />);
|
||||
expect(screen.getByText('TxDetailsChainStakeTotalSupply')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
|
||||
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
|
||||
import { TxDetailsChainEventBuiltinDeposit } from './tx-builtin-deposit';
|
||||
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
|
||||
import { TxDetailsChainEventStakeDeposit } from './tx-stake-deposit';
|
||||
import { TxDetailsChainEventStakeRemove } from './tx-stake-remove';
|
||||
import { TxDetailsChainEventStakeTotalSupply } from './tx-stake-totalsupply';
|
||||
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
|
||||
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
|
||||
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
|
||||
import { TxDetailsChainEventErc20AssetList } from './tx-erc20-asset-list';
|
||||
import { TxDetailsChainEventErc20AssetLimitsUpdated } from './tx-erc20-asset-limits-updated';
|
||||
import { TxDetailsChainEventErc20BridgePause } from './tx-erc20-bridge-pause';
|
||||
@@ -13,6 +13,8 @@ import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
|
||||
import isUndefined from 'lodash/isUndefined';
|
||||
|
||||
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
|
||||
import { TxDetailsChainEventWithdrawal } from './tx-erc20-withdrawal';
|
||||
import { TxDetailsChainEventErc20AssetDelist } from './tx-erc20-asset-delist';
|
||||
|
||||
interface ChainEventProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -32,54 +34,58 @@ interface ChainEventProps {
|
||||
* @returns React.JSXElement
|
||||
*/
|
||||
export const ChainEvent = ({ txData }: ChainEventProps) => {
|
||||
const e = txData?.command.chainEvent;
|
||||
if (!e) {
|
||||
if (!txData?.command.chainEvent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { builtin, erc20, erc20Multisig, stakingEvent } =
|
||||
txData.command.chainEvent;
|
||||
|
||||
// Builtin Asset events
|
||||
if (e.builtin) {
|
||||
if (e.builtin.deposit) {
|
||||
return <TxDetailsChainEventBuiltinDeposit deposit={e.builtin.deposit} />;
|
||||
if (builtin) {
|
||||
if (builtin.deposit) {
|
||||
return <TxDetailsChainEventBuiltinDeposit deposit={builtin.deposit} />;
|
||||
}
|
||||
|
||||
if (e.builtin?.withdrawal) {
|
||||
if (builtin?.withdrawal) {
|
||||
return (
|
||||
<TxDetailsChainEventBuiltinWithdrawal
|
||||
withdrawal={e.builtin?.withdrawal}
|
||||
withdrawal={builtin?.withdrawal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ERC20 asset events
|
||||
if (e.erc20) {
|
||||
if (e.erc20.deposit) {
|
||||
return <TxDetailsChainEventDeposit deposit={e.erc20.deposit} />;
|
||||
if (erc20) {
|
||||
if (erc20.deposit) {
|
||||
return <TxDetailsChainEventDeposit deposit={erc20.deposit} />;
|
||||
}
|
||||
|
||||
if (e.erc20.withdrawal) {
|
||||
if (erc20.withdrawal) {
|
||||
return <TxDetailsChainEventWithdrawal withdrawal={erc20.withdrawal} />;
|
||||
}
|
||||
|
||||
if (erc20.assetList) {
|
||||
return <TxDetailsChainEventErc20AssetList assetList={erc20.assetList} />;
|
||||
}
|
||||
|
||||
if (erc20.assetDelist) {
|
||||
return (
|
||||
<TxDetailsChainEventBuiltinWithdrawal withdrawal={e.erc20.withdrawal} />
|
||||
<TxDetailsChainEventErc20AssetDelist assetDelist={erc20.assetDelist} />
|
||||
);
|
||||
}
|
||||
|
||||
if (e.erc20.assetList) {
|
||||
return (
|
||||
<TxDetailsChainEventErc20AssetList assetList={e.erc20.assetList} />
|
||||
);
|
||||
}
|
||||
|
||||
if (e.erc20.assetLimitsUpdated) {
|
||||
if (erc20.assetLimitsUpdated) {
|
||||
return (
|
||||
<TxDetailsChainEventErc20AssetLimitsUpdated
|
||||
assetLimitsUpdated={e.erc20.assetLimitsUpdated}
|
||||
assetLimitsUpdated={erc20.assetLimitsUpdated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const bridgeStopped = e.erc20.bridgeStopped;
|
||||
const bridgeResumed = e.erc20.bridgeResumed;
|
||||
const bridgeStopped = erc20.bridgeStopped;
|
||||
const bridgeResumed = erc20.bridgeResumed;
|
||||
if (!isUndefined(bridgeStopped) || !isUndefined(bridgeResumed)) {
|
||||
const isPaused = bridgeStopped === false || bridgeResumed === true;
|
||||
return <TxDetailsChainEventErc20BridgePause isPaused={isPaused} />;
|
||||
@@ -87,48 +93,48 @@ export const ChainEvent = ({ txData }: ChainEventProps) => {
|
||||
}
|
||||
|
||||
// ERC20 multisig events
|
||||
if (e.erc20Multisig) {
|
||||
if (e.erc20Multisig.thresholdSet) {
|
||||
if (erc20Multisig) {
|
||||
if (erc20Multisig.thresholdSet) {
|
||||
return (
|
||||
<TxDetailsChainMultisigThreshold
|
||||
thresholdSet={e.erc20Multisig.thresholdSet}
|
||||
thresholdSet={erc20Multisig.thresholdSet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (e.erc20Multisig.signerAdded) {
|
||||
if (erc20Multisig.signerAdded) {
|
||||
return (
|
||||
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerAdded} />
|
||||
<TxDetailsChainMultisigSigner signer={erc20Multisig.signerAdded} />
|
||||
);
|
||||
}
|
||||
|
||||
if (e.erc20Multisig.signerRemoved) {
|
||||
if (erc20Multisig.signerRemoved) {
|
||||
return (
|
||||
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerRemoved} />
|
||||
<TxDetailsChainMultisigSigner signer={erc20Multisig.signerRemoved} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Staking events
|
||||
if (e.stakingEvent) {
|
||||
if (e.stakingEvent.stakeDeposited) {
|
||||
if (stakingEvent) {
|
||||
if (stakingEvent.stakeDeposited) {
|
||||
return (
|
||||
<TxDetailsChainEventStakeDeposit
|
||||
deposit={e.stakingEvent.stakeDeposited}
|
||||
deposit={stakingEvent.stakeDeposited}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (e.stakingEvent.stakeRemoved) {
|
||||
if (stakingEvent.stakeRemoved) {
|
||||
return (
|
||||
<TxDetailsChainEventStakeRemove remove={e.stakingEvent.stakeRemoved} />
|
||||
<TxDetailsChainEventStakeRemove remove={stakingEvent.stakeRemoved} />
|
||||
);
|
||||
}
|
||||
|
||||
if (e.stakingEvent.totalSupply) {
|
||||
if (stakingEvent.totalSupply) {
|
||||
return (
|
||||
<TxDetailsChainEventStakeTotalSupply
|
||||
update={e.stakingEvent.totalSupply}
|
||||
update={stakingEvent.totalSupply}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getBlockTime } from './get-block-time';
|
||||
|
||||
describe('Lib: getBlockTime', () => {
|
||||
it('- gets returned if nothing is provided', () => {
|
||||
const res = getBlockTime();
|
||||
expect(res).toEqual('-');
|
||||
});
|
||||
|
||||
it('Returns a known date string', () => {
|
||||
const mockBlockTime = '1669223762';
|
||||
const usRes = getBlockTime(mockBlockTime, 'en-US');
|
||||
expect(usRes).toEqual('11/23/2022, 5:16:02 PM');
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,22 @@
|
||||
* @param date String or null date
|
||||
* @returns String date in locale time
|
||||
*/
|
||||
export function getBlockTime(date?: string) {
|
||||
if (!date) {
|
||||
export function getBlockTime(date?: string, locale?: Intl.LocalesArgument) {
|
||||
try {
|
||||
if (!date) {
|
||||
throw new Error('No date provided');
|
||||
}
|
||||
|
||||
const timeInSeconds = parseInt(date, 10);
|
||||
|
||||
if (isNaN(timeInSeconds)) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
|
||||
const timeInMs = timeInSeconds * 1000;
|
||||
|
||||
return new Date(timeInMs).toLocaleString(locale);
|
||||
} catch (e) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const timeInSeconds = parseInt(date, 10);
|
||||
const timeInMs = timeInSeconds * 1000;
|
||||
|
||||
return new Date(timeInMs).toLocaleString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { TxDetailsChainEventBuiltinDeposit } from './tx-builtin-deposit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
type Deposit = components['schemas']['vegaBuiltinAssetDeposit'];
|
||||
|
||||
const fullMock: Deposit = {
|
||||
partyId: 'party123',
|
||||
vegaAssetId: 'asset123',
|
||||
amount: 'amount123',
|
||||
};
|
||||
|
||||
describe('Chain Event: Builtin asset deposit', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Deposit;
|
||||
const screen = render(<TxDetailsChainEventBuiltinDeposit deposit={mock} />);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Deposit = {};
|
||||
|
||||
const screen = render(<TxDetailsChainEventBuiltinDeposit deposit={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventBuiltinDeposit deposit={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventBuiltinDeposit deposit={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Built-in asset deposit'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.partyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.partyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -15,26 +15,26 @@ interface TxDetailsChainEventBuiltinDepositProps {
|
||||
export const TxDetailsChainEventBuiltinDeposit = ({
|
||||
deposit,
|
||||
}: TxDetailsChainEventBuiltinDepositProps) => {
|
||||
if (!deposit) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!deposit || !deposit.partyId || !deposit.vegaAssetId || !deposit.amount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('Built-in asset deposit')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={deposit.partyId || ''} />
|
||||
<PartyLink id={deposit.partyId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Asset')}</TableCell>
|
||||
<TableCell>
|
||||
<AssetLink id={deposit.vegaAssetId || ''} /> ({t('built in asset')})
|
||||
<AssetLink id={deposit.vegaAssetId} /> ({t('built in asset')})
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
type Withdrawal = components['schemas']['vegaBuiltinAssetWithdrawal'];
|
||||
|
||||
const fullMock: Withdrawal = {
|
||||
partyId: 'party123',
|
||||
vegaAssetId: 'asset123',
|
||||
amount: 'amount123',
|
||||
};
|
||||
|
||||
describe('Chain Event: Builtin asset withdrawal', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Withdrawal;
|
||||
const screen = render(
|
||||
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Withdrawal = {};
|
||||
|
||||
const screen = render(
|
||||
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventBuiltinWithdrawal withdrawal={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('Built-in asset withdrawal'))
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.partyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.partyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -15,14 +15,19 @@ interface TxDetailsChainEventBuiltinDepositProps {
|
||||
export const TxDetailsChainEventBuiltinWithdrawal = ({
|
||||
withdrawal,
|
||||
}: TxDetailsChainEventBuiltinDepositProps) => {
|
||||
if (!withdrawal) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!withdrawal ||
|
||||
!withdrawal.partyId ||
|
||||
!withdrawal.vegaAssetId ||
|
||||
!withdrawal.amount
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('Built-in asset withdrawal')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventErc20AssetDelist } from './tx-erc20-asset-delist';
|
||||
|
||||
type Delist = components['schemas']['vegaERC20AssetDelist'];
|
||||
|
||||
const fullMock: Delist = {
|
||||
vegaAssetId: 'asset123',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 Asset Delist', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Delist;
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Delist = {};
|
||||
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventErc20AssetDelist assetDelist={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 asset removed'))).toBeInTheDocument();
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -16,8 +16,8 @@ interface TxDetailsChainEventErc20AssetDelistProps {
|
||||
export const TxDetailsChainEventErc20AssetDelist = ({
|
||||
assetDelist,
|
||||
}: TxDetailsChainEventErc20AssetDelistProps) => {
|
||||
if (!assetDelist) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!assetDelist || !assetDelist.vegaAssetId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventErc20AssetLimitsUpdated } from './tx-erc20-asset-limits-updated';
|
||||
|
||||
type AssetLimitsUpdated = components['schemas']['vegaERC20AssetLimitsUpdated'];
|
||||
|
||||
const fullMock: AssetLimitsUpdated = {
|
||||
sourceEthereumAddress: 'eth123',
|
||||
vegaAssetId: 'asset123',
|
||||
lifetimeLimits: '100',
|
||||
withdrawThreshold: '60',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 Asset limits updated', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as AssetLimitsUpdated;
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: AssetLimitsUpdated = {};
|
||||
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventErc20AssetLimitsUpdated
|
||||
assetLimitsUpdated={fullMock}
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('ERC20 asset limits update'))
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Total lifetime limit'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.lifetimeLimits}`)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('Asset withdrawal threshold'))
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(`${fullMock.withdrawThreshold}`)
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.sourceEthereumAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
+19
-17
@@ -23,33 +23,35 @@ interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
|
||||
export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
|
||||
assetLimitsUpdated,
|
||||
}: TxDetailsChainEventErc20AssetLimitsUpdatedProps) => {
|
||||
if (!assetLimitsUpdated) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!assetLimitsUpdated ||
|
||||
!assetLimitsUpdated.sourceEthereumAddress ||
|
||||
!assetLimitsUpdated.vegaAssetId ||
|
||||
!assetLimitsUpdated.lifetimeLimits ||
|
||||
!assetLimitsUpdated.withdrawThreshold
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('ERC20 asset limits updated')}</TableCell>
|
||||
<TableCell>{t('ERC20 asset limits update')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('ERC20 asset')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={assetLimitsUpdated.sourceEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
{assetLimitsUpdated.sourceEthereumAddress ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('ERC20 asset')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={assetLimitsUpdated.sourceEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Vega asset')}</TableCell>
|
||||
<TableCell>
|
||||
<AssetLink id={assetLimitsUpdated.vegaAssetId || ''} />
|
||||
<AssetLink id={assetLimitsUpdated.vegaAssetId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventErc20AssetList } from './tx-erc20-asset-list';
|
||||
|
||||
type List = components['schemas']['vegaERC20AssetList'];
|
||||
|
||||
const fullMock: List = {
|
||||
vegaAssetId: 'asset123',
|
||||
assetSource: 'eth123',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 Asset List', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as List;
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetList assetList={mock} />
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: List = {};
|
||||
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetList assetList={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventErc20AssetList assetList={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventErc20AssetList assetList={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 asset added'))).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.assetSource}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.assetSource}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -19,8 +19,8 @@ interface TxDetailsChainEventErc20AssetListProps {
|
||||
export const TxDetailsChainEventErc20AssetList = ({
|
||||
assetList,
|
||||
}: TxDetailsChainEventErc20AssetListProps) => {
|
||||
if (!assetList) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!assetList || !assetList.assetSource || !assetList.vegaAssetId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -29,21 +29,19 @@ export const TxDetailsChainEventErc20AssetList = ({
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('ERC20 asset added')}</TableCell>
|
||||
</TableRow>
|
||||
{assetList.assetSource ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={assetList.assetSource}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={assetList.assetSource}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Added Vega asset')}</TableCell>
|
||||
<TableCell>
|
||||
<AssetLink id={assetList.vegaAssetId || ''} />
|
||||
<AssetLink id={assetList.vegaAssetId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TxDetailsChainEventErc20BridgePause } from './tx-erc20-bridge-pause';
|
||||
|
||||
describe('Chain Event: ERC20 bridge pause', () => {
|
||||
it('Renders pause if paused', () => {
|
||||
const screen = render(
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventErc20BridgePause isPaused={true} />
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 bridge pause'))).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders unpause if resumed', () => {
|
||||
const screen = render(
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventErc20BridgePause isPaused={false} />
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 bridge unpause'))).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TableRow, TableCell } from '../../../table';
|
||||
|
||||
interface TxDetailsChainEventErc20BridgePauseProps {
|
||||
export interface TxDetailsChainEventErc20BridgePauseProps {
|
||||
isPaused: boolean;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ interface TxDetailsChainEventErc20BridgePauseProps {
|
||||
export const TxDetailsChainEventErc20BridgePause = ({
|
||||
isPaused,
|
||||
}: TxDetailsChainEventErc20BridgePauseProps) => {
|
||||
const event = isPaused ? 'pause' : 'unpaused';
|
||||
const event = isPaused ? 'pause' : 'unpause';
|
||||
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
|
||||
|
||||
type Deposit = components['schemas']['vegaERC20Deposit'];
|
||||
|
||||
const fullMock: Deposit = {
|
||||
vegaAssetId: 'asset123',
|
||||
amount: 'amount123',
|
||||
sourceEthereumAddress: 'eth123',
|
||||
targetPartyId: 'vega123',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 asset deposit', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Deposit;
|
||||
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Deposit = {};
|
||||
|
||||
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventDeposit deposit={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 deposit'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.targetPartyId}`
|
||||
);
|
||||
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.sourceEthereumAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,14 @@ interface TxDetailsChainEventProps {
|
||||
export const TxDetailsChainEventDeposit = ({
|
||||
deposit,
|
||||
}: TxDetailsChainEventProps) => {
|
||||
if (!deposit) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!deposit ||
|
||||
!deposit.sourceEthereumAddress ||
|
||||
!deposit.targetPartyId ||
|
||||
!deposit.vegaAssetId ||
|
||||
!deposit.amount
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -27,27 +33,25 @@ export const TxDetailsChainEventDeposit = ({
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('ERC20 deposit')}</TableCell>
|
||||
</TableRow>
|
||||
{deposit.sourceEthereumAddress ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={deposit.sourceEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={deposit.sourceEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={deposit.targetPartyId || ''} />
|
||||
<PartyLink id={deposit.targetPartyId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Asset')}</TableCell>
|
||||
<TableCell>
|
||||
<AssetLink id={deposit.vegaAssetId || ''} />
|
||||
<AssetLink id={deposit.vegaAssetId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventWithdrawal } from './tx-erc20-withdrawal';
|
||||
type Withdrawal = components['schemas']['vegaERC20Withdrawal'];
|
||||
|
||||
const fullMock: Partial<Withdrawal> = {
|
||||
vegaAssetId: 'asset123',
|
||||
targetEthereumAddress: 'eth123',
|
||||
};
|
||||
|
||||
describe('Chain Event: ERC20 asset deposit', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Withdrawal;
|
||||
const screen = render(<TxDetailsChainEventWithdrawal withdrawal={mock} />);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Withdrawal = {};
|
||||
|
||||
const screen = render(<TxDetailsChainEventWithdrawal withdrawal={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventWithdrawal withdrawal={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventWithdrawal withdrawal={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('ERC20 withdrawal'))).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
|
||||
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
|
||||
expect(assetLink).toBeInTheDocument();
|
||||
expect(assetLink.tagName).toEqual('A');
|
||||
expect(assetLink.getAttribute('href')).toEqual(
|
||||
`/assets#${fullMock.vegaAssetId}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.targetEthereumAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,12 @@ interface TxDetailsChainEventWithdrawalProps {
|
||||
export const TxDetailsChainEventWithdrawal = ({
|
||||
withdrawal,
|
||||
}: TxDetailsChainEventWithdrawalProps) => {
|
||||
if (!withdrawal) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!withdrawal ||
|
||||
!withdrawal.targetEthereumAddress ||
|
||||
!withdrawal.vegaAssetId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -28,22 +32,20 @@ export const TxDetailsChainEventWithdrawal = ({
|
||||
<TableCell>{t('ERC20 withdrawal')}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
{withdrawal.targetEthereumAddress ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={withdrawal.targetEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={withdrawal.targetEthereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Asset')}</TableCell>
|
||||
<TableCell>
|
||||
<AssetLink id={withdrawal.vegaAssetId || ''} />
|
||||
<AssetLink id={withdrawal.vegaAssetId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
|
||||
import { getBlockTime } from './lib/get-block-time';
|
||||
|
||||
type Added = components['schemas']['vegaERC20SignerAdded'];
|
||||
type Removed = components['schemas']['vegaERC20SignerRemoved'];
|
||||
|
||||
const mockBlockTime = '1669631323';
|
||||
|
||||
describe('Chain Event: multisig signer change', () => {
|
||||
it('Copes with a poorly formatted time prop', () => {
|
||||
const addedMock: Added = {
|
||||
newSigner: 'eth123',
|
||||
nonce: 'nonce123',
|
||||
blockTime: 'you shall not parse',
|
||||
};
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainMultisigSigner signer={addedMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders addedSigner correctly', () => {
|
||||
const addedMock: Added = {
|
||||
newSigner: 'eth123',
|
||||
nonce: 'nonce123',
|
||||
blockTime: mockBlockTime,
|
||||
};
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainMultisigSigner signer={addedMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('Add ERC20 bridge multisig signer'))
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Add signer'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${addedMock.newSigner}`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const removedMock: Removed = {
|
||||
oldSigner: 'eth123',
|
||||
nonce: 'nonce123',
|
||||
blockTime: mockBlockTime,
|
||||
};
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainMultisigSigner signer={removedMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('Remove ERC20 bridge multisig signer'))
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Remove signer'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${removedMock.oldSigner}`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -15,8 +15,8 @@ interface TxDetailsChainMultisigSignerProps {
|
||||
export const TxDetailsChainMultisigSigner = ({
|
||||
signer,
|
||||
}: TxDetailsChainMultisigSignerProps) => {
|
||||
if (!signer) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!signer || !signer.blockTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blockTime = getBlockTime(signer.blockTime);
|
||||
@@ -30,10 +30,12 @@ export const TxDetailsChainMultisigSigner = ({
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
{'newSigner' in signer
|
||||
? t('Add ERC20 bridge multisig signer')
|
||||
: t('Remove ERC20 bridge multsig signer')}
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>
|
||||
{'newSigner' in signer
|
||||
? t('Add ERC20 bridge multisig signer')
|
||||
: t('Remove ERC20 bridge multisig signer')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
|
||||
import omit from 'lodash/omit';
|
||||
import { getBlockTime } from './lib/get-block-time';
|
||||
|
||||
type Threshold =
|
||||
components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
|
||||
|
||||
const mockBlockTime = '1669631323';
|
||||
// Note: nonce is missing from this partial because the component does not render
|
||||
// the nonce currently. It could render the nonce, at which point it can be added
|
||||
// here.
|
||||
const fullMock: Partial<Threshold> = {
|
||||
blockTime: mockBlockTime,
|
||||
newThreshold: 667,
|
||||
};
|
||||
|
||||
describe('Chain Event: multisig threshold change', () => {
|
||||
it('Copes with a poorly formatted time prop', () => {
|
||||
const mockWithBadTime: Threshold = {
|
||||
blockTime: '-',
|
||||
newThreshold: 1000,
|
||||
nonce: 'nonce123',
|
||||
};
|
||||
const screen = render(
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainMultisigThreshold thresholdSet={mockWithBadTime} />
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Threshold change date'))).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainMultisigThreshold thresholdSet={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const mock: Threshold = Object.assign({}, fullMock, {
|
||||
nonce: 'nonce123',
|
||||
});
|
||||
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainMultisigThreshold thresholdSet={mock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('ERC20 multisig threshold set'))
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Threshold'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`66.7%`)).toBeInTheDocument();
|
||||
|
||||
const expectedDate = getBlockTime(mockBlockTime);
|
||||
|
||||
expect(screen.getByText(t('Threshold change date'))).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedDate)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+8
-21
@@ -2,23 +2,7 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TableRow, TableCell } from '../../../table';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
|
||||
/**
|
||||
* Returns a reasonably formatted time from unix timestamp of block height
|
||||
*
|
||||
* @param date String or null date
|
||||
* @returns String date in locale time
|
||||
*/
|
||||
function getBlockTime(date?: string) {
|
||||
if (!date) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const timeInSeconds = parseInt(date, 10);
|
||||
const timeInMs = timeInSeconds * 1000;
|
||||
|
||||
return new Date(timeInMs).toLocaleString();
|
||||
}
|
||||
import { getBlockTime } from './lib/get-block-time';
|
||||
|
||||
interface TxDetailsChainMultisigThresholdProps {
|
||||
thresholdSet: components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
|
||||
@@ -27,12 +11,15 @@ interface TxDetailsChainMultisigThresholdProps {
|
||||
/**
|
||||
* Someone updated multsig threshold value on the smart contract.
|
||||
* It's a percentage, with 1000 being 100% and 0 being 0%.
|
||||
*
|
||||
* - Nonce is not rendered. It's in the full transaction details thing
|
||||
* in case anyone really wants it, but for now it feels like detail we don't need
|
||||
*/
|
||||
export const TxDetailsChainMultisigThreshold = ({
|
||||
thresholdSet,
|
||||
}: TxDetailsChainMultisigThresholdProps) => {
|
||||
if (!thresholdSet) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!thresholdSet || !thresholdSet.blockTime || !thresholdSet.newThreshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blockTime = getBlockTime(thresholdSet.blockTime);
|
||||
@@ -43,7 +30,7 @@ export const TxDetailsChainMultisigThreshold = ({
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('ERC20 multisig threshold set')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
@@ -51,7 +38,7 @@ export const TxDetailsChainMultisigThreshold = ({
|
||||
<TableCell>{threshold}%</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Threshold set from')}</TableCell>
|
||||
<TableCell>{t('Threshold change date')}</TableCell>
|
||||
<TableCell>{blockTime}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventStakeDeposit } from './tx-stake-deposit';
|
||||
|
||||
type Deposit = components['schemas']['vegaStakeDeposited'];
|
||||
|
||||
const fullMock: Deposit = {
|
||||
amount: 'amount123',
|
||||
blockTime: 'block123',
|
||||
ethereumAddress: 'eth123',
|
||||
vegaPublicKey: 'vega123',
|
||||
};
|
||||
|
||||
describe('Chain Event: Stake deposit', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Deposit;
|
||||
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Deposit = {};
|
||||
|
||||
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventStakeDeposit deposit={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Stake deposit'))).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Deposited at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.blockTime}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.vegaPublicKey}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.ethereumAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2,33 +2,48 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TableRow, TableCell } from '../../../table';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { PartyLink } from '../../../links';
|
||||
import {
|
||||
EthExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../links/eth-explorer-link/eth-explorer-link';
|
||||
|
||||
interface TxDetailsChainEventStakeDepositProps {
|
||||
deposit: components['schemas']['vegaStakeDeposited'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Someone addedd some stake for a particular party
|
||||
* Someone added some stake for a particular party
|
||||
* This should link to the Governance asset, but doesn't
|
||||
* as that would require checking the Network Paramters
|
||||
* as that would require checking the Network Parameters
|
||||
* Ethereum address should also be a link to an ETH block explorer
|
||||
*/
|
||||
export const TxDetailsChainEventStakeDeposit = ({
|
||||
deposit,
|
||||
}: TxDetailsChainEventStakeDepositProps) => {
|
||||
if (!deposit) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!deposit ||
|
||||
!deposit.ethereumAddress ||
|
||||
!deposit.vegaPublicKey ||
|
||||
!deposit.amount ||
|
||||
!deposit.blockTime
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Stake deposited')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('Stake deposit')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>{deposit.ethereumAddress || ''}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={deposit.ethereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
@@ -42,7 +57,7 @@ export const TxDetailsChainEventStakeDeposit = ({
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Deposited at')}</TableCell>
|
||||
<TableCell>{deposit.amount}</TableCell>
|
||||
<TableCell>{deposit.blockTime}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventStakeRemove } from './tx-stake-remove';
|
||||
|
||||
type Remove = components['schemas']['vegaStakeRemoved'];
|
||||
|
||||
const fullMock: Remove = {
|
||||
amount: 'amount123',
|
||||
blockTime: 'block123',
|
||||
ethereumAddress: 'eth123',
|
||||
vegaPublicKey: 'vega123',
|
||||
};
|
||||
|
||||
describe('Chain Event: Stake remove', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as Remove;
|
||||
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: Remove = {};
|
||||
|
||||
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventStakeRemove remove={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(screen.getByText(t('Stake remove'))).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Removed at'))).toBeInTheDocument();
|
||||
expect(screen.getByText(`${fullMock.blockTime}`)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
|
||||
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
|
||||
expect(partyLink).toBeInTheDocument();
|
||||
expect(partyLink.tagName).toEqual('A');
|
||||
expect(partyLink.getAttribute('href')).toEqual(
|
||||
`/parties/${fullMock.vegaPublicKey}`
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.ethereumAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,10 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TableRow, TableCell } from '../../../table';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { PartyLink } from '../../../links';
|
||||
import {
|
||||
EthExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../links/eth-explorer-link/eth-explorer-link';
|
||||
|
||||
interface TxDetailsChainEventStakeRemoveProps {
|
||||
remove: components['schemas']['vegaStakeRemoved'];
|
||||
@@ -16,24 +20,35 @@ interface TxDetailsChainEventStakeRemoveProps {
|
||||
export const TxDetailsChainEventStakeRemove = ({
|
||||
remove,
|
||||
}: TxDetailsChainEventStakeRemoveProps) => {
|
||||
if (!remove) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (
|
||||
!remove ||
|
||||
!remove.ethereumAddress ||
|
||||
!remove.vegaPublicKey ||
|
||||
!remove.amount ||
|
||||
!remove.blockTime
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Stake removed')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('Stake remove')}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>{remove.ethereumAddress || ''}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={remove.ethereumAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Recipient')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={remove.vegaPublicKey || ''} />
|
||||
<PartyLink id={remove.vegaPublicKey} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
@@ -41,7 +56,7 @@ export const TxDetailsChainEventStakeRemove = ({
|
||||
<TableCell>{remove.amount}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Deposited at')}</TableCell>
|
||||
<TableCell>{t('Removed at')}</TableCell>
|
||||
<TableCell>{remove.blockTime}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import omit from 'lodash/omit';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { TxDetailsChainEventStakeTotalSupply } from './tx-stake-totalsupply';
|
||||
|
||||
type TotalSupply = components['schemas']['vegaStakeTotalSupply'];
|
||||
|
||||
const fullMock: TotalSupply = {
|
||||
totalSupply: '123000000000000000000000',
|
||||
tokenAddress: 'eth123',
|
||||
};
|
||||
|
||||
describe('Chain Event: Stake total supply change', () => {
|
||||
it('Renders nothing if no good data is provided', () => {
|
||||
const mock = undefined as unknown as TotalSupply;
|
||||
const screen = render(
|
||||
<TxDetailsChainEventStakeTotalSupply update={mock} />
|
||||
);
|
||||
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing if correct type with no data is provided', () => {
|
||||
const mock: TotalSupply = {};
|
||||
|
||||
const screen = render(
|
||||
<TxDetailsChainEventStakeTotalSupply update={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it(`Renders nothing if correct type with partial data is provided`, () => {
|
||||
for (const key in fullMock) {
|
||||
const mock = omit(fullMock, key);
|
||||
const screen = render(
|
||||
<TxDetailsChainEventStakeTotalSupply update={mock} />
|
||||
);
|
||||
expect(screen.container).toBeEmptyDOMElement();
|
||||
}
|
||||
});
|
||||
|
||||
it('Renders TableRows if all data is provided', () => {
|
||||
const screen = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<table>
|
||||
<tbody>
|
||||
<TxDetailsChainEventStakeTotalSupply update={fullMock} />
|
||||
</tbody>
|
||||
</table>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(t('Stake total supply update'))
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Total supply'))).toBeInTheDocument();
|
||||
expect(screen.getByText('123,000')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(t('Source'))).toBeInTheDocument();
|
||||
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
|
||||
expect(ethLink.getAttribute('href')).toContain(
|
||||
`/address/${fullMock.tokenAddress}`
|
||||
);
|
||||
});
|
||||
});
|
||||
+17
-18
@@ -18,33 +18,32 @@ interface TxDetailsChainEventStakeTotalSupplyProps {
|
||||
export const TxDetailsChainEventStakeTotalSupply = ({
|
||||
update,
|
||||
}: TxDetailsChainEventStakeTotalSupplyProps) => {
|
||||
if (!update) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
if (!update || !update.tokenAddress || !update.totalSupply) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let totalSupply = update.totalSupply || '';
|
||||
if (totalSupply.length > 0) {
|
||||
totalSupply = formatNumber(toBigNum(totalSupply, 18));
|
||||
}
|
||||
const totalSupply =
|
||||
update.totalSupply.length > 0
|
||||
? formatNumber(toBigNum(update.totalSupply, 18))
|
||||
: update.totalSupply;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Chain Event type')}</TableCell>
|
||||
<TableCell>{t('Chain event type')}</TableCell>
|
||||
<TableCell>{t('Stake total supply update')}</TableCell>
|
||||
</TableRow>
|
||||
|
||||
{update.tokenAddress ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={update.tokenAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Source')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={update.tokenAddress}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Total supply')}</TableCell>
|
||||
<TableCell>{totalSupply}</TableCell>
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
|
||||
export const ErrorCodes = new Map([
|
||||
[51, 'Transaction failed validation'],
|
||||
[60, 'Transaction could not be decoded'],
|
||||
[70, 'Internal error'],
|
||||
[80, 'Unknown command'],
|
||||
[89, 'Rejected as spam'],
|
||||
[0, 'Success'],
|
||||
]);
|
||||
|
||||
export const successCodes = new Set([0]);
|
||||
|
||||
interface ChainResponseCodeProps {
|
||||
code: number;
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Someone deposited some of a builtin asset. Builtin assets
|
||||
* have no value outside the Vega chain and should appear only
|
||||
* on Test networks.
|
||||
*/
|
||||
export const ChainResponseCode = ({
|
||||
code,
|
||||
hideLabel = false,
|
||||
}: ChainResponseCodeProps) => {
|
||||
const isSuccess = successCodes.has(code);
|
||||
|
||||
const icon = isSuccess ? '✅' : '❌';
|
||||
const label = ErrorCodes.get(code) || 'Unknown response code';
|
||||
|
||||
return (
|
||||
<div title={`Response code: ${code} - ${label}`}>
|
||||
<span
|
||||
className="mr-2"
|
||||
aria-label={isSuccess ? 'Success' : 'Warning'}
|
||||
role="img"
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
{hideLabel ? null : <span>{label}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { TimeAgo } from '../../../time-ago';
|
||||
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
|
||||
import { Time } from '../../../time';
|
||||
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
|
||||
|
||||
interface TxDetailsSharedProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -28,13 +29,19 @@ export const TxDetailsShared = ({
|
||||
}
|
||||
|
||||
const time: string = blockData?.result.block.header.time || '';
|
||||
const height: string = blockData?.result.block.header.height || '';
|
||||
const height: string = blockData?.result.block.header.height || txData.block;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Type')}</TableCell>
|
||||
<TableCell>{txData.type}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Hash')}</TableCell>
|
||||
<TableCell>{txData.hash}</TableCell>
|
||||
<TableCell>
|
||||
<code>{txData.hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Submitter')}</TableCell>
|
||||
@@ -63,6 +70,12 @@ export const TxDetailsShared = ({
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Response code')}</TableCell>
|
||||
<TableCell>
|
||||
<ChainResponseCode code={txData.code} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const TxDetailsBatch = ({
|
||||
const countTotal = countSubmissions + countAmendments + countCancellations;
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Batch size')}</TableCell>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const TxDetailsChainEvent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<ChainEvent txData={txData} />
|
||||
</TableWithTbody>
|
||||
|
||||
@@ -11,6 +11,8 @@ import { TxDetailsBatch } from './tx-batch';
|
||||
import { TxDetailsChainEvent } from './tx-chain-event';
|
||||
import { TxContent } from '../../../routes/txs/id/tx-content';
|
||||
import { TxDetailsNodeVote } from './tx-node-vote';
|
||||
import { TxDetailsOrderCancel } from './tx-order-cancel';
|
||||
import get from 'lodash/get';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -26,7 +28,8 @@ export const TxDetailsWrapper = ({
|
||||
const {
|
||||
state: { data: blockData },
|
||||
} = useFetch<TendermintBlocksResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/block?height=${height}`
|
||||
`${DATA_SOURCES.tendermintUrl}/block?height=${height}`,
|
||||
{ cache: 'force-cache' }
|
||||
);
|
||||
|
||||
const child = useMemo(() => getTransactionComponent(txData), [txData]);
|
||||
@@ -35,6 +38,8 @@ export const TxDetailsWrapper = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>{child({ txData, pubKey, blockData })}</section>
|
||||
@@ -44,12 +49,12 @@ export const TxDetailsWrapper = ({
|
||||
<TxContent data={txData} />
|
||||
</details>
|
||||
|
||||
<details title={t('Raw transaction')} className="mt-3">
|
||||
<summary className="cursor-pointer">{t('Raw transaction')}</summary>
|
||||
<code className="break-all font-mono text-xs">
|
||||
{blockData?.result.block.data.txs[txData.index]}
|
||||
</code>
|
||||
</details>
|
||||
{raw ? (
|
||||
<details title={t('Raw transaction')} className="mt-3">
|
||||
<summary className="cursor-pointer">{t('Raw transaction')}</summary>
|
||||
<code className="break-all font-mono text-xs">{raw}</code>
|
||||
</details>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -68,6 +73,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
switch (txData.type) {
|
||||
case 'Submit Order':
|
||||
return TxDetailsOrder;
|
||||
case 'Cancel Order':
|
||||
return TxDetailsOrderCancel;
|
||||
case 'Validator Heartbeat':
|
||||
return TxDetailsHeartbeat;
|
||||
case 'Amend LiquidityProvision Order':
|
||||
|
||||
@@ -24,7 +24,7 @@ export const TxDetailsGeneric = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
</TableWithTbody>
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ export const TxDetailsHeartbeat = ({
|
||||
const blockHeight = txData.command.blockHeight || '';
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Node')}</TableCell>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const TxDetailsLPAmend = ({
|
||||
const marketId = txData.command.liquidityProvisionAmendment?.marketId || '';
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
|
||||
@@ -38,7 +38,7 @@ export const TxDetailsNodeVote = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{data && !!data.deposit
|
||||
? TxDetailsNodeVoteDeposit({ deposit: data })
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import { MarketLink } from '../../links/';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import DeterministicOrderDetails from '../../deterministic-order-details/deterministic-order-details';
|
||||
|
||||
interface TxDetailsOrderCancelProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Someone cancelled an order
|
||||
*/
|
||||
export const TxDetailsOrderCancel = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsOrderCancelProps) => {
|
||||
if (!txData || !txData.command.orderCancellation) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const marketId: string = txData.command.orderCancellation.marketId || '-';
|
||||
const orderId: string = txData.command.orderCancellation.orderId || '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
{orderId !== '-' ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,8 @@ import { MarketLink } from '../../links/';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import DeterministicOrderDetails from '../../deterministic-order-details/deterministic-order-details';
|
||||
|
||||
interface TxDetailsOrderProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -27,15 +29,32 @@ export const TxDetailsOrder = ({
|
||||
}
|
||||
const marketId = txData.command.orderSubmission.marketId || '-';
|
||||
|
||||
let deterministicId = '';
|
||||
|
||||
const sig = txData.signature.value as string;
|
||||
if (sig) {
|
||||
deterministicId = txSignatureToDeterministicId(sig);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
|
||||
{deterministicId.length > 0 ? (
|
||||
<DeterministicOrderDetails id={deterministicId} version={1} />
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { TxList } from './tx-list';
|
||||
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';
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { hexToString, txSignatureToDeterministicId } from './deterministic-ids';
|
||||
|
||||
it('txSignatureToDeterministicId Turns a known signature in to a known deterministic ID', () => {
|
||||
const signature =
|
||||
'0f34fc11ffb7513295d8545a96f9628c388ef1aee028e94f0399fc5a4a7d867e5c5516ea3eec1d4ec4b2e80d8bc69ccdbde4af4494d7c9fe18450cb3a442e50e';
|
||||
const id = txSignatureToDeterministicId(signature);
|
||||
expect(id).toStrictEqual(
|
||||
'9df52e506304bf2efb0220506af688ffadbc8f52b6072b73c3694513b410137d'
|
||||
);
|
||||
});
|
||||
|
||||
it('hexToString only accepts a hex string', () => {
|
||||
expect(() => {
|
||||
hexToString('test');
|
||||
}).toThrowError('is not a hex string');
|
||||
});
|
||||
|
||||
it('hexToString encodes a known good value as bytes', () => {
|
||||
const hex = 'edd';
|
||||
const res = hexToString(hex);
|
||||
expect(res).toEqual([14, 221]);
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { sha3_256 } from 'js-sha3';
|
||||
|
||||
/**
|
||||
* Encodes a string as bytes
|
||||
* @param hex
|
||||
* @returns number[]
|
||||
*/
|
||||
export function hexToString(hex: string) {
|
||||
if (!hex.match(/^[0-9a-fA-F]+$/)) {
|
||||
throw new Error('is not a hex string.');
|
||||
}
|
||||
|
||||
const paddedHex = hex.length % 2 !== 0 ? `0${hex}` : hex;
|
||||
|
||||
const bytes = [];
|
||||
for (let n = 0; n < paddedHex.length; n += 2) {
|
||||
const code = parseInt(paddedHex.substring(n, n + 2), 16);
|
||||
bytes.push(code);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a transaction signature string, returns the deterministic
|
||||
* ID that the transaction will get. This works for:
|
||||
* - orders
|
||||
* - proposals
|
||||
*
|
||||
* @param signature
|
||||
* @return string deterministic id
|
||||
*/
|
||||
export function txSignatureToDeterministicId(signature: string): string {
|
||||
const bytes = hexToString(signature);
|
||||
const hash = sha3_256.create();
|
||||
|
||||
hash.update(bytes);
|
||||
|
||||
return hash.hex();
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import type { components } from '../../../types/explorer';
|
||||
|
||||
interface TxOrderTypeProps {
|
||||
orderType: string;
|
||||
chainEvent?: components['schemas']['v1ChainEvent'];
|
||||
command?: components['schemas']['v1InputData'];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const displayString: StringMap = {
|
||||
OrderAmendment: 'Order Amendment',
|
||||
VoteSubmission: 'Vote Submission',
|
||||
WithdrawSubmission: 'Withdraw Submission',
|
||||
Withdraw: 'Withdraw Request',
|
||||
LiquidityProvisionSubmission: 'Liquidity Provision',
|
||||
LiquidityProvisionCancellation: 'Liquidity Cancellation',
|
||||
LiquidityProvisionAmendment: 'Liquidity Amendment',
|
||||
@@ -36,6 +37,31 @@ const displayString: StringMap = {
|
||||
ValidatorHeartbeat: 'Validator Heartbeat',
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a proposal, will return a specific label
|
||||
* @param chainEvent
|
||||
* @returns
|
||||
*/
|
||||
export function getLabelForProposal(
|
||||
proposal: components['schemas']['v1ProposalSubmission']
|
||||
): string {
|
||||
if (proposal.terms?.newAsset) {
|
||||
return t('Proposal: New asset');
|
||||
} else if (proposal.terms?.updateAsset) {
|
||||
return t('Proposal: Update asset');
|
||||
} else if (proposal.terms?.newMarket) {
|
||||
return t('Proposal: New market');
|
||||
} else if (proposal.terms?.updateMarket) {
|
||||
return t('Proposal: Update market');
|
||||
} else if (proposal.terms?.updateNetworkParameter) {
|
||||
return t('Proposal: Network parameter');
|
||||
} else if (proposal.terms?.newFreeform) {
|
||||
return t('Proposal: Freeform');
|
||||
} else {
|
||||
return t('Proposal');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a chain event, will try to provide a more useful label
|
||||
* @param chainEvent
|
||||
@@ -75,7 +101,7 @@ export function getLabelForChainEvent(
|
||||
return t('Staking event');
|
||||
} else if (chainEvent.erc20Multisig) {
|
||||
if (chainEvent.erc20Multisig.signerAdded) {
|
||||
return t('Signer adde');
|
||||
return t('Signer added');
|
||||
} else if (chainEvent.erc20Multisig.signerRemoved) {
|
||||
return t('Signer remove');
|
||||
} else if (chainEvent.erc20Multisig.thresholdSet) {
|
||||
@@ -86,17 +112,44 @@ export function getLabelForChainEvent(
|
||||
return t('Chain Event');
|
||||
}
|
||||
|
||||
export const TxOrderType = ({ orderType, chainEvent }: TxOrderTypeProps) => {
|
||||
/**
|
||||
* Actually it's a transaction type, rather than an order type - this just
|
||||
* hasn't been refactored yet.
|
||||
*
|
||||
* There's no logic to the colours used -
|
||||
* - Chain events are white text on pink background
|
||||
* - Proposals are black text on yellow background
|
||||
*
|
||||
* Both of these were opted as they're easy to pick out when scrolling
|
||||
* the infinite transaction list
|
||||
*
|
||||
* The multiple paths on this one are different types from the old chain
|
||||
* explorer and the new one. When there are no longer two different APIs
|
||||
* in use, these should be consistent. For now, the view on a block page
|
||||
* can have a different label to the transaction list - but the colours
|
||||
* are consistent.
|
||||
*/
|
||||
export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
|
||||
let type = displayString[orderType] || orderType;
|
||||
|
||||
let colours = 'text-white dark:text-white bg-zinc-800 dark:bg-zinc-800';
|
||||
|
||||
// This will get unwieldy and should probably produce a different colour of tag
|
||||
if (type === 'Chain Event' && !!chainEvent) {
|
||||
type = getLabelForChainEvent(chainEvent);
|
||||
if (type === 'Chain Event' && !!command?.chainEvent) {
|
||||
type = getLabelForChainEvent(command.chainEvent);
|
||||
colours =
|
||||
'text-white dark-text-white bg-vega-pink-dark dark:bg-vega-pink-dark';
|
||||
} else if (type === 'Proposal' || type === 'Governance Proposal') {
|
||||
if (command && !!command.proposalSubmission) {
|
||||
type = getLabelForProposal(command.proposalSubmission);
|
||||
}
|
||||
colours = 'text-black bg-vega-yellow';
|
||||
}
|
||||
|
||||
if (type === 'Vote on Proposal' || type === 'Vote Submission') {
|
||||
colours = 'text-black bg-vega-yellow';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="tx-type"
|
||||
|
||||
@@ -5,65 +5,78 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
describe('Txs infinite list item', () => {
|
||||
it('should display "missing vital data" if "type" data missing', () => {
|
||||
render(
|
||||
<TxsInfiniteListItem
|
||||
type={undefined}
|
||||
submitter="test"
|
||||
hash=""
|
||||
index={0}
|
||||
block="1"
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type={undefined}
|
||||
submitter="test"
|
||||
hash=""
|
||||
code={0}
|
||||
block="1"
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "missing vital data" if "hash" data missing', () => {
|
||||
render(
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash={undefined}
|
||||
index={0}
|
||||
block="1"
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash={undefined}
|
||||
code={0}
|
||||
block="1"
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "missing vital data" if "submitter" data missing', () => {
|
||||
render(
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter={undefined}
|
||||
hash="test"
|
||||
index={0}
|
||||
block="1"
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter={undefined}
|
||||
hash="test"
|
||||
code={0}
|
||||
block="1"
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "missing vital data" if "block" data missing', () => {
|
||||
render(
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash="test"
|
||||
index={0}
|
||||
block={undefined}
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash="test"
|
||||
code={0}
|
||||
block={undefined}
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display "missing vital data" if "index" data missing', () => {
|
||||
it('should display "missing vital data" if "code" data missing', () => {
|
||||
render(
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash="test"
|
||||
index={undefined}
|
||||
block="1"
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<TxsInfiniteListItem
|
||||
type="test"
|
||||
submitter="test"
|
||||
hash="test"
|
||||
block="1"
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getByText('Missing vital data')).toBeInTheDocument();
|
||||
});
|
||||
@@ -75,8 +88,9 @@ describe('Txs infinite list item', () => {
|
||||
type="testType"
|
||||
submitter="testPubKey"
|
||||
hash="testTxHash"
|
||||
index={1}
|
||||
block="1"
|
||||
code={0}
|
||||
command={{}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -84,6 +98,6 @@ describe('Txs infinite list item', () => {
|
||||
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
|
||||
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
|
||||
expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-index')).toHaveTextContent('1');
|
||||
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success: ✅');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,23 +4,26 @@ import { Routes } from '../../routes/route-names';
|
||||
import { TxOrderType } from './tx-order-type';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import { toHex } from '../search/detect-search';
|
||||
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
|
||||
const TRUNCATE_LENGTH = 14;
|
||||
const TRUNCATE_LENGTH = 5;
|
||||
|
||||
export const TxsInfiniteListItem = ({
|
||||
hash,
|
||||
code,
|
||||
submitter,
|
||||
type,
|
||||
block,
|
||||
index,
|
||||
command,
|
||||
}: Partial<BlockExplorerTransactionResult>) => {
|
||||
if (
|
||||
!hash ||
|
||||
!submitter ||
|
||||
!type ||
|
||||
code === undefined ||
|
||||
block === undefined ||
|
||||
index === undefined
|
||||
command === undefined
|
||||
) {
|
||||
return <div>Missing vital data</div>;
|
||||
}
|
||||
@@ -55,7 +58,7 @@ export const TxsInfiniteListItem = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
|
||||
<TxOrderType orderType={type} chainEvent={command?.chainEvent} />
|
||||
<TxOrderType orderType={type} command={command} />
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
|
||||
@@ -71,10 +74,16 @@ export const TxsInfiniteListItem = ({
|
||||
</div>
|
||||
<div
|
||||
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
|
||||
data-testid="tx-index"
|
||||
data-testid="tx-success"
|
||||
>
|
||||
<span className="xl:hidden uppercase text-zinc-500">Index: </span>
|
||||
{index}
|
||||
<span className="xl:hidden uppercase text-zinc-500">
|
||||
Success:
|
||||
</span>
|
||||
{isNumber(code) ? (
|
||||
<ChainResponseCode code={code} hideLabel={true} />
|
||||
) : (
|
||||
code
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,9 @@ const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||
submitter:
|
||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||
type: 'Submit Order',
|
||||
signature: {
|
||||
value: '123',
|
||||
},
|
||||
code: 0,
|
||||
cursor: '87901.2',
|
||||
command: {
|
||||
|
||||
@@ -31,10 +31,19 @@ const Item = ({ index, style, isLoading, error }: ItemProps) => {
|
||||
} else if (isLoading) {
|
||||
content = t('Loading...');
|
||||
} else {
|
||||
const { hash, submitter, type, command, block, index: blockIndex } = index;
|
||||
const {
|
||||
hash,
|
||||
submitter,
|
||||
type,
|
||||
command,
|
||||
block,
|
||||
code,
|
||||
index: blockIndex,
|
||||
} = index;
|
||||
content = (
|
||||
<TxsInfiniteListItem
|
||||
type={type}
|
||||
code={code}
|
||||
command={command}
|
||||
submitter={submitter}
|
||||
hash={hash}
|
||||
@@ -82,7 +91,7 @@ export const TxsInfiniteList = ({
|
||||
<div className="col-span-3">Submitted By</div>
|
||||
<div className="col-span-2">Type</div>
|
||||
<div className="col-span-1">Block</div>
|
||||
<div className="col-span-1">Index</div>
|
||||
<div className="col-span-1">Success</div>
|
||||
</div>
|
||||
<div data-testid="infinite-scroll-wrapper">
|
||||
<InfiniteLoader
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ChainExplorerTxResponse } from '../../routes/types/chain-explorer-response';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { DATA_SOURCES } from '../../config';
|
||||
import { RenderFetched } from '../render-fetched';
|
||||
@@ -6,32 +5,28 @@ import { TruncatedLink } from '../truncate/truncated-link';
|
||||
import { TxOrderType } from './tx-order-type';
|
||||
import { Table, TableRow, TableCell } from '../table';
|
||||
import { t, useFetch } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||
|
||||
interface TxsPerBlockProps {
|
||||
blockHeight: string | undefined;
|
||||
blockHeight: string;
|
||||
txCount: number;
|
||||
}
|
||||
|
||||
const truncateLength = 14;
|
||||
|
||||
export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||
// TODO after https://github.com/vegaprotocol/vega/pull/6958/files is merged and deployed, use filter
|
||||
// by block height instead
|
||||
const {
|
||||
state: { data: decodedBlockData, loading, error },
|
||||
} = useFetch<ChainExplorerTxResponse[]>(DATA_SOURCES.chainExplorerUrl, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
block_height: parseInt(blockHeight!),
|
||||
node_url: `${DATA_SOURCES.tendermintUrl}/`,
|
||||
}),
|
||||
});
|
||||
|
||||
state: { data, loading, error },
|
||||
} = useFetch<BlockExplorerTransactions>(
|
||||
`${
|
||||
DATA_SOURCES.blockExplorerUrl
|
||||
}/transactions/before=${blockHeight.toString()}.0&limit=${txCount}`
|
||||
);
|
||||
return (
|
||||
<RenderFetched error={error} loading={loading} className="text-body-large">
|
||||
{decodedBlockData && decodedBlockData.length > 0 ? (
|
||||
{data && data.transactions.length > 0 ? (
|
||||
<div className="overflow-x-auto whitespace-nowrap mb-28">
|
||||
<Table>
|
||||
<thead>
|
||||
@@ -42,33 +37,31 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
{decodedBlockData.map(({ TxHash, PubKey, Type, Command }) => {
|
||||
const chainEvent = JSON.parse(Command || '');
|
||||
|
||||
{data.transactions.map(({ hash, submitter, type, command }) => {
|
||||
return (
|
||||
<TableRow
|
||||
modifier="bordered"
|
||||
key={TxHash}
|
||||
key={hash}
|
||||
data-testid="transaction-row"
|
||||
>
|
||||
<TableCell modifier="bordered" className="pr-12">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.TX}/${TxHash}`}
|
||||
text={TxHash}
|
||||
to={`/${Routes.TX}/${hash}`}
|
||||
text={hash}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered" className="pr-12">
|
||||
<TruncatedLink
|
||||
to={`/${Routes.PARTIES}/${PubKey}`}
|
||||
text={PubKey}
|
||||
to={`/${Routes.PARTIES}/${submitter}`}
|
||||
text={submitter}
|
||||
startChars={truncateLength}
|
||||
endChars={truncateLength}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell modifier="bordered">
|
||||
<TxOrderType orderType={Type} chainEvent={chainEvent} />
|
||||
<TxOrderType orderType={type} command={command} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ export const ENV = {
|
||||
// Environment
|
||||
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'),
|
||||
|
||||
@@ -29,11 +29,13 @@ export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
||||
url.searchParams.append('limit', limit);
|
||||
}
|
||||
|
||||
// Hacky fix for param as array
|
||||
let urlAsString = url.toString();
|
||||
if (filters) {
|
||||
url.searchParams.append('filters', filters);
|
||||
urlAsString += '&' + filters;
|
||||
}
|
||||
|
||||
return url;
|
||||
return urlAsString;
|
||||
};
|
||||
|
||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
@@ -49,7 +51,7 @@ export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
||||
const {
|
||||
state: { data, error, loading },
|
||||
refetch,
|
||||
} = useFetch<BlockExplorerTransactions>(url.href, {}, false);
|
||||
} = useFetch<BlockExplorerTransactions>(url, {}, false);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.transactions?.length) {
|
||||
|
||||
@@ -44,7 +44,7 @@ const Blocks = () => {
|
||||
refetch,
|
||||
} = useFetch<TendermintBlockchainResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/blockchain`,
|
||||
undefined,
|
||||
{ cache: 'no-cache' },
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ const Block = () => {
|
||||
const {
|
||||
state: { data: blockData, loading, error },
|
||||
} = useFetch<TendermintBlocksResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/block?height=${block}`
|
||||
`${DATA_SOURCES.tendermintUrl}/block?height=${block}`,
|
||||
{ cache: 'force-cache' }
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -57,6 +58,24 @@ const Block = () => {
|
||||
{blockData && (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Block hash')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<code>{blockData.result.block_id.hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Data hash')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<code>{blockData.result.block.header.data_hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Consensus hash')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<code>{blockData.result.block.header.consensus_hash}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Mined by</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
@@ -87,7 +106,10 @@ const Block = () => {
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
{blockData.result.block.data.txs.length > 0 ? (
|
||||
<TxsPerBlock blockHeight={block} />
|
||||
<TxsPerBlock
|
||||
blockHeight={blockData.result.block.header.height}
|
||||
txCount={blockData.result.block.data.txs.length}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
|
||||
export type ExplorerProposalsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: string, rejectionReason?: Types.ProposalRejectionReason | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: string, enactmentDatetime?: string | null, change: { __typename: 'NewAsset', symbol: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: string, party: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } }> | null } } } } | null> | null } | null };
|
||||
export type ExplorerProposalsQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'NewAsset', symbol: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string } } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket', marketId: string } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: any, party: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } }> | null }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, votes?: Array<{ __typename?: 'Vote', value: Types.VoteValue, datetime: any, party: { __typename?: 'Party', id: string, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string } } }> | null } } } } | null> | null } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalsDocument = gql`
|
||||
|
||||
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
|
||||
export type ExplorerMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'AccountBalance', balance: string } | null } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: string, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } }> } | null };
|
||||
export type ExplorerMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, decimalPlaces: number, tradingMode: Types.MarketTradingMode, state: Types.MarketState, fees: { __typename?: 'Fees', factors: { __typename?: 'FeeFactors', makerFee: string, infrastructureFee: string, liquidityFee: string } }, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string, metadata: { __typename?: 'InstrumentMetadata', tags?: Array<string> | null }, product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', id: string, name: string, decimals: number, globalRewardPoolAccount?: { __typename?: 'AccountBalance', balance: string } | null } } }, riskModel: { __typename?: 'LogNormalRiskModel', tau: number, riskAversionParameter: number, params: { __typename?: 'LogNormalModelParams', r: number, sigma: number, mu: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, marginCalculator?: { __typename?: 'MarginCalculator', scalingFactors: { __typename?: 'ScalingFactors', searchLevel: number, initialMargin: number, collateralRelease: number } } | null }, openingAuction: { __typename?: 'AuctionDuration', durationSecs: number, volume: number }, priceMonitoringSettings: { __typename?: 'PriceMonitoringSettings', parameters?: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null } | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: number, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', balance: string, type: Types.AccountType, asset: { __typename?: 'Asset', id: string, name: string } } } | null> | null } | null, data?: { __typename?: 'MarketData', markPrice: string, bestBidPrice: string, bestBidVolume: string, bestOfferPrice: string, bestOfferVolume: string, bestStaticBidPrice: string, bestStaticBidVolume: string, bestStaticOfferPrice: string, bestStaticOfferVolume: string, midPrice: string, staticMidPrice: string, timestamp: any, openInterest: string, auctionEnd?: string | null, auctionStart?: string | null, indicativePrice: string, indicativeVolume: string, trigger: Types.AuctionTrigger, extensionTrigger: Types.AuctionTrigger, targetStake?: string | null, suppliedStake?: string | null, marketValueProxy: string, priceMonitoringBounds?: Array<{ __typename?: 'PriceMonitoringBounds', minValidPrice: string, maxValidPrice: string, referencePrice: string, trigger: { __typename?: 'PriceMonitoringTrigger', auctionExtensionSecs: number, probability: number } }> | null, liquidityProviderFeeShare?: Array<{ __typename?: 'LiquidityProviderFeeShare', equityLikeShare: string, averageEntryValuation: string, party: { __typename?: 'Party', id: string } }> | null } | null } }> } | null };
|
||||
|
||||
|
||||
export const ExplorerMarketsDocument = gql`
|
||||
|
||||
@@ -7,14 +7,14 @@ import {
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
removePaginationWrapper,
|
||||
suitableForSyntaxHighlighter,
|
||||
t,
|
||||
useNetworkParamsQuery,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { suitableForSyntaxHighlighter } from '@vegaprotocol/react-helpers';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
|
||||
import { useNetworkParamsQuery } from '@vegaprotocol/react-helpers';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
const PERCENTAGE_PARAMS = [
|
||||
'governance.proposal.asset.requiredMajority',
|
||||
@@ -107,9 +107,7 @@ export const NetworkParametersTable = ({
|
||||
error={error}
|
||||
render={(data) => {
|
||||
const ascParams = orderBy(
|
||||
compact(data.networkParametersConnection.edges).map(
|
||||
({ node }) => node
|
||||
) || [],
|
||||
removePaginationWrapper(data.networkParametersConnection.edges),
|
||||
(param) => param.key,
|
||||
'asc'
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string } | null } } | null> | null } | null } }> } | null };
|
||||
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string } } } | null } } | null> | null } | null } }> } | null };
|
||||
|
||||
|
||||
export const ExplorerPartyAssetsDocument = gql`
|
||||
@@ -51,6 +51,11 @@ export const ExplorerPartyAssetsDocument = gql`
|
||||
balance
|
||||
market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
import {
|
||||
t,
|
||||
useFetch,
|
||||
addDecimalsFormatNumber,
|
||||
useScreenDimensions,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SubHeading } from '../../../components/sub-heading';
|
||||
import { SyntaxHighlighter, AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '../../../components/panel';
|
||||
import { InfoPanel } from '../../../components/info-panel';
|
||||
import { toNonHex } from '../../../components/search/detect-search';
|
||||
import { DATA_SOURCES } from '../../../config';
|
||||
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';
|
||||
import { useExplorerPartyAssetsQuery } from './__generated__/party-assets';
|
||||
import type { Schema } from '@vegaprotocol/types';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const accountTypeString: Record<Schema.AccountType, string> = {
|
||||
ACCOUNT_TYPE_BOND: t('Bond'),
|
||||
ACCOUNT_TYPE_EXTERNAL: t('External'),
|
||||
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: t('Fees (Infrastructure)'),
|
||||
ACCOUNT_TYPE_FEES_LIQUIDITY: t('Fees (Liquidity)'),
|
||||
ACCOUNT_TYPE_FEES_MAKER: t('Fees (Maker)'),
|
||||
ACCOUNT_TYPE_GENERAL: t('General'),
|
||||
ACCOUNT_TYPE_GLOBAL_INSURANCE: t('Global Insurance Pool'),
|
||||
ACCOUNT_TYPE_GLOBAL_REWARD: t('Global Reward Pool'),
|
||||
ACCOUNT_TYPE_INSURANCE: t('Insurance'),
|
||||
ACCOUNT_TYPE_MARGIN: t('Margin'),
|
||||
ACCOUNT_TYPE_PENDING_TRANSFERS: t('Pending Transfers'),
|
||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: t('Reward - LP Fees received'),
|
||||
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: t('Reward - Maker fees paid'),
|
||||
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: t('Reward - Maker fees received'),
|
||||
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: t('Reward - Market proposers'),
|
||||
ACCOUNT_TYPE_SETTLEMENT: t('Settlement'),
|
||||
};
|
||||
|
||||
const Party = () => {
|
||||
const { party } = useParams<{ party: string }>();
|
||||
@@ -29,12 +46,6 @@ const Party = () => {
|
||||
filters,
|
||||
});
|
||||
|
||||
const {
|
||||
state: { data: partyData },
|
||||
} = useFetch<TendermintSearchTransactionResponse>(
|
||||
`${DATA_SOURCES.tendermintUrl}/tx_search?query="tx.submitter='${partyId}'"`
|
||||
);
|
||||
|
||||
const { data } = useExplorerPartyAssetsQuery({
|
||||
// Don't cache data for this query, party information can move quite quickly
|
||||
fetchPolicy: 'network-only',
|
||||
@@ -65,9 +76,13 @@ const Party = () => {
|
||||
if (!account || !account.asset) {
|
||||
return '';
|
||||
}
|
||||
const m = get(account, 'market.tradableInstrument.instrument.name');
|
||||
|
||||
return (
|
||||
<InfoPanel title={account.asset.name} id={account.asset.id}>
|
||||
<InfoPanel
|
||||
title={account.asset.name}
|
||||
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
|
||||
>
|
||||
<section>
|
||||
<dl className="flex gap-2 flex-wrap">
|
||||
<dt className="text-zinc-500 dark:text-zinc-400 text-md">
|
||||
@@ -125,32 +140,16 @@ const Party = () => {
|
||||
{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}
|
||||
|
||||
{partyData ? (
|
||||
<>
|
||||
<SubHeading>{t('Tendermint Data')}</SubHeading>
|
||||
<SyntaxHighlighter data={partyData} />
|
||||
<SubHeading>{t('Transactions')}</SubHeading>
|
||||
<TxsInfiniteList
|
||||
hasMoreTxs={hasMoreTxs}
|
||||
areTxsLoading={loading}
|
||||
txs={txsData}
|
||||
loadMoreTxs={loadTxs}
|
||||
error={error}
|
||||
className="mb-28"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user