feat(explorer): batch market instructions (#2617)
* feat(explorer): add basic table for all types in a batch * feat(explorer): use marketlink for market ids in batch * feat(explorer): add ordersummary * feat(explorer): improve caching with some slight overfetching and cache config
This commit is contained in:
parent
cd0ed05eb8
commit
ff53e5e841
@ -1,23 +1,15 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import * as Sentry from '@sentry/react';
|
|
||||||
import { BrowserTracing } from '@sentry/tracing';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import { EnvironmentProvider, NetworkLoader } from '@vegaprotocol/environment';
|
||||||
EnvironmentProvider,
|
|
||||||
NetworkLoader,
|
|
||||||
useEnvironment,
|
|
||||||
} from '@vegaprotocol/environment';
|
|
||||||
import { NetworkInfo } from '@vegaprotocol/network-info';
|
import { NetworkInfo } from '@vegaprotocol/network-info';
|
||||||
import { Nav } from './components/nav';
|
import { Nav } from './components/nav';
|
||||||
import { Header } from './components/header';
|
import { Header } from './components/header';
|
||||||
import { Main } from './components/main';
|
import { Main } from './components/main';
|
||||||
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
||||||
import { ENV } from './config/env';
|
|
||||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { VEGA_ENV } = useEnvironment();
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -26,18 +18,9 @@ function App() {
|
|||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Sentry.init({
|
|
||||||
dsn: ENV.dsn,
|
|
||||||
integrations: [new BrowserTracing()],
|
|
||||||
tracesSampleRate: 1,
|
|
||||||
environment: VEGA_ENV,
|
|
||||||
});
|
|
||||||
}, [VEGA_ENV]);
|
|
||||||
|
|
||||||
const cacheConfig: InMemoryCacheConfig = {
|
const cacheConfig: InMemoryCacheConfig = {
|
||||||
typePolicies: {
|
typePolicies: {
|
||||||
Node: {
|
statistics: {
|
||||||
keyFields: false,
|
keyFields: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -18,7 +18,6 @@ const AssetBalance = ({
|
|||||||
showAssetLink = true,
|
showAssetLink = true,
|
||||||
}: AssetBalanceProps) => {
|
}: AssetBalanceProps) => {
|
||||||
const { data } = useExplorerAssetQuery({
|
const { data } = useExplorerAssetQuery({
|
||||||
fetchPolicy: 'cache-first',
|
|
||||||
variables: { id: assetId },
|
variables: { id: assetId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
|
|||||||
*/
|
*/
|
||||||
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
|
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
|
||||||
const { data } = useExplorerAssetQuery({
|
const { data } = useExplorerAssetQuery({
|
||||||
fetchPolicy: 'cache-first',
|
|
||||||
variables: { id },
|
variables: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
|
|||||||
label = (
|
label = (
|
||||||
<div title={t('Unknown market')}>
|
<div title={t('Unknown market')}>
|
||||||
<span role="img" aria-label="Unknown market" className="img">
|
<span role="img" aria-label="Unknown market" className="img">
|
||||||
⚠️
|
⚠️ {t('Invalid market')}
|
||||||
</span>
|
</span>
|
||||||
{id}
|
{id}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,9 +22,15 @@ fragment ExplorerDeterministicOrderFields on Order {
|
|||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
quoteName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query ExplorerDeterministicOrder($orderId: ID!, $version: Int) {
|
query ExplorerDeterministicOrder($orderId: ID!, $version: Int) {
|
||||||
|
@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, 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 ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, 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, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
|
||||||
|
|
||||||
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
||||||
orderId: Types.Scalars['ID'];
|
orderId: Types.Scalars['ID'];
|
||||||
@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, 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 ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, 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, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
|
||||||
|
|
||||||
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||||
fragment ExplorerDeterministicOrderFields on Order {
|
fragment ExplorerDeterministicOrderFields on Order {
|
||||||
@ -38,9 +38,15 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
|||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
quoteName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const ExplorerDeterministicOrderDocument = gql`
|
export const ExplorerDeterministicOrderDocument = gql`
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
|
||||||
export interface DeterministicOrderDetailsProps {
|
export interface DeterministicOrderDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -19,7 +20,8 @@ export const statusText: Record<Schema.OrderStatus, string> = {
|
|||||||
STATUS_STOPPED: t('Stopped'),
|
STATUS_STOPPED: t('Stopped'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sideText: Record<Schema.Side, string> = {
|
export const sideText: Record<components['schemas']['vegaSide'], string> = {
|
||||||
|
SIDE_UNSPECIFIED: t('Unspecified'),
|
||||||
SIDE_BUY: t('Buy'),
|
SIDE_BUY: t('Buy'),
|
||||||
SIDE_SELL: t('Sell'),
|
SIDE_SELL: t('Sell'),
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import OrderSummary from './order-summary';
|
||||||
|
import type { OrderSummaryModifier } from './order-summary';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { ExplorerDeterministicOrderDocument } from '../order-details/__generated__/Order';
|
||||||
|
|
||||||
|
const mock = {
|
||||||
|
request: {
|
||||||
|
query: ExplorerDeterministicOrderDocument,
|
||||||
|
variables: {
|
||||||
|
orderId: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
orderByID: {
|
||||||
|
__typename: 'Order',
|
||||||
|
id: '123',
|
||||||
|
type: 'GTC',
|
||||||
|
status: 'OPEN',
|
||||||
|
version: '1',
|
||||||
|
createdAt: 'Tue, Jan 10, 2023 3:35',
|
||||||
|
expiresAt: 'Tue, Jan 10, 2023 3:35',
|
||||||
|
updatedAt: 'Tue, Jan 10, 2023 3:35',
|
||||||
|
rejectionReason: null,
|
||||||
|
reference: null,
|
||||||
|
timeInForce: 'GTC',
|
||||||
|
price: '333',
|
||||||
|
side: 'SIDE_BUY',
|
||||||
|
remaining: '100',
|
||||||
|
size: '100',
|
||||||
|
party: {
|
||||||
|
id: '456',
|
||||||
|
},
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: '789',
|
||||||
|
decimalPlaces: 2,
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'TEST',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function renderComponent(
|
||||||
|
id: string,
|
||||||
|
mocks?: MockedResponse[],
|
||||||
|
modifier?: OrderSummaryModifier
|
||||||
|
) {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<OrderSummary id={id} modifier={modifier} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Order Summary component', () => {
|
||||||
|
it('side, size are present', async () => {
|
||||||
|
const res = renderComponent(mock.result.data.orderByID.id, [mock]);
|
||||||
|
expect(await res.findByText('Buy')).toBeInTheDocument();
|
||||||
|
expect(await res.findByText('100')).toBeInTheDocument();
|
||||||
|
// Note: Market is not mocked so the PriceInMarket component is not rendering in this test - hence no price formatting
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cancelled modifier add strikethrough', async () => {
|
||||||
|
const res = renderComponent(
|
||||||
|
mock.result.data.orderByID.id,
|
||||||
|
[mock],
|
||||||
|
'cancelled'
|
||||||
|
);
|
||||||
|
const buy = await res.findByText('Buy');
|
||||||
|
expect(buy.parentElement).toHaveClass('line-through');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
import { useExplorerDeterministicOrderQuery } from '../order-details/__generated__/Order';
|
||||||
|
import PriceInMarket from '../price-in-market/price-in-market';
|
||||||
|
import { sideText } from '../order-details/lib/order-labels';
|
||||||
|
|
||||||
|
// Note: Edited has no style currently
|
||||||
|
export type OrderSummaryModifier = 'cancelled' | 'edited';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides Tailwind classnames to apply to order
|
||||||
|
* @param modifier
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function getClassName(modifier?: OrderSummaryModifier) {
|
||||||
|
if (modifier === 'cancelled') {
|
||||||
|
return 'line-through';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderSummaryProps {
|
||||||
|
id: string;
|
||||||
|
modifier?: OrderSummaryModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component renders the *current* details for an order, like OrderDetails but much
|
||||||
|
* more compact. It is equivalent to OrderTxSummary, but operates on an order rather than
|
||||||
|
* the transaction that creates an order
|
||||||
|
*/
|
||||||
|
const OrderSummary = ({ id, modifier }: OrderSummaryProps) => {
|
||||||
|
const { data, error } = useExplorerDeterministicOrderQuery({
|
||||||
|
variables: { orderId: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error || !data || (data && !data.orderByID)) {
|
||||||
|
return <div data-testid="order-summary">-</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = data.orderByID;
|
||||||
|
return (
|
||||||
|
<div data-testid="order-summary" className={getClassName(modifier)}>
|
||||||
|
<span>{sideText[order.side]}</span>
|
||||||
|
<span>{order.size}</span> <i>@</i>
|
||||||
|
<PriceInMarket marketId={order.market.id} price={order.price} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderSummary;
|
@ -0,0 +1,119 @@
|
|||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import type { components } from '../../../types/explorer';
|
||||||
|
import type { UnknownObject } from '../nested-data-list';
|
||||||
|
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import OrderTxSummary from './order-tx-summary';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
|
||||||
|
|
||||||
|
type Order = components['schemas']['v1OrderSubmission'];
|
||||||
|
|
||||||
|
function renderComponent(order: Order, mocks?: MockedResponse[]) {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={mocks}>
|
||||||
|
<OrderTxSummary order={order} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
describe('Order TX Summary component', () => {
|
||||||
|
it('Renders nothing if the order passed is somehow null', () => {
|
||||||
|
const res = renderComponent({} as UnknownObject as Order);
|
||||||
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders nothing if the order passed lacks a side', () => {
|
||||||
|
const o: Order = {
|
||||||
|
marketId: '123',
|
||||||
|
price: '100',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
size: '10',
|
||||||
|
};
|
||||||
|
const res = renderComponent(o);
|
||||||
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders nothing if the order passed lacks a price', () => {
|
||||||
|
const o: Order = {
|
||||||
|
marketId: '123',
|
||||||
|
side: 'SIDE_BUY',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
size: '10',
|
||||||
|
};
|
||||||
|
const res = renderComponent(o);
|
||||||
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders nothing if the order has an unspecified side', () => {
|
||||||
|
const o: Order = {
|
||||||
|
marketId: '123',
|
||||||
|
side: 'SIDE_UNSPECIFIED',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
size: '10',
|
||||||
|
price: '10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(o);
|
||||||
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders nothing if the order has an unspecified market', () => {
|
||||||
|
const o: Order = {
|
||||||
|
side: 'SIDE_BUY',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
size: '10',
|
||||||
|
price: '10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = renderComponent(o);
|
||||||
|
expect(res.queryByTestId('order-summary')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('side, size and price in market if all details are present', async () => {
|
||||||
|
const o: Order = {
|
||||||
|
marketId: '123',
|
||||||
|
side: 'SIDE_BUY',
|
||||||
|
type: 'TYPE_LIMIT',
|
||||||
|
size: '10',
|
||||||
|
price: '333',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mock = {
|
||||||
|
request: {
|
||||||
|
query: ExplorerMarketDocument,
|
||||||
|
variables: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
market: {
|
||||||
|
id: '123',
|
||||||
|
decimalPlaces: 2,
|
||||||
|
state: 'irrelevant-test-data',
|
||||||
|
tradableInstrument: {
|
||||||
|
instrument: {
|
||||||
|
name: 'TEST',
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
quoteName: 'TEST',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const res = renderComponent(o, [mock]);
|
||||||
|
expect(res.queryByTestId('order-summary')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('Buy')).toBeInTheDocument();
|
||||||
|
expect(res.getByText('10')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Initially renders price alone
|
||||||
|
expect(res.getByText('333')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// After fetch renders formatted price and asset quotename
|
||||||
|
expect(await res.findByText('3.33')).toBeInTheDocument();
|
||||||
|
expect(await res.findByText('TEST')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,41 @@
|
|||||||
|
import type { components } from '../../../types/explorer';
|
||||||
|
|
||||||
|
import PriceInMarket from '../price-in-market/price-in-market';
|
||||||
|
import { sideText } from '../order-details/lib/order-labels';
|
||||||
|
|
||||||
|
export type OrderSummaryProps = {
|
||||||
|
order: components['schemas']['v1OrderSubmission'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a brief, relatively plaintext summary of an order transaction
|
||||||
|
* showing the price, correctly formatted. Created for the Batch Submission
|
||||||
|
* list, should be kept compact.
|
||||||
|
*
|
||||||
|
* Market name is expected to be listed elsewhere, so is not included
|
||||||
|
*/
|
||||||
|
const OrderTxSummary = ({ order }: OrderSummaryProps) => {
|
||||||
|
// Render nothing if the Order Submission doesn't look right
|
||||||
|
if (
|
||||||
|
!order ||
|
||||||
|
!order.marketId ||
|
||||||
|
!order.price ||
|
||||||
|
!order.side ||
|
||||||
|
order.side === 'SIDE_UNSPECIFIED'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="order-summary">
|
||||||
|
<span>{sideText[order.side]}</span>
|
||||||
|
<span>{order.size}</span> <i className="text-xs">@</i>
|
||||||
|
<PriceInMarket
|
||||||
|
marketId={order.marketId}
|
||||||
|
price={order.price}
|
||||||
|
></PriceInMarket>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderTxSummary;
|
@ -15,6 +15,7 @@ export type PriceInMarketProps = {
|
|||||||
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
||||||
const { data } = useExplorerMarketQuery({
|
const { data } = useExplorerMarketQuery({
|
||||||
variables: { id: marketId },
|
variables: { id: marketId },
|
||||||
|
fetchPolicy: 'cache-first',
|
||||||
});
|
});
|
||||||
|
|
||||||
let label = price;
|
let label = price;
|
||||||
@ -37,9 +38,9 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="inline-block">
|
<label>
|
||||||
<span>{label}</span> <span>{suffix}</span>
|
<span>{label}</span> <span>{suffix}</span>
|
||||||
</div>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
|
||||||
|
import { TxOrderType } from '../../tx-order-type';
|
||||||
|
import { MarketLink } from '../../../links';
|
||||||
|
import OrderSummary from '../../../order-summary/order-summary';
|
||||||
|
|
||||||
|
interface BatchAmendProps {
|
||||||
|
index: number;
|
||||||
|
submission: BatchInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table row for a single amendment in a batch submission
|
||||||
|
*/
|
||||||
|
export const BatchAmend = ({ index, submission }: BatchAmendProps) => {
|
||||||
|
return (
|
||||||
|
<tr key={`amend-${index}`}>
|
||||||
|
<td>{index}</td>
|
||||||
|
<td>
|
||||||
|
<TxOrderType orderType={'OrderAmendment'} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<OrderSummary id={submission.orderId} modifier="edited" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<MarketLink id={submission.marketId} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import type { BatchCancellationInstruction } from '../../../../routes/types/block-explorer-response';
|
||||||
|
import { TxOrderType } from '../../tx-order-type';
|
||||||
|
import { MarketLink } from '../../../links';
|
||||||
|
import OrderSummary from '../../../order-summary/order-summary';
|
||||||
|
|
||||||
|
interface BatchCancelProps {
|
||||||
|
index: number;
|
||||||
|
submission: BatchCancellationInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table row for a single cancellation in a batch submission
|
||||||
|
*/
|
||||||
|
export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{index}</td>
|
||||||
|
<td>
|
||||||
|
<TxOrderType orderType={'OrderCancellation'} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<MarketLink id={submission.marketId} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import type { BatchInstruction } from '../../../../routes/types/block-explorer-response';
|
||||||
|
import { TxOrderType } from '../../tx-order-type';
|
||||||
|
import { MarketLink } from '../../../links';
|
||||||
|
import OrderTxSummary from '../../../order-summary/order-tx-summary';
|
||||||
|
|
||||||
|
interface BatchOrderProps {
|
||||||
|
index: number;
|
||||||
|
submission: BatchInstruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table row for a single order in a batch submission
|
||||||
|
*/
|
||||||
|
export const BatchOrder = ({ index, submission }: BatchOrderProps) => {
|
||||||
|
return (
|
||||||
|
<tr key={`batch-${index}`}>
|
||||||
|
<td>{index}</td>
|
||||||
|
<td>
|
||||||
|
<TxOrderType orderType={'OrderSubmission'} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<OrderTxSummary order={submission} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<MarketLink id={submission.marketId} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
@ -31,6 +31,10 @@ export const ChainResponseCode = ({
|
|||||||
const icon = isSuccess ? '✅' : '❌';
|
const icon = isSuccess ? '✅' : '❌';
|
||||||
const label = ErrorCodes.get(code) || 'Unknown response code';
|
const label = ErrorCodes.get(code) || 'Unknown response code';
|
||||||
|
|
||||||
|
// Hack for batches with many errors - see https://github.com/vegaprotocol/vega/issues/7245
|
||||||
|
const displayError =
|
||||||
|
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div title={`Response code: ${code} - ${label}`}>
|
<div title={`Response code: ${code} - ${label}`}>
|
||||||
<span
|
<span
|
||||||
@ -41,8 +45,8 @@ export const ChainResponseCode = ({
|
|||||||
{icon}
|
{icon}
|
||||||
</span>
|
</span>
|
||||||
{hideLabel ? null : <span>{label}</span>}
|
{hideLabel ? null : <span>{label}</span>}
|
||||||
{!hideLabel && !!error ? (
|
{!hideLabel && !!displayError ? (
|
||||||
<span className="ml-1">— {error}</span>
|
<span className="ml-1 whitespace-pre">— {displayError}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,12 @@ interface TxDetailsSharedProps {
|
|||||||
blockData: TendermintBlocksResponse | undefined;
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Applied to all header cells
|
||||||
|
const sharedHeaderProps = {
|
||||||
|
// Ensures that multi line contents still have the header aligned to the first line
|
||||||
|
className: 'align-top',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These rows are shown for every transaction type, providing a consistent set of rows for the top
|
* These rows are shown for every transaction type, providing a consistent set of rows for the top
|
||||||
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
|
* of a transaction details row. The order is relatively arbitrary but felt right - it might need to
|
||||||
@ -34,27 +40,27 @@ export const TxDetailsShared = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Type')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||||
<TableCell>{txData.type}</TableCell>
|
<TableCell>{txData.type}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Hash')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<code>{txData.hash}</code>
|
<code>{txData.hash}</code>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Submitter')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Submitter')}</TableCell>
|
||||||
<TableCell>{pubKey ? <PartyLink id={pubKey} /> : '-'}</TableCell>
|
<TableCell>{pubKey ? <PartyLink id={pubKey} /> : '-'}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Block')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<BlockLink height={height} />
|
<BlockLink height={height} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Time')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{time ? (
|
{time ? (
|
||||||
<div>
|
<div>
|
||||||
@ -71,7 +77,7 @@ export const TxDetailsShared = ({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Response code')}</TableCell>
|
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<ChainResponseCode code={txData.code} error={txData.error} />
|
<ChainResponseCode code={txData.code} error={txData.error} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
import type {
|
||||||
|
BatchCancellationInstruction,
|
||||||
|
BatchInstruction,
|
||||||
|
BlockExplorerTransactionResult,
|
||||||
|
} from '../../../routes/types/block-explorer-response';
|
||||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||||
import { TableWithTbody, TableRow, TableCell } from '../../table';
|
import { TableWithTbody, TableRow, TableCell, Table } from '../../table';
|
||||||
|
import { BatchCancel } from './batch-submission/batch-cancel';
|
||||||
|
import { BatchAmend } from './batch-submission/batch-amend';
|
||||||
|
import { BatchOrder } from './batch-submission/batch-order';
|
||||||
|
|
||||||
interface TxDetailsBatchProps {
|
interface TxDetailsBatchProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@ -16,6 +23,11 @@ interface TxDetailsBatchProps {
|
|||||||
*
|
*
|
||||||
* Design considerations for batch:
|
* Design considerations for batch:
|
||||||
* - So far it's very basic details about the size of the batch
|
* - So far it's very basic details about the size of the batch
|
||||||
|
*
|
||||||
|
* Batches are processed in the following order:
|
||||||
|
* - Cancellations
|
||||||
|
* - Amends
|
||||||
|
* - Submissions
|
||||||
*/
|
*/
|
||||||
export const TxDetailsBatch = ({
|
export const TxDetailsBatch = ({
|
||||||
txData,
|
txData,
|
||||||
@ -26,17 +38,27 @@ export const TxDetailsBatch = ({
|
|||||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const countSubmissions =
|
const submissions: BatchInstruction[] =
|
||||||
txData.command.batchMarketInstructions.submissions?.length || 0;
|
txData.command.batchMarketInstructions.submissions;
|
||||||
const countAmendments =
|
const countSubmissions = submissions?.length || 0;
|
||||||
txData.command.batchMarketInstructions.amendments?.length || 0;
|
|
||||||
const countCancellations =
|
|
||||||
txData.command.batchMarketInstructions.cancellations?.length || 0;
|
|
||||||
const countTotal = countSubmissions + countAmendments + countCancellations;
|
|
||||||
|
|
||||||
|
const amendments: BatchInstruction[] =
|
||||||
|
txData.command.batchMarketInstructions.amendments;
|
||||||
|
const countAmendments = amendments?.length || 0;
|
||||||
|
|
||||||
|
const cancellations: BatchCancellationInstruction[] =
|
||||||
|
txData.command.batchMarketInstructions.cancellations;
|
||||||
|
const countCancellations = cancellations.length || 0;
|
||||||
|
const countTotal = countSubmissions + countAmendments + countCancellations;
|
||||||
|
let index = 0;
|
||||||
return (
|
return (
|
||||||
|
<div key={`tx-${index}`}>
|
||||||
<TableWithTbody className="mb-8">
|
<TableWithTbody className="mb-8">
|
||||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
/>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>{t('Batch size')}</TableCell>
|
<TableCell>{t('Batch size')}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@ -45,10 +67,10 @@ export const TxDetailsBatch = ({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="ml-5">{t('Submissions')}</span>
|
<span className="ml-5">{t('Cancellations')}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span>{countSubmissions}</span>
|
<span>{countCancellations}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
@ -61,12 +83,35 @@ export const TxDetailsBatch = ({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="ml-5">{t('Cancellations')}</span>
|
<span className="ml-5">{t('Submissions')}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span>{countCancellations}</span>
|
<span>{countSubmissions}</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableWithTbody>
|
</TableWithTbody>
|
||||||
|
|
||||||
|
<Table className="max-w-5xl min-w-fit">
|
||||||
|
<thead>
|
||||||
|
<TableRow modifier="bordered" className="font-mono">
|
||||||
|
<th align="left">{t('#')}</th>
|
||||||
|
<th align="left">{t('Type')}</th>
|
||||||
|
<th align="left">{t('Order')}</th>
|
||||||
|
<th align="left">{t('Market')}</th>
|
||||||
|
</TableRow>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{cancellations.map((c) => (
|
||||||
|
<BatchCancel key={`bc-${index}`} submission={c} index={index++} />
|
||||||
|
))}
|
||||||
|
{amendments.map((a) => (
|
||||||
|
<BatchAmend key={`ba-${index}`} submission={a} index={index++} />
|
||||||
|
))}
|
||||||
|
{submissions.map((s) => (
|
||||||
|
<BatchOrder key={`bo-${index}`} submission={s} index={index++} />
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ export const TxDetailsWrapper = ({
|
|||||||
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
|
const raw = get(blockData, `result.block.data.txs[${txData.index}]`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div key={`txd-${txData.hash}`}>
|
||||||
<section>{child({ txData, pubKey, blockData })}</section>
|
<section>{child({ txData, pubKey, blockData })}</section>
|
||||||
|
|
||||||
<details title={t('Decoded transaction')} className="mt-3">
|
<details title={t('Decoded transaction')} className="mt-3">
|
||||||
@ -59,7 +59,7 @@ export const TxDetailsWrapper = ({
|
|||||||
<code className="break-all font-mono text-xs">{raw}</code>
|
<code className="break-all font-mono text-xs">{raw}</code>
|
||||||
</details>
|
</details>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,9 +15,15 @@ fragment ExplorerPartyAssetsAccounts on AccountBalance {
|
|||||||
balance
|
balance
|
||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
|
decimalPlaces
|
||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import * as Apollo from '@apollo/client';
|
import * as Apollo from '@apollo/client';
|
||||||
const defaultOptions = {} as const;
|
const defaultOptions = {} as const;
|
||||||
export type ExplorerPartyAssetsAccountsFragment = { __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 };
|
export type ExplorerPartyAssetsAccountsFragment = { __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, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
|
||||||
|
|
||||||
export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
|
export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
|
||||||
partyId: Types.Scalars['ID'];
|
partyId: Types.Scalars['ID'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
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 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, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
|
||||||
|
|
||||||
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
|
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
|
||||||
fragment ExplorerPartyAssetsAccounts on AccountBalance {
|
fragment ExplorerPartyAssetsAccounts on AccountBalance {
|
||||||
@ -30,9 +30,15 @@ export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
|
|||||||
balance
|
balance
|
||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
|
decimalPlaces
|
||||||
tradableInstrument {
|
tradableInstrument {
|
||||||
instrument {
|
instrument {
|
||||||
name
|
name
|
||||||
|
product {
|
||||||
|
... on Future {
|
||||||
|
quoteName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
key={`pa-${account.asset.id}-${account.type}`}
|
||||||
title={account.asset.name}
|
title={account.asset.name}
|
||||||
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
|
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
|
||||||
>
|
>
|
||||||
|
@ -51,12 +51,12 @@ const Party = () => {
|
|||||||
const staking = (
|
const staking = (
|
||||||
<section>
|
<section>
|
||||||
{p?.stakingSummary?.currentStakeAvailable ? (
|
{p?.stakingSummary?.currentStakeAvailable ? (
|
||||||
<p className="mt-4 leading-3">
|
<div className="mt-4 leading-3">
|
||||||
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
|
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
|
||||||
<GovernanceAssetBalance
|
<GovernanceAssetBalance
|
||||||
price={p.stakingSummary.currentStakeAvailable}
|
price={p.stakingSummary.currentStakeAvailable}
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
|
|||||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<section className="mb-10">
|
<section className="mb-10" key={txData.hash}>
|
||||||
<TxDetailsWrapper height={txData.block} txData={txData} pubKey={pubKey} />
|
<TxDetailsWrapper height={txData.block} txData={txData} pubKey={pubKey} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user