Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86d234b6eb | ||
|
|
587065beb4 | ||
|
|
3a1522bbf5 | ||
|
|
fc0ae5478e | ||
|
|
cf89c4f902 | ||
|
|
5e9009eaf1 | ||
|
|
7f77fceeab | ||
|
|
da551f9d3c | ||
|
|
47e4861fdb | ||
|
|
1a85a89440 | ||
|
|
7ab412a8f7 | ||
|
|
5a14174a81 | ||
|
|
5e00b93783 | ||
|
|
31d9b023e6 | ||
|
|
103e503a47 | ||
|
|
1952cb0e78 | ||
|
|
69340f4ddf | ||
|
|
c52bf2200e | ||
|
|
8dccee69f2 | ||
|
|
c7a6fdd879 | ||
|
|
8bcdaf4cda | ||
|
|
3eb1359504 | ||
|
|
39cf8ad4d6 | ||
|
|
faef98f0ae | ||
|
|
710e2daa27 | ||
|
|
06ea3924fa | ||
|
|
965e2d8972 | ||
|
|
73de2fed43 | ||
|
|
536859e067 | ||
|
|
54bfaef473 | ||
|
|
e2cad707a7 | ||
|
|
ec8d9798ab | ||
|
|
e629c1e81a | ||
|
|
25feed793c | ||
|
|
2867367e16 | ||
|
|
50b408e74d | ||
|
|
8f531c7c05 | ||
|
|
350d16fee1 | ||
|
|
c052c66266 | ||
|
|
e2032df236 | ||
|
|
9ece19869d | ||
|
|
22abc8160c | ||
|
|
b1a7a22bf9 | ||
|
|
c4b38cc0dc | ||
|
|
44d6d931d5 | ||
|
|
7f1e47d7fd | ||
|
|
0eddfa4073 | ||
|
|
83a034ce91 | ||
|
|
5ab5aa01a2 | ||
|
|
b22f9156b5 | ||
|
|
bab5e2018b | ||
|
|
3f7692ce75 | ||
|
|
b69fb3b0e5 | ||
|
|
83af6e74f6 |
@@ -6,7 +6,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
|
||||
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
|
||||
|
||||
@@ -6,3 +6,4 @@ NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
|
||||
|
||||
@@ -2,6 +2,7 @@ query ExplorerMarket($id: ID!) {
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
+2
-1
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
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, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
|
||||
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
|
||||
|
||||
|
||||
export const ExplorerMarketDocument = gql`
|
||||
@@ -16,6 +16,7 @@ export const ExplorerMarketDocument = gql`
|
||||
market(id: $id) {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -55,6 +55,7 @@ describe('Market link component', () => {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
|
||||
)}
|
||||
</h4>
|
||||
{!hasChildren && (
|
||||
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
|
||||
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
|
||||
{JSON.stringify(value, null, ' ')}
|
||||
</code>
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,7 @@ fragment ExplorerDeterministicOrderFields on Order {
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -3,7 +3,7 @@ import * 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, 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 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, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
|
||||
|
||||
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
|
||||
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, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: 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, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
|
||||
|
||||
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
fragment ExplorerDeterministicOrderFields on Order {
|
||||
@@ -35,6 +35,7 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
|
||||
market {
|
||||
id
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
|
||||
@@ -61,6 +61,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
__typename: 'Market',
|
||||
id: '789',
|
||||
state: 'STATUS_ACTIVE',
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: '5',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
@@ -88,6 +89,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
|
||||
market: {
|
||||
id: '789',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MarketLink } from '../links';
|
||||
import PriceInMarket from '../price-in-market/price-in-market';
|
||||
import { Time } from '../time';
|
||||
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
|
||||
import SizeInMarket from '../size-in-market/size-in-market';
|
||||
|
||||
export interface DeterministicOrderDetailsProps {
|
||||
id: string;
|
||||
@@ -90,7 +91,7 @@ const DeterministicOrderDetails = ({
|
||||
<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}
|
||||
<SizeInMarket size={o.size} marketId={o.market.id} />
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ const mock = {
|
||||
__typename: 'Market',
|
||||
id: '789',
|
||||
state: 'STATE_ACTIVE',
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: 2,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -90,6 +90,7 @@ describe('Order TX Summary component', () => {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -37,6 +37,7 @@ const fullMock = {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import React from 'react';
|
||||
import { StatusMessage } from '../status-message';
|
||||
|
||||
interface RenderFetchedProps {
|
||||
|
||||
@@ -66,7 +66,9 @@ export const Search = () => {
|
||||
className="text-white"
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t('Enter block number, party id or transaction hash')}
|
||||
placeholder={t(
|
||||
'Enter block number, public key or transaction hash'
|
||||
)}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
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 SizeInMarket from './size-in-market';
|
||||
import type { DecimalSource } from './size-in-market';
|
||||
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
|
||||
|
||||
function renderComponent(
|
||||
price: string,
|
||||
marketId: string,
|
||||
mocks: MockedResponse[],
|
||||
decimalSource: DecimalSource = 'MARKET'
|
||||
) {
|
||||
return (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MemoryRouter>
|
||||
<SizeInMarket
|
||||
marketId={marketId}
|
||||
size={price}
|
||||
decimalSource={decimalSource}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const fullMock = {
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
id: '123',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test dai',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: 'dai',
|
||||
settlementAsset: {
|
||||
decimals: 18,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Size in Market component', () => {
|
||||
it('Renders the raw size when there is no market data', () => {
|
||||
const res = render(renderComponent('100', '123', []));
|
||||
expect(res.getByText('100')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the formatted size when market data is fetched', async () => {
|
||||
const res = render(renderComponent('100', '123', [fullMock]));
|
||||
expect(await res.findByText('1.00')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
|
||||
|
||||
export type DecimalSource = 'MARKET';
|
||||
|
||||
export type PriceInMarketProps = {
|
||||
marketId: string;
|
||||
size: string | number;
|
||||
decimalSource?: DecimalSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a market ID and an order size it will fetch the market
|
||||
* order size, and format the size accordingly
|
||||
*/
|
||||
const SizeInMarket = ({
|
||||
marketId,
|
||||
size,
|
||||
decimalSource = 'MARKET',
|
||||
}: PriceInMarketProps) => {
|
||||
const { data } = useExplorerMarketQuery({
|
||||
variables: { id: marketId },
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
let label = size;
|
||||
|
||||
if (data) {
|
||||
if (decimalSource === 'MARKET' && data.market?.positionDecimalPlaces) {
|
||||
label = addDecimalsFormatNumber(size, data.market.positionDecimalPlaces);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<label>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default SizeInMarket;
|
||||
@@ -3,6 +3,7 @@ import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface TableProps {
|
||||
allowWrap?: boolean;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
@@ -25,8 +26,15 @@ interface TableCellProps extends ThHTMLAttributes<HTMLTableCellElement> {
|
||||
modifier?: 'bordered' | 'background';
|
||||
}
|
||||
|
||||
export const Table = ({ children, className, ...props }: TableProps) => {
|
||||
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
|
||||
export const Table = ({
|
||||
allowWrap,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: TableProps) => {
|
||||
const classes = allowWrap
|
||||
? className
|
||||
: classnames(className, 'overflow-x-auto whitespace-nowrap');
|
||||
return (
|
||||
<div className={classes}>
|
||||
<table className="w-full" {...props}>
|
||||
@@ -37,11 +45,14 @@ export const Table = ({ children, className, ...props }: TableProps) => {
|
||||
};
|
||||
|
||||
export const TableWithTbody = ({
|
||||
allowWrap,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: TableProps) => {
|
||||
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
|
||||
const classes = allowWrap
|
||||
? className
|
||||
: classnames(className, 'overflow-x-auto whitespace-nowrap');
|
||||
return (
|
||||
<div className={classes}>
|
||||
<table className="w-full" {...props}>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { getValues } from './bound-factors';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
|
||||
type KeyValueBundle = components['schemas']['vegaKeyValueBundle'][];
|
||||
|
||||
describe('getValues', () => {
|
||||
it('handles an empty array by returning a dashed template', () => {
|
||||
const res = getValues([]);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '-');
|
||||
expect(res.down).toHaveProperty('value', '-');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '-');
|
||||
expect(res.up).toHaveProperty('value', '-');
|
||||
});
|
||||
|
||||
it('handles undefined', () => {
|
||||
const res = getValues(undefined as unknown as KeyValueBundle);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '-');
|
||||
expect(res.down).toHaveProperty('value', '-');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '-');
|
||||
expect(res.up).toHaveProperty('value', '-');
|
||||
});
|
||||
|
||||
it('handles a kvb that only has one side (should not happen)', () => {
|
||||
const k: KeyValueBundle = [
|
||||
{
|
||||
key: 'up',
|
||||
tolerance: '0.1',
|
||||
value: { vectorVal: { value: ['0.123'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '-');
|
||||
expect(res.down).toHaveProperty('value', '-');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '0.1');
|
||||
expect(res.up).toHaveProperty('value', '0.123');
|
||||
});
|
||||
|
||||
it('handles a kvb that has a matrixVal instead of a scalarval by ignoring it', () => {
|
||||
const k: KeyValueBundle = [
|
||||
{
|
||||
key: 'up',
|
||||
tolerance: '0.1',
|
||||
value: { matrixVal: { value: [{ value: ['0.123'] }] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '-');
|
||||
expect(res.down).toHaveProperty('value', '-');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '0.1');
|
||||
expect(res.up).toHaveProperty('value', '-');
|
||||
});
|
||||
|
||||
it('ignores unexpected extra values in the kvb', () => {
|
||||
const k: KeyValueBundle = [
|
||||
{
|
||||
key: 'up',
|
||||
tolerance: '0.1',
|
||||
value: { vectorVal: { value: ['0.123', '0.77'] } },
|
||||
},
|
||||
{
|
||||
key: 'down',
|
||||
tolerance: '0.001',
|
||||
value: { vectorVal: { value: ['0.321'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '0.001');
|
||||
expect(res.down).toHaveProperty('value', '0.321');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '0.1');
|
||||
expect(res.up).toHaveProperty('value', '0.123');
|
||||
});
|
||||
|
||||
it('handles a full kvb', () => {
|
||||
const k: KeyValueBundle = [
|
||||
{
|
||||
key: 'up',
|
||||
tolerance: '0.1',
|
||||
value: { vectorVal: { value: ['0.123'] } },
|
||||
},
|
||||
{
|
||||
key: 'down',
|
||||
tolerance: '0.001',
|
||||
value: { vectorVal: { value: ['0.321'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res).toHaveProperty('down');
|
||||
expect(res.down).toHaveProperty('tolerance', '0.001');
|
||||
expect(res.down).toHaveProperty('value', '0.321');
|
||||
expect(res).toHaveProperty('up');
|
||||
expect(res.up).toHaveProperty('tolerance', '0.1');
|
||||
expect(res.up).toHaveProperty('value', '0.123');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
|
||||
|
||||
interface StateVariableProposalBoundFactorsProps {
|
||||
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
|
||||
}
|
||||
|
||||
/**
|
||||
* A dumb as rocks function completely tied to what the structure of this variable should be
|
||||
* @param kvb The key/value bundle
|
||||
* @returns Object
|
||||
*/
|
||||
export function getValues(kvb: StateVariableProposalBoundFactorsProps['kvb']) {
|
||||
const template = {
|
||||
up: {
|
||||
tolerance: '-',
|
||||
value: '-',
|
||||
},
|
||||
down: {
|
||||
tolerance: '-',
|
||||
value: '-',
|
||||
},
|
||||
};
|
||||
|
||||
if (kvb && kvb.length > 0) {
|
||||
kvb.forEach((v) => {
|
||||
if (v.key === 'up') {
|
||||
template.up.tolerance = v.tolerance || '-';
|
||||
template.up.value = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal.value[0]
|
||||
: '-';
|
||||
} else if (v.key === 'down') {
|
||||
template.down.tolerance = v.tolerance || '-';
|
||||
template.down.value = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal.value[0]
|
||||
: '-';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals updating Bound Factors. This contains two bundles,
|
||||
* an up vector and a down vector
|
||||
*
|
||||
* This is nearly identical to risk factors.
|
||||
*/
|
||||
export const StateVariableProposalBoundFactors = ({
|
||||
kvb,
|
||||
}: StateVariableProposalBoundFactorsProps) => {
|
||||
const v = getValues(kvb);
|
||||
|
||||
return (
|
||||
<Table allowWrap={true} className="w-1/3">
|
||||
<thead>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader align="left">{t('Parameter')}</TableHeader>
|
||||
<TableHeader align="center">{t('New value')}</TableHeader>
|
||||
<TableHeader align="right">{t('Tolerance')}</TableHeader>
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Up')}</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.up.value}
|
||||
</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.up.tolerance}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Down')}</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.down.value}
|
||||
</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.down.tolerance}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { StateVariableProposalUnknown } from './unknown';
|
||||
import { StateVariableProposalBoundFactors } from './bound-factors';
|
||||
import { StateVariableProposalRiskFactors } from './risk-factors';
|
||||
|
||||
interface StateVariableProposalWrapperProps {
|
||||
stateVariable: string | undefined;
|
||||
kvb: readonly components['schemas']['vegaKeyValueBundle'][] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals
|
||||
*/
|
||||
export const StateVariableProposalWrapper = ({
|
||||
stateVariable,
|
||||
kvb,
|
||||
}: StateVariableProposalWrapperProps) => {
|
||||
if (!stateVariable || !kvb || kvb.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stateVariable.indexOf('bound-factors') !== -1) {
|
||||
return <StateVariableProposalBoundFactors kvb={kvb} />;
|
||||
} else if (stateVariable.indexOf('risk-factors') !== -1) {
|
||||
return <StateVariableProposalRiskFactors kvb={kvb} />;
|
||||
} else if (stateVariable.indexOf('probability_of_trading') !== -1) {
|
||||
return <StateVariableProposalRiskFactors kvb={kvb} />;
|
||||
} else {
|
||||
return <StateVariableProposalUnknown kvb={kvb} />;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import zip from 'lodash/zip';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
|
||||
import { StateVariableProposalUnknown } from './unknown';
|
||||
|
||||
interface StateVariableProposalRiskFactorsProps {
|
||||
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
|
||||
}
|
||||
|
||||
/**
|
||||
* A dumb as rocks function completely tied to what the structure of this variable should be
|
||||
*
|
||||
* @param kvb The key/value bundle
|
||||
* @returns Object
|
||||
*/
|
||||
export function getValues(kvb: StateVariableProposalRiskFactorsProps['kvb']) {
|
||||
try {
|
||||
const template = {
|
||||
bid: {
|
||||
offsetTolerance: '-',
|
||||
probabilityTolerance: '-',
|
||||
offset: [] as Readonly<string[]>,
|
||||
probability: [] as Readonly<string[]>,
|
||||
rows: [] as [string | undefined, string | undefined][],
|
||||
},
|
||||
ask: {
|
||||
offsetTolerance: '-',
|
||||
probabilityTolerance: '-',
|
||||
offset: [] as Readonly<string[]>,
|
||||
probability: [] as Readonly<string[]>,
|
||||
rows: [] as [string | undefined, string | undefined][],
|
||||
},
|
||||
};
|
||||
|
||||
kvb.forEach((v) => {
|
||||
if (v.key === 'bidOffset') {
|
||||
template.bid.offsetTolerance = v.tolerance || '-';
|
||||
template.bid.offset = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal?.value
|
||||
: ['0'];
|
||||
} else if (v.key === 'bidProbability') {
|
||||
template.bid.probabilityTolerance = v.tolerance || '-';
|
||||
template.bid.probability = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal?.value
|
||||
: ['0'];
|
||||
} else if (v.key === 'askOffset') {
|
||||
template.ask.offsetTolerance = v.tolerance || '-';
|
||||
template.ask.offset = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal?.value
|
||||
: ['0'];
|
||||
} else if (v.key === 'askProbability') {
|
||||
template.ask.probabilityTolerance = v.tolerance || '-';
|
||||
template.ask.probability = v.value?.vectorVal?.value
|
||||
? v.value?.vectorVal?.value
|
||||
: ['0'];
|
||||
}
|
||||
});
|
||||
|
||||
// Bundles up offset and probability in to a row
|
||||
if (template.bid.offset.length > 0 && template.bid.probability.length > 0) {
|
||||
template.bid.rows = zip(template.bid.offset, template.bid.probability);
|
||||
}
|
||||
if (template.ask.offset.length > 0 && template.ask.probability.length > 0) {
|
||||
template.ask.rows = zip(template.ask.offset, template.ask.probability);
|
||||
}
|
||||
|
||||
return template;
|
||||
} catch (e) {
|
||||
// This will result in the table not being rendered
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals updating Risk Factors. This contains two bundles,
|
||||
* a long vector and a short vector
|
||||
*/
|
||||
export const StateVariableProposalRiskFactors = ({
|
||||
kvb,
|
||||
}: StateVariableProposalRiskFactorsProps) => {
|
||||
const v = getValues(kvb);
|
||||
const all = v ? zip(v.bid.rows, v.ask.rows) : [];
|
||||
|
||||
if (all.length === 0) {
|
||||
// Give up, do a JSON view
|
||||
return <StateVariableProposalUnknown kvb={kvb} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table allowWrap={true} className="text-xs lg:text-base max-w-2xl">
|
||||
<thead>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader align="left">{t('Mid offset')}</TableHeader>
|
||||
<TableHeader align="right">{t('Bid probability')}</TableHeader>
|
||||
<TableHeader align="right" className="pl-2">
|
||||
{t('Ask probability')}
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
{all.map((r) => {
|
||||
// Simple remapping of the data to protect against undefineds
|
||||
const row = {
|
||||
o: r[0] ? r[0][0] : r[1] ? r[1][0] : '-',
|
||||
b: r[0] ? r[0][1] : '-',
|
||||
a: r[1] ? r[1][1] : '-',
|
||||
};
|
||||
return (
|
||||
<TableRow key={`${row.o}${row.b}${row.a}`}>
|
||||
<TableCell align="left">{row.o}</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{row.b}
|
||||
</TableCell>
|
||||
<TableCell align="right" className="pl-2 font-mono">
|
||||
{row.a}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
import { getValues, StateVariableProposalRiskFactors } from './risk-factors';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
type kvb = components['schemas']['vegaKeyValueBundle'][];
|
||||
|
||||
describe('Risk Factors: getValues', () => {
|
||||
it('returns null if null is passed in', () => {
|
||||
const res = getValues(null as unknown as kvb);
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns a blank template if kvb is empty', () => {
|
||||
const res = getValues([]);
|
||||
expect(res).not.toBeNull();
|
||||
expect(res?.bid.offsetTolerance).toEqual('-');
|
||||
expect(res?.bid.probabilityTolerance).toEqual('-');
|
||||
expect(res?.bid.probability).toEqual([]);
|
||||
expect(res?.bid.offset).toEqual([]);
|
||||
expect(res?.bid.rows.length).toEqual(0);
|
||||
|
||||
expect(res?.ask.offsetTolerance).toEqual('-');
|
||||
expect(res?.ask.probabilityTolerance).toEqual('-');
|
||||
expect(res?.ask.probability).toEqual([]);
|
||||
expect(res?.ask.offset).toEqual([]);
|
||||
expect(res?.ask.rows.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('parses out a correct bid offset and probability', () => {
|
||||
const k: kvb = [
|
||||
{
|
||||
key: 'bidOffset',
|
||||
value: { vectorVal: { value: ['1'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidProbability',
|
||||
value: { vectorVal: { value: ['2'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res?.bid.offset).toEqual(['1']);
|
||||
expect(res?.bid.probability).toEqual(['2']);
|
||||
expect(res?.bid.rows).toEqual([['1', '2']]);
|
||||
});
|
||||
|
||||
it('parses out a correct ask offset and probability', () => {
|
||||
const k: kvb = [
|
||||
{
|
||||
key: 'askOffset',
|
||||
value: { vectorVal: { value: ['1'] } },
|
||||
},
|
||||
{
|
||||
key: 'askProbability',
|
||||
value: { vectorVal: { value: ['2'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res?.ask.offset).toEqual(['1']);
|
||||
expect(res?.ask.probability).toEqual(['2']);
|
||||
expect(res?.ask.rows).toEqual([['1', '2']]);
|
||||
});
|
||||
|
||||
it('parses out a correct ask/bid offset and probability', () => {
|
||||
const k: kvb = [
|
||||
{
|
||||
key: 'askOffset',
|
||||
value: { vectorVal: { value: ['1'] } },
|
||||
},
|
||||
{
|
||||
key: 'askProbability',
|
||||
value: { vectorVal: { value: ['2'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidOffset',
|
||||
value: { vectorVal: { value: ['3'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidProbability',
|
||||
value: { vectorVal: { value: ['4'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const res = getValues(k);
|
||||
expect(res?.ask.rows).toEqual([['1', '2']]);
|
||||
expect(res?.bid.rows).toEqual([['3', '4']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk Factors: component', () => {
|
||||
it('renders 3 rows correctly', () => {
|
||||
const k: kvb = [
|
||||
{
|
||||
key: 'askOffset',
|
||||
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
|
||||
},
|
||||
{
|
||||
key: 'askProbability',
|
||||
value: { vectorVal: { value: ['2.2', '2.3', '2.4'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidOffset',
|
||||
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidProbability',
|
||||
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
|
||||
expect(screen.getByText('Mid offset')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bid probability')).toBeInTheDocument();
|
||||
expect(screen.getByText('Ask probability')).toBeInTheDocument();
|
||||
// First row
|
||||
expect(screen.getByText('1.1')).toBeInTheDocument();
|
||||
expect(screen.getByText('2.2')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.4')).toBeInTheDocument();
|
||||
// Second row
|
||||
expect(screen.getByText('1.2')).toBeInTheDocument();
|
||||
expect(screen.getByText('2.3')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.5')).toBeInTheDocument();
|
||||
// Third row
|
||||
expect(screen.getByText('1.3')).toBeInTheDocument();
|
||||
expect(screen.getByText('2.4')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.6')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders uneven row counts correctly', () => {
|
||||
const k: kvb = [
|
||||
{
|
||||
key: 'askOffset',
|
||||
value: { vectorVal: { value: ['1.1'] } },
|
||||
},
|
||||
{
|
||||
key: 'askProbability',
|
||||
value: { vectorVal: { value: ['2.2'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidOffset',
|
||||
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
|
||||
},
|
||||
{
|
||||
key: 'bidProbability',
|
||||
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
|
||||
},
|
||||
];
|
||||
|
||||
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
|
||||
expect(screen.getByText('Mid offset')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bid probability')).toBeInTheDocument();
|
||||
expect(screen.getByText('Ask probability')).toBeInTheDocument();
|
||||
// First row, as previous test
|
||||
expect(screen.getByText('1.1')).toBeInTheDocument();
|
||||
expect(screen.getByText('2.2')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.4')).toBeInTheDocument();
|
||||
// Second row - offset comes from bid, not ask
|
||||
expect(screen.getByText('1.2')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.5')).toBeInTheDocument();
|
||||
// Third row
|
||||
expect(screen.getByText('1.3')).toBeInTheDocument();
|
||||
expect(screen.getByText('4.6')).toBeInTheDocument();
|
||||
|
||||
// The askOffset levels without a probability render -
|
||||
expect(screen.getAllByText('-')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
|
||||
import { getValues } from './bound-factors';
|
||||
|
||||
interface StateVariableProposalBoundFactorsProps {
|
||||
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals updating Bound Factors. This contains two bundles,
|
||||
* an up vector and a down vector
|
||||
*
|
||||
* This is nearly identical to risk factors.
|
||||
*/
|
||||
export const StateVariableProposalBoundFactors = ({
|
||||
kvb,
|
||||
}: StateVariableProposalBoundFactorsProps) => {
|
||||
const v = getValues(kvb);
|
||||
|
||||
return (
|
||||
<Table allowWrap={true} className="w-1/3">
|
||||
<thead>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader align="left">{t('Parameter')}</TableHeader>
|
||||
<TableHeader align="center">{t('New value')}</TableHeader>
|
||||
<TableHeader align="right">{t('Tolerance')}</TableHeader>
|
||||
</TableRow>
|
||||
</thead>
|
||||
<tbody>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Up')}</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.up.value}
|
||||
</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.up.tolerance}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Down')}</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.down.value}
|
||||
</TableCell>
|
||||
<TableCell align="right" className="font-mono">
|
||||
{v.down.tolerance}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
|
||||
interface StateVariableProposalUnknownProps {
|
||||
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals of an unknown type. Let's just dump
|
||||
* it out.
|
||||
*/
|
||||
export const StateVariableProposalUnknown = ({
|
||||
kvb,
|
||||
}: StateVariableProposalUnknownProps) => {
|
||||
return <SyntaxHighlighter data={kvb} />;
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export const TxDetailsBatch = ({
|
||||
let index = 0;
|
||||
return (
|
||||
<div key={`tx-${index}`}>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const TxDetailsChainEvent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<ChainEvent txData={txData} />
|
||||
</TableWithTbody>
|
||||
|
||||
@@ -38,7 +38,7 @@ export const TxDetailsDataSubmission = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const TxDetailsDelegate = ({
|
||||
txData.command.delegateSubmission;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{d.nodeId ? (
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { BlockLink } from '../../links';
|
||||
import { StatusMessage } from '../../status-message';
|
||||
import { ENV } from '../../../config/env';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
|
||||
interface TxDetailsProtocolUpgradeProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator event: Protocol Upgrade proposal
|
||||
*/
|
||||
export const TxDetailsProtocolUpgrade = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsProtocolUpgradeProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const upgrade: components['schemas']['v1ProtocolUpgradeProposal'] =
|
||||
txData.command.protocolUpgradeProposal;
|
||||
|
||||
if (!upgrade || !upgrade.upgradeBlockHeight || !upgrade.vegaReleaseTag) {
|
||||
return (
|
||||
<StatusMessage>{t('Invalid upgrade proposal format')}</StatusMessage>
|
||||
);
|
||||
}
|
||||
|
||||
const urlBase = ENV.dataSources.vegaRepoUrl;
|
||||
const release = upgrade.vegaReleaseTag;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Upgrade at block')}</TableCell>
|
||||
<TableCell>
|
||||
<BlockLink height={upgrade.upgradeBlockHeight} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Upgrade to')}</TableCell>
|
||||
<TableCell>
|
||||
<ExternalLink href={`${urlBase}${release}`}>
|
||||
{upgrade.vegaReleaseTag}
|
||||
</ExternalLink>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -19,6 +19,9 @@ import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
|
||||
import { TxDetailsLiquidityCancellation } from './tx-liquidity-cancel';
|
||||
import { TxDetailsDataSubmission } from './tx-data-submission';
|
||||
import { TxProposalVote } from './tx-proposal-vote';
|
||||
import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
|
||||
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
|
||||
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -66,10 +69,14 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
|
||||
// These come from https://github.com/vegaprotocol/vega/blob/develop/core/txn/command.go#L72-L98
|
||||
switch (txData.type) {
|
||||
case 'Issue Signatures':
|
||||
return TxDetailsIssueSignatures;
|
||||
case 'Submit Order':
|
||||
return TxDetailsOrder;
|
||||
case 'Submit Oracle Data':
|
||||
return TxDetailsDataSubmission;
|
||||
case 'Protocol Upgrade':
|
||||
return TxDetailsProtocolUpgrade;
|
||||
case 'Cancel Order':
|
||||
return TxDetailsOrderCancel;
|
||||
case 'Amend Order':
|
||||
@@ -96,6 +103,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsDelegate;
|
||||
case 'Undelegate':
|
||||
return TxDetailsUndelegate;
|
||||
case 'State Variable Proposal':
|
||||
return TxDetailsStateVariable;
|
||||
default:
|
||||
return TxDetailsGeneric;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ interface TxDetailsGenericProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is not yet a custom component for a transaction, just display
|
||||
* the basic details. This allows someone to view the decoded transaction.
|
||||
* A node is
|
||||
*/
|
||||
export const TxDetailsGeneric = ({
|
||||
txData,
|
||||
@@ -24,7 +23,7 @@ export const TxDetailsGeneric = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
</TableWithTbody>
|
||||
);
|
||||
|
||||
@@ -60,7 +60,7 @@ export const TxDetailsHeartbeat = ({
|
||||
const blockHeight = txData.command.blockHeight || '';
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Node')}</TableCell>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableRow, TableCell, TableWithTbody } from '../../table';
|
||||
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import {
|
||||
EthExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../links/eth-explorer-link/eth-explorer-link';
|
||||
import { NodeLink } from '../../links';
|
||||
|
||||
type Command = components['schemas']['v1IssueSignatures'];
|
||||
|
||||
const kind: Record<components['schemas']['v1NodeSignatureKind'], string> = {
|
||||
NODE_SIGNATURE_KIND_UNSPECIFIED: 'Unspecified',
|
||||
NODE_SIGNATURE_KIND_ASSET_NEW: 'New asset',
|
||||
NODE_SIGNATURE_KIND_ASSET_WITHDRAWAL: 'Asset withdrawal',
|
||||
NODE_SIGNATURE_KIND_ASSET_UPDATE: ' Asset update',
|
||||
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_ADDED: 'Multisig signer added',
|
||||
NODE_SIGNATURE_KIND_ERC20_MULTISIG_SIGNER_REMOVED: 'Multisig signer removed',
|
||||
};
|
||||
|
||||
interface TxDetailsGenericProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is not yet a custom component for a transaction, just display
|
||||
* the basic details. This allows someone to view the decoded transaction.
|
||||
*/
|
||||
export const TxDetailsIssueSignatures = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsGenericProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const cmd: Command = txData.command;
|
||||
const k = cmd.kind ? kind[cmd.kind] : null;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{k ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Kind')}</TableCell>
|
||||
<TableCell>{k}</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{cmd.submitter ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('ETH key')}</TableCell>
|
||||
<TableCell>
|
||||
<EthExplorerLink
|
||||
id={cmd.submitter}
|
||||
type={EthExplorerLinkTypes.address}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
{cmd.validatorNodeId ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Validator')}</TableCell>
|
||||
<TableCell>
|
||||
<NodeLink id={cmd.validatorNodeId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
);
|
||||
};
|
||||
@@ -36,7 +36,7 @@ export const TxDetailsLiquidityAmendment = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 type { components } from '../../../../types/explorer';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
|
||||
export type LiquidityCancellation =
|
||||
components['schemas']['v1LiquidityProvisionCancellation'];
|
||||
@@ -36,7 +37,7 @@ export const TxDetailsLiquidityCancellation = ({
|
||||
const marketId: string = cancel.marketId || '-';
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const TxDetailsLiquiditySubmission = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const TxDetailsNodeVote = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{data && !!data.deposit
|
||||
? TxDetailsNodeVoteDeposit({ deposit: data })
|
||||
|
||||
@@ -29,7 +29,7 @@ export const TxDetailsOrderAmend = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const TxDetailsOrderCancel = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const TxDetailsOrder = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const TxProposalVote = ({
|
||||
|
||||
const vote = txData.command.voteSubmission.value ? '👍' : '👎';
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal ID')}</TableCell>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
hackyGetMarketFromStateVariable,
|
||||
hackyGetVariableFromStateVariable,
|
||||
} from './tx-state-variable-proposal';
|
||||
|
||||
describe('Hacky Get market from state variable', () => {
|
||||
it('Extracts a market id from a known state variable proposal id', () => {
|
||||
const knownId =
|
||||
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
|
||||
const res = hackyGetMarketFromStateVariable(knownId);
|
||||
expect(res).toEqual(
|
||||
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba'
|
||||
);
|
||||
});
|
||||
|
||||
it('Returns null if the string looks a bit like the known one, but with different segments', () => {
|
||||
const knownId =
|
||||
'5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
|
||||
const res = hackyGetMarketFromStateVariable(knownId);
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
|
||||
it('Handles empty/weird data', () => {
|
||||
expect(hackyGetMarketFromStateVariable(null as unknown as string)).toEqual(
|
||||
null
|
||||
);
|
||||
expect(hackyGetMarketFromStateVariable('')).toEqual(null);
|
||||
expect(
|
||||
hackyGetMarketFromStateVariable(undefined as unknown as string)
|
||||
).toEqual(null);
|
||||
expect(hackyGetMarketFromStateVariable(2 as unknown as string)).toEqual(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hacky Get Variable from state variable proposal id', () => {
|
||||
it('Extracts an variable name from a known state variable proposal id', () => {
|
||||
const knownId =
|
||||
'84fff099818dc4f5319477f0812b4341565cfb32ccf735beede734e386b8108f_5d69ff4a485a9f963272c8614c1d0d84bb8ea57886f5b11aad53f0ccc77731ba_probability_of_trading';
|
||||
const res = hackyGetVariableFromStateVariable(knownId);
|
||||
expect(res).toEqual('probability of trading');
|
||||
});
|
||||
|
||||
it('Handles empty/weird data', () => {
|
||||
expect(
|
||||
hackyGetVariableFromStateVariable(null as unknown as string)
|
||||
).toEqual(null);
|
||||
expect(hackyGetVariableFromStateVariable('')).toEqual(null);
|
||||
expect(
|
||||
hackyGetVariableFromStateVariable(undefined as unknown as string)
|
||||
).toEqual(null);
|
||||
expect(hackyGetVariableFromStateVariable(2 as unknown as string)).toEqual(
|
||||
null
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import { MarketLink } from '../../links';
|
||||
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { StateVariableProposalWrapper } from './state-variable/data-wrapper';
|
||||
|
||||
interface TxDetailsStateVariableProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no market ID in the event, but it appears to be encoded in to the variable
|
||||
* ID so let's pull it out. MarketLink component will handle if it isn't a real market.
|
||||
*
|
||||
* Given how liable to break this is, it's wrapped in a try catch
|
||||
*
|
||||
* @param stateVarId The full state variable proposal variable name
|
||||
* @returns null or a string market id
|
||||
*/
|
||||
export function hackyGetMarketFromStateVariable(
|
||||
stateVarId?: string
|
||||
): string | null {
|
||||
try {
|
||||
const res = stateVarId ? stateVarId.split('_')[1] : null;
|
||||
return res && res.length === 64 ? res : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no event name in the event, but it appears to be encoded in to the variable
|
||||
* ID so let's pull it out. Will display nothing if it doesn't parse as expected
|
||||
*
|
||||
* Given how liable to break this is, it's wrapped in a try catch
|
||||
*
|
||||
* @param stateVarId The full state variable proposal variable name
|
||||
* @returns null or a string variable name
|
||||
*/
|
||||
export function hackyGetVariableFromStateVariable(
|
||||
stateVarId?: string
|
||||
): string | null {
|
||||
try {
|
||||
if (!stateVarId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return stateVarId.split('_').slice(2).join(' ').replace('-', ' ');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State Variable proposals
|
||||
*/
|
||||
export const TxDetailsStateVariable = ({
|
||||
txData,
|
||||
pubKey,
|
||||
blockData,
|
||||
}: TxDetailsStateVariableProps) => {
|
||||
if (!txData) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const command: components['schemas']['v1StateVariableProposal'] =
|
||||
txData.command.stateVariableProposal;
|
||||
|
||||
const variable = hackyGetVariableFromStateVariable(
|
||||
command.proposal?.stateVarId
|
||||
);
|
||||
const marketId = hackyGetMarketFromStateVariable(
|
||||
command.proposal?.stateVarId
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
/>
|
||||
{marketId ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Variable')}</TableCell>
|
||||
<TableCell className="capitalize">
|
||||
<span>{variable}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<section>
|
||||
<StateVariableProposalWrapper
|
||||
stateVariable={command.proposal?.stateVarId}
|
||||
kvb={command.proposal?.kvb}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const TxDetailsUndelegate = ({
|
||||
txData.command.undelegateSubmission;
|
||||
|
||||
return (
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared txData={txData} pubKey={pubKey} blockData={blockData} />
|
||||
{u.nodeId ? (
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TxDetailsWithdrawSubmission = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8">
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
|
||||
@@ -10,6 +10,7 @@ const truthy = ['1', 'true'];
|
||||
export const ENV = {
|
||||
// Data sources
|
||||
// Environment
|
||||
env: windowOrDefault('NX_VEGA_ENV'),
|
||||
dsn: windowOrDefault('NX_EXPLORER_SENTRY_DSN'),
|
||||
dataSources: {
|
||||
blockExplorerUrl: windowOrDefault('NX_BLOCK_EXPLORER'),
|
||||
@@ -17,6 +18,7 @@ export const ENV = {
|
||||
tendermintWebsocketUrl: windowOrDefault('NX_TENDERMINT_WEBSOCKET_URL'),
|
||||
ethExplorerUrl: windowOrDefault('NX_ETHERSCAN_URL'),
|
||||
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
|
||||
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
|
||||
},
|
||||
flags: {
|
||||
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
|
||||
|
||||
@@ -10,7 +10,7 @@ export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec',
|
||||
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean }, edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } } } | null> | null } | null };
|
||||
|
||||
export type ExplorerOracleSpecByIdQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
@@ -73,7 +73,7 @@ export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
`;
|
||||
export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection(pagination: {first: 1}) {
|
||||
dataConnection {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
@@ -113,13 +113,11 @@ export const ExplorerOracleSpecsDocument = gql`
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ExplorerOracleDataSourceFragmentDoc}
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
${ExplorerOracleDataSourceFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleSpecsQuery__
|
||||
|
||||
@@ -22,7 +22,7 @@ export type SourceType =
|
||||
interface OracleDetailsProps {
|
||||
id: string;
|
||||
dataSource: ExplorerOracleDataSourceFragment;
|
||||
dataConnection: ExplorerOracleDataConnectionFragment;
|
||||
dataConnection?: ExplorerOracleDataConnectionFragment;
|
||||
// Defaults to false. Hides the count of 'broadcasts' this oracle has seen
|
||||
showBroadcasts?: boolean;
|
||||
}
|
||||
@@ -41,7 +41,8 @@ export const OracleDetails = ({
|
||||
showBroadcasts = false,
|
||||
}: OracleDetailsProps) => {
|
||||
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
|
||||
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
|
||||
const reportsCount: number =
|
||||
dataConnection?.dataConnection.edges?.length || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -63,7 +64,9 @@ export const OracleDetails = ({
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<OracleFilter data={dataSource} />
|
||||
{showBroadcasts ? <OracleData data={dataConnection} /> : null}
|
||||
{showBroadcasts && dataConnection ? (
|
||||
<OracleData data={dataConnection} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ const Oracles = () => {
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
dataConnection={o?.node}
|
||||
showBroadcasts={false}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
export const JumpToParty = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useDocumentTitle(['Parties']);
|
||||
useDocumentTitle(['Public keys']);
|
||||
|
||||
const handleSubmit = (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -27,8 +27,8 @@ export const JumpToParty = () => {
|
||||
};
|
||||
return (
|
||||
<JumpTo
|
||||
label={t('Go to party')}
|
||||
placeholder={t('Party id')}
|
||||
label={t('Go to public key')}
|
||||
placeholder={t('Public key')}
|
||||
inputId="party-input"
|
||||
inputType="text"
|
||||
inputName="partyId"
|
||||
@@ -40,7 +40,7 @@ export const JumpToParty = () => {
|
||||
const Parties = () => {
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="parties-header">{t('Parties')}</RouteTitle>
|
||||
<RouteTitle data-testid="parties-header">{t('Public keys')}</RouteTitle>
|
||||
<JumpToParty />
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ type PartyIdErrorProps = {
|
||||
const PartyIdError = ({ id, error }: PartyIdErrorProps) => {
|
||||
const end = isValidPartyId(id)
|
||||
? t('No accounts or transactions found for: ')
|
||||
: 'Invalid party id: ';
|
||||
: 'Invalid public key: ';
|
||||
return (
|
||||
<section>
|
||||
<p>
|
||||
|
||||
@@ -16,7 +16,7 @@ import { PartyAccounts } from './components/party-accounts';
|
||||
const Party = () => {
|
||||
const { party } = useParams<{ party: string }>();
|
||||
|
||||
useDocumentTitle(['Parties', party || '-']);
|
||||
useDocumentTitle(['Public keys', party || '-']);
|
||||
const partyId = toNonHex(party ? party : '');
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
||||
@@ -44,7 +44,7 @@ const Party = () => {
|
||||
/>
|
||||
) : (
|
||||
<Panel>
|
||||
<p>No party found for key {party}</p>
|
||||
<p>No data found for public key {party}</p>
|
||||
</Panel>
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const Party = () => {
|
||||
className="font-alpha uppercase font-xl mb-4 text-zinc-800 dark:text-zinc-200"
|
||||
data-testid="parties-header"
|
||||
>
|
||||
{t('Party')}
|
||||
{t('Public key')}
|
||||
</h1>
|
||||
{partyRes.data ? (
|
||||
<>
|
||||
|
||||
@@ -38,7 +38,7 @@ const Tx = () => {
|
||||
to={`/${Routes.TX}`}
|
||||
>
|
||||
<Icon
|
||||
className="text-vega-light-300 dark:text-vega-light-300"
|
||||
className="text-vega-light-150 dark:text-vega-light-150"
|
||||
name={IconNames.CHEVRON_LEFT}
|
||||
/>
|
||||
All Transactions
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -16,7 +16,6 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=jpeAkxcffzTLCzBX2m5TZIp3hF500YZhHwESwNKOGksdGPXeeIznXypaDfpNe2M9
|
||||
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
|
||||
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
|
||||
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
@@ -29,3 +28,4 @@ CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f4864
|
||||
CYPRESS_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
CYPRESS_VEGA_URL=http://localhost:3028/query
|
||||
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
|
||||
CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
|
||||
@@ -37,8 +37,8 @@ context(
|
||||
function () {
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.connectVegaWallet();
|
||||
cy.vega_wallet_teardown();
|
||||
cy.navigate_to('validators');
|
||||
}
|
||||
@@ -57,7 +57,16 @@ context(
|
||||
//0005-ETXN-006
|
||||
//0005-ETXN-003
|
||||
//0005-ETXN-005
|
||||
cy.staking_page_associate_tokens('2');
|
||||
cy.staking_page_associate_tokens('2', { skipConfirmation: true });
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '0.00');
|
||||
cy.validate_wallet_currency('Pending association', '2.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
|
||||
// 0005-ETXN-002
|
||||
cy.get(ethWalletAssociatedBalances, txTimeout)
|
||||
@@ -98,6 +107,15 @@ context(
|
||||
|
||||
cy.staking_page_disassociate_tokens('2');
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '2.00');
|
||||
cy.validate_wallet_currency('Pending association', '2.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '0.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
|
||||
cy.get(ethWalletAssociatedBalances, txTimeout).should('not.exist');
|
||||
|
||||
cy.get(ethWalletTotalAssociatedBalance, txTimeout)
|
||||
@@ -194,7 +212,19 @@ context(
|
||||
// 1004-ASSO-024
|
||||
// 1004-ASSO-023
|
||||
|
||||
cy.staking_page_associate_tokens('2', { type: 'contract' });
|
||||
cy.staking_page_associate_tokens('2', {
|
||||
type: 'contract',
|
||||
skipConfirmation: true,
|
||||
});
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '0.00');
|
||||
cy.validate_wallet_currency('Pending association', '2.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
|
||||
cy.get(ethWalletAssociatedBalances, txTimeout)
|
||||
.contains(vegaWalletPublicKeyShort)
|
||||
@@ -210,7 +240,19 @@ context(
|
||||
});
|
||||
|
||||
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
|
||||
cy.staking_page_disassociate_tokens('1', { type: 'contract' });
|
||||
cy.staking_page_disassociate_tokens('1', {
|
||||
type: 'contract',
|
||||
skipConfirmation: true,
|
||||
});
|
||||
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '2.00');
|
||||
cy.validate_wallet_currency('Pending association', '1.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '1.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
|
||||
cy.get(ethWalletAssociatedBalances, txTimeout)
|
||||
.contains(vegaWalletPublicKeyShort)
|
||||
@@ -286,6 +328,35 @@ context(
|
||||
});
|
||||
|
||||
// 1004-ASSO-004
|
||||
|
||||
it('Pending association outside of app is shown', function () {
|
||||
cy.vega_wallet_associate('2');
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '0.00');
|
||||
cy.validate_wallet_currency('Pending association', '2.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '2.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.validate_wallet_currency('Associated', '2.00');
|
||||
});
|
||||
|
||||
it('Disassociation outside of app is shown', function () {
|
||||
cy.staking_page_associate_tokens('2');
|
||||
cy.validate_wallet_currency('Associated', '2.00');
|
||||
cy.vega_wallet_disassociate('2');
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'have.length.above',
|
||||
3
|
||||
);
|
||||
cy.validate_wallet_currency('Associated', '2.00');
|
||||
cy.validate_wallet_currency('Pending association', '2.00');
|
||||
cy.validate_wallet_currency('Total associated after pending', '0.00');
|
||||
cy.getByTestId('currency-title', txTimeout).should('have.length', 3);
|
||||
cy.validate_wallet_currency('Associated', '0.00');
|
||||
});
|
||||
|
||||
it('Able to associate tokens to different public key of connected vega wallet', function () {
|
||||
cy.get(ethWalletAssociateButton).first().click();
|
||||
cy.get(associateWalletRadioButton).click();
|
||||
@@ -310,6 +381,7 @@ context(
|
||||
'vegaWalletPublicKey2Short'
|
||||
)} can now participate in governance and nominate a validator with your associated $VEGA.`
|
||||
);
|
||||
cy.staking_page_disassociate_all_tokens();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -321,6 +321,8 @@ context(
|
||||
'200000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
});
|
||||
|
||||
@@ -58,6 +58,8 @@ Cypress.Commands.add('staking_validator_page_remove_stake', (stake) => {
|
||||
Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
|
||||
let approve = options && options.approve ? options.approve : false;
|
||||
let type = options && options.type ? options.type : 'wallet';
|
||||
let skipConfirmation =
|
||||
options && options.skipConfirmation ? options.skipConfirmation : false;
|
||||
|
||||
cy.highlight(`Associating ${amount} tokens from ${type}`);
|
||||
cy.get(ethWalletAssociateButton).first().click();
|
||||
@@ -79,16 +81,19 @@ Cypress.Commands.add('staking_page_associate_tokens', (amount, options) => {
|
||||
);
|
||||
}
|
||||
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
|
||||
cy.contains(
|
||||
`Associating with Vega key. Waiting for ${Cypress.env(
|
||||
'blockConfirmations'
|
||||
)} more confirmations..`,
|
||||
txTimeout
|
||||
).should('be.visible');
|
||||
cy.contains(
|
||||
'can now participate in governance and nominate a validator',
|
||||
txTimeout
|
||||
).should('be.visible');
|
||||
|
||||
if (!skipConfirmation) {
|
||||
cy.contains(
|
||||
`Associating with Vega key. Waiting for ${Cypress.env(
|
||||
'blockConfirmations'
|
||||
)} more confirmations..`,
|
||||
txTimeout
|
||||
).should('be.visible');
|
||||
cy.contains(
|
||||
'can now participate in governance and nominate a validator',
|
||||
txTimeout
|
||||
).should('be.visible');
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('staking_page_disassociate_tokens', (amount, options) => {
|
||||
@@ -216,3 +221,19 @@ Cypress.Commands.add('close_staking_dialog', () => {
|
||||
cy.get('a').should('have.text', 'Back to Staking').click();
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'validate_wallet_currency',
|
||||
(currencyTitle, expectedAmount) => {
|
||||
cy.get("[data-testid='currency-title']")
|
||||
.contains(currencyTitle)
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getByTestId('currency-value', txTimeout).should(
|
||||
'have.text',
|
||||
expectedAmount
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
|
||||
const vegaWalletContainer = '[data-testid="vega-wallet"]';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
@@ -43,6 +42,11 @@ before('Vega wallet teardown prep', function () {
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
cy.wrap(this.stakingBridgeContract).as('stakingBridgeContract');
|
||||
cy.wrap(this.vestingContract).as('vestingContract');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('deposit_asset', function (assetEthAddress) {
|
||||
cy.get('@signer', { log: false }).then((signer) => {
|
||||
// Approve asset
|
||||
@@ -91,29 +95,18 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_teardown', function () {
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.find('[data-testid="eth-wallet-associated-balances"]').length) {
|
||||
cy.vega_wallet_teardown_vesting(this.vestingContract);
|
||||
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
|
||||
}
|
||||
});
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance)
|
||||
.first()
|
||||
.invoke('text')
|
||||
.then((balance) => {
|
||||
if (balance != '0.00') {
|
||||
cy.vega_wallet_teardown_vesting(this.vestingContract);
|
||||
cy.vega_wallet_teardown_staking(this.stakingBridgeContract);
|
||||
}
|
||||
});
|
||||
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should(
|
||||
'contain.text',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, { timeout: transactionTimeout }).should(
|
||||
'contain',
|
||||
'0.00',
|
||||
{ timeout: transactionTimeout }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
@@ -176,6 +169,22 @@ Cypress.Commands.add('vega_wallet_teardown_vesting', (vestingContract) => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_associate', (amount) => {
|
||||
amount = amount + '0'.repeat(18);
|
||||
cy.highlight('Associating tokens');
|
||||
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
|
||||
stakingBridgeContract.stake(amount, vegaWalletPubKey);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_disassociate', (amount) => {
|
||||
amount = amount + '0'.repeat(18);
|
||||
cy.highlight('Disassociating tokens');
|
||||
cy.get('@stakingBridgeContract').then((stakingBridgeContract) => {
|
||||
stakingBridgeContract.remove_stake(amount, vegaWalletPubKey);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('wait_for_transaction', (tx) => {
|
||||
cy.wrap(tx.wait(1).catch(cy.log), { timeout: transactionTimeout });
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AppLoader } from './app-loader';
|
||||
import { NetworkInfo } from '@vegaprotocol/network-info';
|
||||
import { BalanceManager } from './components/balance-manager';
|
||||
import { EthWallet } from './components/eth-wallet';
|
||||
import { AppLayout } from './components/page-templates/app-layout';
|
||||
import { TemplateSidebar } from './components/page-templates/template-sidebar';
|
||||
import { TransactionModal } from './components/transactions-modal';
|
||||
import { VegaWallet } from './components/vega-wallet';
|
||||
@@ -88,14 +89,14 @@ const Web3Container = ({
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
<>
|
||||
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
|
||||
<AppLayout>
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<footer className="p-4 border-t border-neutral-700">
|
||||
<NetworkInfo />
|
||||
</footer>
|
||||
</div>
|
||||
</AppLayout>
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
<WithdrawalDialog />
|
||||
|
||||
@@ -18,7 +18,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
<AppNavLink
|
||||
name={
|
||||
<NavDropdownMenuTrigger
|
||||
className="w-auto text-capMenu"
|
||||
className="w-auto flex items-center"
|
||||
data-testid="state-trigger"
|
||||
onClick={() => setOpen(!isOpen)}
|
||||
>
|
||||
@@ -38,6 +38,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={'inherit'}
|
||||
subNav={true}
|
||||
end={true}
|
||||
fullWidth={true}
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ interface AppNavLinkProps {
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
end?: boolean;
|
||||
fullWidth?: boolean;
|
||||
subNav?: boolean;
|
||||
}
|
||||
|
||||
export const AppNavLink = ({
|
||||
@@ -23,28 +24,32 @@ export const AppNavLink = ({
|
||||
testId,
|
||||
end = false,
|
||||
fullWidth = false,
|
||||
subNav = false,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
});
|
||||
const borderClasses = classNames(
|
||||
'absolute h-0.5 w-full bottom-[-1px] left-0',
|
||||
{
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<NavLink
|
||||
key={path}
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme, fullWidth)}
|
||||
className={getNavLinkClassNames(navbarTheme, fullWidth, subNav)}
|
||||
target={target}
|
||||
end={end}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={subNav ? 'inline-block relative pb-1' : undefined}>
|
||||
{name}
|
||||
{isActive && (
|
||||
<span data-testid="link-active" className={borderClasses} />
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
|
||||
@@ -22,9 +22,8 @@ const renderComponent = (initialEntries?: string[]) => {
|
||||
};
|
||||
|
||||
describe('nav', () => {
|
||||
it('Renders title and logo with link to home', () => {
|
||||
it('Renders logo with link to home', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Governance')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('logo-link')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/'
|
||||
|
||||
@@ -3,7 +3,8 @@ import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import vegaWhite from '../../images/vega_white.png';
|
||||
import logoWhiteText from '../../images/logo-white-text.png';
|
||||
import logoBlackText from '../../images/logo-black-text.png';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NavDrawer } from './nav-draw';
|
||||
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -46,14 +47,19 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
navbarTheme={navbarTheme}
|
||||
icon={
|
||||
<Link to="/" data-testid="logo-link">
|
||||
<img alt="Vega" src={vegaWhite} height={30} width={30} />
|
||||
<img
|
||||
alt="Vega"
|
||||
src={navbarTheme === 'yellow' ? logoBlackText : logoWhiteText}
|
||||
height={30}
|
||||
width={250}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
title={t('Governance')}
|
||||
title={undefined}
|
||||
titleContent={<NetworkSwitcher />}
|
||||
>
|
||||
{isDesktop ? (
|
||||
<nav className="flex items-center flex-1 px-2">
|
||||
<nav className="flex items-center flex-1 px-4">
|
||||
{TOP_LEVEL_ROUTES.map((r) => (
|
||||
<AppNavLink
|
||||
key={r.path}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import classNames from 'classnames';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
export const AppLayout = ({ children }: AppLayoutProps) => {
|
||||
const { isReadOnly } = useVegaWallet();
|
||||
const AppLayoutClasses = classNames(
|
||||
'app w-full max-w-[1500px] mx-auto grid min-h-full',
|
||||
'border-neutral-700 lg:border-l lg:border-r',
|
||||
'lg:text-body-large',
|
||||
{
|
||||
'grid-rows-[repeat(2,min-content)_1fr_min-content]': !isReadOnly,
|
||||
'grid-rows-[repeat(3,min-content)_1fr_min-content]': isReadOnly,
|
||||
}
|
||||
);
|
||||
|
||||
return <div className={AppLayoutClasses}>{children}</div>;
|
||||
};
|
||||
@@ -30,9 +30,7 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
) : null}
|
||||
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[1fr] lg:grid-cols-[1fr_450px]">
|
||||
<main className="col-start-1 p-4">{children}</main>
|
||||
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
+1
-1
@@ -15,7 +15,7 @@ export const ProposalFormTransactionDialog = ({
|
||||
finalizedProposal,
|
||||
TransactionDialog,
|
||||
}: ProposalFormTransactionDialogProps) => {
|
||||
// Render a custom complete UI if the proposal was rejected other wise
|
||||
// Render a custom complete UI if the proposal was rejected otherwise
|
||||
// pass undefined so that the default vega transaction dialog UI gets used
|
||||
const completeContent = finalizedProposal?.rejectionReason ? (
|
||||
<p>{finalizedProposal.rejectionReason}</p>
|
||||
|
||||
@@ -90,22 +90,15 @@ describe('Raw proposal form', () => {
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
busEvents: [
|
||||
{
|
||||
__typename: 'BusEvent',
|
||||
type: Schema.BusEventType.Proposal,
|
||||
event: {
|
||||
__typename: 'Proposal',
|
||||
id: '2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50',
|
||||
reference: 'proposal-reference',
|
||||
state: Schema.ProposalState.STATE_OPEN,
|
||||
rejectionReason:
|
||||
Schema.ProposalRejectionReason
|
||||
.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE,
|
||||
errorDetails: 'error-details',
|
||||
},
|
||||
},
|
||||
],
|
||||
proposals: {
|
||||
__typename: 'Proposal',
|
||||
id: '2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50',
|
||||
reference: 'proposal-reference',
|
||||
state: Schema.ProposalState.STATE_OPEN,
|
||||
rejectionReason:
|
||||
Schema.ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE,
|
||||
errorDetails: 'error-details',
|
||||
},
|
||||
},
|
||||
},
|
||||
delay: 300,
|
||||
|
||||
@@ -177,7 +177,7 @@ interface TotalPenaltiesRendererProps {
|
||||
performanceScore: string;
|
||||
performancePenalty: string;
|
||||
overstakedAmount: string;
|
||||
overstakedPenalty: string;
|
||||
overstakingPenalty: string;
|
||||
totalPenalties: string;
|
||||
};
|
||||
}
|
||||
@@ -192,20 +192,10 @@ export const TotalPenaltiesRenderer = ({
|
||||
description={
|
||||
<>
|
||||
<div>
|
||||
<span>
|
||||
{t('performancePenalty')}: {data.performancePenalty}
|
||||
</span>
|
||||
<span className="pl-2">
|
||||
({t('score')} {data.performanceScore})
|
||||
</span>
|
||||
{t('performancePenalty')}: {data.performancePenalty}
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
{t('overstakedPenalty')}: {data.overstakedPenalty}
|
||||
</span>
|
||||
<span className="pl-2">
|
||||
({t('overstaked')} {data.overstakedAmount})
|
||||
</span>
|
||||
{t('overstakedPenalty')}: {data.overstakingPenalty}
|
||||
</div>
|
||||
<div>
|
||||
{t('totalPenalties')}:{' '}
|
||||
|
||||
@@ -13,3 +13,9 @@
|
||||
@apply mb-2 text-neutral-400;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.border-default {
|
||||
@apply border-vega-dark-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ const txTimeout = Cypress.env('txTimeout');
|
||||
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
|
||||
const btcName =
|
||||
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
|
||||
const vegaName =
|
||||
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
|
||||
const btcSymbol = 'tBTC';
|
||||
const vegaSymbol = 'VEGA';
|
||||
const usdcSymbol = 'fUSDC';
|
||||
const toastContent = 'toast-content';
|
||||
const ordersTab = 'Orders';
|
||||
@@ -33,9 +36,112 @@ const toastCloseBtn = 'toast-close';
|
||||
const price = '390';
|
||||
const size = '0.0005';
|
||||
const newPrice = '200';
|
||||
const completeWithdrawalBtn = 'complete-withdrawal';
|
||||
|
||||
// TODO: ensure this test runs only if capsule is running via workflow
|
||||
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
|
||||
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
before(() => {
|
||||
cy.createMarket();
|
||||
cy.get('@markets').then((markets) => {
|
||||
cy.wrap(markets[0]).as('market');
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('can deposit', function () {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
// 1001-DEPO-001
|
||||
// 1001-DEPO-002
|
||||
// 1001-DEPO-003
|
||||
// 1001-DEPO-005
|
||||
// 1001-DEPO-006
|
||||
// 1001-DEPO-007
|
||||
// 1001-DEPO-008
|
||||
// 1001-DEPO-009
|
||||
// 1001-DEPO-010
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.getByTestId('deposit-approve-submit').click();
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
|
||||
cy.get('[data-testid="Return to deposit"]').click();
|
||||
cy.get(amountField).clear().type('10');
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 10.00 ${btcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId('Collateral').click();
|
||||
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
|
||||
cy.get('[col-id="txHash"]')
|
||||
.should('have.length.above', 2)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '10.00');
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Finalized');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
});
|
||||
|
||||
it('can not withdrawal because of no MultiSign', function () {
|
||||
// 1002-WITH-022
|
||||
// 1002-WITH-023
|
||||
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Funds unlocked'
|
||||
);
|
||||
|
||||
cy.getByTestId('tab-withdrawals').within(() => {
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('[col-id="status"]').should('contain.text', 'Pending');
|
||||
});
|
||||
});
|
||||
|
||||
cy.highlight('withdrawals verification');
|
||||
cy.getByTestId('toast-complete-withdrawal').click();
|
||||
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Error occurredprocessing response error'
|
||||
);
|
||||
cy.getByTestId(completeWithdrawalBtn).should(
|
||||
'contain.text',
|
||||
'Complete withdrawal'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('capsule', { tags: '@slow' }, () => {
|
||||
before(() => {
|
||||
cy.createMarket();
|
||||
@@ -128,13 +234,15 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
|
||||
cy.get('#limitPrice').focus().clear().type(newPrice);
|
||||
cy.getByTestId('edit-order').find('[type="submit"]').click();
|
||||
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(ordersTab).click();
|
||||
|
||||
cy.getByTestId(toastCloseBtn).click({ multiple: true });
|
||||
cy.getByTestId(ordersTab).click();
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
.first()
|
||||
@@ -164,62 +272,8 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
.should('contain.text', OrderStatusMapping.STATUS_CANCELLED);
|
||||
});
|
||||
|
||||
it('can deposit', function () {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
// 1001-DEPO-001
|
||||
// 1001-DEPO-002
|
||||
// 1001-DEPO-003
|
||||
// 1001-DEPO-005
|
||||
// 1001-DEPO-006
|
||||
// 1001-DEPO-007
|
||||
// 1001-DEPO-008
|
||||
// 1001-DEPO-009
|
||||
// 1001-DEPO-010
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.getByTestId('deposit-approve-submit').click();
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
|
||||
cy.get('[data-testid="Return to deposit"]').click();
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 1.00 ${btcSymbol}`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId('Collateral').click();
|
||||
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.getByTestId('asset', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
|
||||
cy.get('[col-id="txHash"]')
|
||||
.should('have.length.above', 2)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '1.00');
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Finalized');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
});
|
||||
|
||||
it('can withdrawal', function () {
|
||||
// 1002-WITH-001
|
||||
// 1002-WITH-0014
|
||||
// 1002-WITH-006
|
||||
// 1002-WITH-009
|
||||
// 1002-WITH-011
|
||||
@@ -229,14 +283,18 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
// 1002-WITH-014
|
||||
// 1002-WITH-015
|
||||
// 1002-WITH-016
|
||||
// 1002-WITH-017
|
||||
// 1002-WITH-019
|
||||
// 1002-WITH-020
|
||||
// 1002-WITH-021
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
cy.get(assetSelectField, txTimeout).select(
|
||||
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC',
|
||||
{ force: true }
|
||||
);
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
@@ -255,12 +313,13 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
|
||||
cy.highlight('withdrawals verification');
|
||||
cy.getByTestId('toast-complete-withdrawal').click();
|
||||
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Transaction confirmed'
|
||||
);
|
||||
|
||||
cy.getByTestId('complete-withdrawal', txTimeout).should('not.exist');
|
||||
cy.getByTestId(completeWithdrawalBtn).eq(0, txTimeout).should('not.exist');
|
||||
|
||||
cy.get('[col-id="txHash"]', txTimeout)
|
||||
.should('have.length.above', 1)
|
||||
@@ -281,9 +340,12 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
// comment because of bug #2819
|
||||
// cy.getByTestId('withdraw-dialog-button').click();
|
||||
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
|
||||
});
|
||||
|
||||
it('deposit - if approved amount is less than deposit: must see that an approval is needed and be prompted to approve more', function () {
|
||||
it('approved amount is less than deposit', function () {
|
||||
// 1001-DEPO-006
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
@@ -292,6 +354,67 @@ describe('capsule', { tags: '@slow' }, () => {
|
||||
cy.get(amountField).clear().type('20000000');
|
||||
cy.getByTestId('deposit-approve-submit').should('be.visible');
|
||||
});
|
||||
|
||||
it('withdraw - delay verification', function () {
|
||||
// 1001-DEPO-007
|
||||
// 1001-DEPO-024
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('main[data-testid="/portfolio"]').should('exist');
|
||||
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
|
||||
cy.getByTestId('deposit-approve-submit').click();
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
|
||||
cy.get('[data-testid="Return to deposit"]').click();
|
||||
cy.get(amountField).clear().type('10000');
|
||||
cy.getByTestId('deposit-submit').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
`Your transaction has been confirmed.`,
|
||||
{ matchCase: false }
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId('Collateral').click();
|
||||
|
||||
cy.highlight('deposit verification');
|
||||
|
||||
cy.getByTestId('asset', txTimeout).should('contain.text', vegaSymbol);
|
||||
cy.getByTestId(depositsTab).click();
|
||||
cy.get('.ag-cell-value', txTimeout).should('contain.text', vegaSymbol);
|
||||
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
|
||||
|
||||
cy.get('[col-id="txHash"]')
|
||||
.should('have.length.above', 2)
|
||||
.eq(1)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get('[col-id="asset.symbol"]').should('have.text', vegaSymbol);
|
||||
cy.get('[col-id="amount"]').should('have.text', '10,000.00');
|
||||
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
|
||||
cy.get('[col-id="status"]').should('have.text', 'Finalized');
|
||||
cy.get('[col-id="txHash"]')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', `${sepoliaUrl}/tx/0x`);
|
||||
});
|
||||
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
|
||||
cy.get(amountField).clear().type('10000');
|
||||
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
'contain.text',
|
||||
'Your funds have been unlocked'
|
||||
);
|
||||
cy.getByTestId(toastCloseBtn).click();
|
||||
cy.getByTestId(completeWithdrawalBtn).first().should('be.visible').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should('contain.text', 'Delayed');
|
||||
});
|
||||
});
|
||||
|
||||
function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
|
||||
|
||||
@@ -4,6 +4,7 @@ const marketInfoBtn = 'Info';
|
||||
const row = 'key-value-table-row';
|
||||
const marketTitle = 'accordion-title';
|
||||
const externalLink = 'external-link';
|
||||
const accordionContent = 'accordion-content';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
@@ -179,7 +180,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
'termination.BTC.value'
|
||||
);
|
||||
|
||||
cy.getByTestId(externalLink)
|
||||
cy.getByTestId(accordionContent)
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/oracles');
|
||||
});
|
||||
@@ -187,12 +189,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
it('proposal displayed', () => {
|
||||
cy.getByTestId(marketTitle).contains('Proposal').click();
|
||||
|
||||
cy.getByTestId(externalLink)
|
||||
cy.getByTestId(accordionContent)
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.first()
|
||||
.should('have.text', 'View governance proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('contain', '/proposals/market-0');
|
||||
cy.getByTestId(externalLink)
|
||||
cy.getByTestId(accordionContent)
|
||||
.find(`[data-testid="${externalLink}"]`)
|
||||
.eq(1)
|
||||
.should('have.text', 'Propose a change to market')
|
||||
.and('have.attr', 'href')
|
||||
|
||||
@@ -104,7 +104,8 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
'have.length',
|
||||
10
|
||||
);
|
||||
cy.getByTestId('external-link')
|
||||
cy.getByTestId('tab-proposed-markets')
|
||||
.find('[data-testid="external-link"]')
|
||||
.should('have.length', 11)
|
||||
.last()
|
||||
.should('have.text', 'Propose a new market')
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
|
||||
cy.get(toAddressField).should('have.value', ethAddressValue);
|
||||
});
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
@@ -50,6 +51,8 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
it('max amount', () => {
|
||||
// 1002-WITH-005
|
||||
// 1002-WITH-008
|
||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
@@ -60,6 +63,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('can set amount using use maximum button', () => {
|
||||
// 1002-WITH-004
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(useMaximumAmount).click();
|
||||
cy.get(amountField).should('have.value', '1000.00000');
|
||||
@@ -68,6 +72,9 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
|
||||
|
||||
describe('withdraw actions', { tags: '@regression' }, () => {
|
||||
// this is extremely ugly hack, but setting it properly in contract is too much effort for such simple validation
|
||||
|
||||
// 1002-WITH-018
|
||||
|
||||
const withdrawalThreshold =
|
||||
Cypress.env('VEGA_ENV') === 'CUSTOM' ? '0.00' : '100.00';
|
||||
before(() => {
|
||||
@@ -88,6 +95,8 @@ describe('withdraw actions', { tags: '@regression' }, () => {
|
||||
});
|
||||
|
||||
it('triggers transaction when submitted', () => {
|
||||
// 1002-WITH-002
|
||||
// 1002-WITH-003
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId('BALANCE_AVAILABLE_label').should(
|
||||
'contain.text',
|
||||
@@ -108,7 +117,4 @@ describe('withdraw actions', { tags: '@regression' }, () => {
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
|
||||
});
|
||||
|
||||
it.skip('creates a withdrawal on submit'); // Needs capsule
|
||||
it.skip('creates a withdrawal on submit and prompts to complete withdrawal'); // Needs capsule
|
||||
});
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
|
||||
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -9,6 +9,7 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_URL=http://localhost:3028/query
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
|
||||
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"h
|
||||
NX_VEGA_TOKEN_URL=https://token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -7,4 +7,5 @@ NX_VEGA_EXPLORER_URL=https://mainnet-mirror.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"STAGNET3\":\"https://stagnet3.console.vega.xyz\"}
|
||||
NX_VEGA_TOKEN_URL=https://mainnet-mirror.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -7,3 +7,4 @@ NX_VEGA_NETWORKS={\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET3\":\"https
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/sandbox-network.json
|
||||
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
|
||||
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
|
||||
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
|
||||
@@ -8,3 +8,4 @@ NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"
|
||||
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_TOKEN_URL=https://token.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { AccountManager } from '@vegaprotocol/accounts';
|
||||
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
|
||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||
|
||||
export const AccountsContainer = () => {
|
||||
@@ -13,6 +13,7 @@ export const AccountsContainer = () => {
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
|
||||
const openDepositDialog = useDepositDialog((store) => store.open);
|
||||
const openTransferDialog = useTransferDialog((store) => store.open);
|
||||
|
||||
const onClickAsset = useCallback(
|
||||
(assetId?: string) => {
|
||||
@@ -41,7 +42,10 @@ export const AccountsContainer = () => {
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="flex justify-end p-2 px-[11px]">
|
||||
<div className="flex gap-2 justify-end p-2 px-[11px]">
|
||||
<Button size="sm" onClick={() => openTransferDialog()}>
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => openDepositDialog()}>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const Banner = () => {
|
||||
const { update, shouldDisplayAnnouncementBanner } = useGlobalStore(
|
||||
(store) => ({
|
||||
update: store.update,
|
||||
shouldDisplayAnnouncementBanner: store.shouldDisplayAnnouncementBanner,
|
||||
})
|
||||
);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayAnnouncementBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
onClick={() => update({ shouldDisplayAnnouncementBanner: false })}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">The Mainnet sims are live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './banner';
|
||||
@@ -15,7 +15,7 @@ export const Header = ({ title, children }: TradeMarketHeaderProps) => {
|
||||
<div className="px-4 xl:px-0 pb-2 xl:pb-3">{title}</div>
|
||||
<div
|
||||
data-testid="header-summary"
|
||||
className="flex flex-nowrap items-start xl:flex-1 w-full overflow-x-auto text-xs"
|
||||
className="flex flex-nowrap items-end xl:flex-1 w-full overflow-x-auto text-xs"
|
||||
>
|
||||
{Children.map(children, (child, index) => {
|
||||
if (!child) return null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useGlobalStore } from '../../stores/global';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
import {
|
||||
Drawer,
|
||||
@@ -165,7 +165,7 @@ export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<LinkList className="hidden md:flex" navbarTheme={navbarTheme} />
|
||||
<LinkList className="hidden md:flex md:px-2" navbarTheme={navbarTheme} />
|
||||
<div className="flex items-center gap-2 ml-auto overflow-hidden">
|
||||
<VegaWalletConnectButton />
|
||||
<ThemeSwitcher className="hidden md:block" />
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { VegaTransaction } from './vega-transaction';
|
||||
@@ -1,57 +0,0 @@
|
||||
import { WithdrawalFeedback } from '@vegaprotocol/withdraws';
|
||||
import { OrderFeedback } from '@vegaprotocol/orders';
|
||||
|
||||
import {
|
||||
VegaDialog,
|
||||
VegaTxStatus,
|
||||
isWithdrawTransaction,
|
||||
isOrderCancellationTransaction,
|
||||
isOrderSubmissionTransaction,
|
||||
isOrderAmendmentTransaction,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type { VegaStoredTxState } from '@vegaprotocol/wallet';
|
||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
||||
|
||||
export const VegaTransaction = ({
|
||||
transaction,
|
||||
}: {
|
||||
transaction: VegaStoredTxState;
|
||||
}) => {
|
||||
const createEthWithdrawalApproval = useEthWithdrawApprovalsStore(
|
||||
(state) => state.create
|
||||
);
|
||||
if (isWithdrawTransaction(transaction.body)) {
|
||||
if (
|
||||
transaction.status === VegaTxStatus.Complete &&
|
||||
transaction.withdrawal
|
||||
) {
|
||||
return (
|
||||
<WithdrawalFeedback
|
||||
transaction={transaction}
|
||||
withdrawal={transaction.withdrawal}
|
||||
availableTimestamp={null}
|
||||
submitWithdraw={() => {
|
||||
if (!transaction?.withdrawal) {
|
||||
return;
|
||||
}
|
||||
createEthWithdrawalApproval(
|
||||
transaction.withdrawal,
|
||||
transaction.withdrawalApproval
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
(isOrderCancellationTransaction(transaction.body) ||
|
||||
isOrderSubmissionTransaction(transaction.body) ||
|
||||
isOrderAmendmentTransaction(transaction.body)) &&
|
||||
transaction.status === VegaTxStatus.Complete &&
|
||||
transaction.order
|
||||
) {
|
||||
return (
|
||||
<OrderFeedback transaction={transaction} order={transaction.order} />
|
||||
);
|
||||
}
|
||||
return <VegaDialog transaction={transaction} />;
|
||||
};
|
||||
@@ -13,11 +13,13 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
Drawer,
|
||||
DropdownMenuSeparator,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { WalletIcon } from '../icons/wallet';
|
||||
import { useTransferDialog } from '@vegaprotocol/accounts';
|
||||
|
||||
const MobileWalletButton = ({
|
||||
isConnected,
|
||||
@@ -30,6 +32,7 @@ const MobileWalletButton = ({
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const openTransferDialog = useTransferDialog((store) => store.open);
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const isYellow = VEGA_ENV === Networks.TESTNET;
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
@@ -115,7 +118,16 @@ const MobileWalletButton = ({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="m-4">
|
||||
<div className="flex flex-col gap-2 m-4">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDrawerOpen(false);
|
||||
openTransferDialog(true);
|
||||
}}
|
||||
fill
|
||||
>
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
<Button onClick={mobileDisconnect} fill>
|
||||
{t('Disconnect')}
|
||||
</Button>
|
||||
@@ -131,6 +143,7 @@ export const VegaWalletConnectButton = () => {
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const openTransferDialog = useTransferDialog((store) => store.open);
|
||||
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
|
||||
const isConnected = pubKey !== null;
|
||||
|
||||
@@ -171,6 +184,10 @@ export const VegaWalletConnectButton = () => {
|
||||
<KeypairItem key={pk.publicKey} pk={pk} />
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => openTransferDialog(true)}>
|
||||
{t('Transfer')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem data-testid="disconnect" onClick={disconnect}>
|
||||
{t('Disconnect')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -3,8 +3,12 @@ import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import { ETHERSCAN_TX, useEtherscanLink } from '@vegaprotocol/environment';
|
||||
import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
|
||||
import { useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { ExternalLink, Intent, ProgressBar } from '@vegaprotocol/ui-toolkit';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import type { EthStoredTxState } from '@vegaprotocol/web3';
|
||||
import {
|
||||
@@ -44,34 +48,35 @@ const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
|
||||
if (isWithdraw) label = t('Withdraw');
|
||||
if (isDeposit) label = t('Deposit');
|
||||
assetInfo = (
|
||||
<div className="mt-[5px]">
|
||||
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
|
||||
{label}{' '}
|
||||
{formatNumber(toBigNum(tx.args[1], asset.decimals), asset.decimals)}{' '}
|
||||
{asset.symbol}
|
||||
</span>
|
||||
</div>
|
||||
<strong>
|
||||
{label}{' '}
|
||||
{formatNumber(toBigNum(tx.args[1], asset.decimals), asset.decimals)}{' '}
|
||||
{asset.symbol}
|
||||
</strong>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{assetInfo}
|
||||
{tx.status === EthTxStatus.Pending && (
|
||||
<div className="mt-[10px]">
|
||||
<span className="font-mono text-xs">
|
||||
{t('Awaiting confirmations')}{' '}
|
||||
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
|
||||
</span>
|
||||
<ProgressBar
|
||||
value={(tx.confirmations / tx.requiredConfirmations) * 100}
|
||||
intent={Intent.Warning}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
if (assetInfo || tx.requiresConfirmation) {
|
||||
return (
|
||||
<Panel>
|
||||
{assetInfo}
|
||||
{tx.status === EthTxStatus.Pending && (
|
||||
<>
|
||||
<p className="mt-[2px]">
|
||||
{t('Awaiting confirmations')}{' '}
|
||||
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
|
||||
</p>
|
||||
<ProgressBar
|
||||
value={(tx.confirmations / tx.requiredConfirmations) * 100}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
type EthTxToastContentProps = {
|
||||
@@ -80,26 +85,26 @@ type EthTxToastContentProps = {
|
||||
|
||||
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Action required')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Action required')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Please go to your wallet application and approve or reject the transaction.'
|
||||
)}
|
||||
</p>
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
|
||||
<p>{t('Please wait for your transaction to be confirmed.')}</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -112,11 +117,11 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
errorMessage = tx.error.message;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Error occurred')}</h3>
|
||||
<p>{errorMessage}</p>
|
||||
<>
|
||||
<ToastHeading>{t('Error occurred')}</ToastHeading>
|
||||
<p className="first-letter:uppercase">{errorMessage}</p>
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -136,42 +141,63 @@ const EtherscanLink = ({ tx }: EthTxToastContentProps) => {
|
||||
|
||||
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Transaction confirmed')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
|
||||
<p>{t('Your transaction has been confirmed.')}</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const isDeposit = isDepositTransaction(tx);
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">
|
||||
<>
|
||||
<ToastHeading>
|
||||
{t('Processing')} {isDeposit && t('deposit')}
|
||||
</h3>
|
||||
</ToastHeading>
|
||||
<p>
|
||||
{t('Your transaction has been completed.')}{' '}
|
||||
{isDeposit && t('Waiting for deposit confirmation.')}
|
||||
</p>
|
||||
<EtherscanLink tx={tx} />
|
||||
<EthTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isFinal = (tx: EthStoredTxState) =>
|
||||
[EthTxStatus.Confirmed, EthTxStatus.Error].includes(tx.status);
|
||||
|
||||
export const useEthereumTransactionToasts = () => {
|
||||
const ethTransactions = useEthTransactionStore((state) =>
|
||||
state.transactions.filter((transaction) => transaction?.dialogOpen)
|
||||
);
|
||||
const dismissEthTransaction = useEthTransactionStore(
|
||||
(state) => state.dismiss
|
||||
const [setToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.remove,
|
||||
]);
|
||||
|
||||
const [dismissTx, deleteTx] = useEthTransactionStore((state) => [
|
||||
state.dismiss,
|
||||
state.delete,
|
||||
]);
|
||||
|
||||
const onClose = useCallback(
|
||||
(tx: EthStoredTxState) => () => {
|
||||
const safeToDelete = isFinal(tx);
|
||||
if (safeToDelete) {
|
||||
deleteTx(tx.id);
|
||||
} else {
|
||||
dismissTx(tx.id);
|
||||
}
|
||||
removeToast(`eth-${tx.id}`);
|
||||
},
|
||||
[deleteTx, dismissTx, removeToast]
|
||||
);
|
||||
|
||||
const fromEthTransaction = useCallback(
|
||||
(tx: EthStoredTxState): Toast => {
|
||||
let content: ToastContent = <TransactionContent {...tx} />;
|
||||
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
|
||||
if (tx.status === EthTxStatus.Requested) {
|
||||
content = <EthTxRequestedToastContent tx={tx} />;
|
||||
}
|
||||
@@ -191,15 +217,21 @@ export const useEthereumTransactionToasts = () => {
|
||||
return {
|
||||
id: `eth-${tx.id}`,
|
||||
intent: intentMap[tx.status],
|
||||
onClose: () => dismissEthTransaction(tx.id),
|
||||
onClose: onClose(tx),
|
||||
loader: [EthTxStatus.Pending, EthTxStatus.Complete].includes(tx.status),
|
||||
content,
|
||||
closeAfter,
|
||||
};
|
||||
},
|
||||
[dismissEthTransaction]
|
||||
[onClose]
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
return [...compact(ethTransactions).map(fromEthTransaction)];
|
||||
}, [ethTransactions, fromEthTransaction]);
|
||||
useEthTransactionStore.subscribe(
|
||||
(state) => compact(state.transactions.filter((tx) => tx?.dialogOpen)),
|
||||
(txs) => {
|
||||
txs.forEach((tx) => {
|
||||
setToast(fromEthTransaction(tx));
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { formatNumber, t, toBigNum } from '@vegaprotocol/react-helpers';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
|
||||
import { useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ApprovalStatus, VerificationStatus } from '@vegaprotocol/withdraws';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import type { EthWithdrawalApprovalState } from '@vegaprotocol/web3';
|
||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
||||
@@ -30,49 +34,72 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
if (tx.status === ApprovalStatus.Delayed) {
|
||||
title = t('Delayed');
|
||||
}
|
||||
if (tx.status === ApprovalStatus.Ready) {
|
||||
title = t('Approved');
|
||||
}
|
||||
const num = formatNumber(
|
||||
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
|
||||
tx.withdrawal.asset.decimals
|
||||
);
|
||||
const details = (
|
||||
<div className="mt-[5px]">
|
||||
<span className="font-mono text-xs p-1 bg-gray-100 rounded">
|
||||
<Panel>
|
||||
<strong>
|
||||
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
|
||||
</span>
|
||||
</div>
|
||||
</strong>
|
||||
</Panel>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
{title.length > 0 && <h3 className="font-bold">{title}</h3>}
|
||||
<>
|
||||
{title.length > 0 && (
|
||||
<ToastHeading className="font-bold">{title}</ToastHeading>
|
||||
)}
|
||||
<VerificationStatus state={tx} />
|
||||
{details}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isFinal = (tx: EthWithdrawalApprovalState) =>
|
||||
[ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status);
|
||||
|
||||
export const useEthereumWithdrawApprovalsToasts = () => {
|
||||
const { withdrawApprovals, dismissWithdrawApproval } =
|
||||
useEthWithdrawApprovalsStore((state) => ({
|
||||
withdrawApprovals: state.transactions.filter(
|
||||
(transaction) => transaction?.dialogOpen
|
||||
),
|
||||
dismissWithdrawApproval: state.dismiss,
|
||||
}));
|
||||
const [setToast, remove] = useToasts((state) => [
|
||||
state.setToast,
|
||||
state.remove,
|
||||
]);
|
||||
const [dismissTx, deleteTx] = useEthWithdrawApprovalsStore((state) => [
|
||||
state.dismiss,
|
||||
state.delete,
|
||||
]);
|
||||
|
||||
const fromWithdrawalApproval = useCallback(
|
||||
(tx: EthWithdrawalApprovalState): Toast => ({
|
||||
id: `withdrawal-${tx.id}`,
|
||||
intent: intentMap[tx.status],
|
||||
onClose: () => dismissWithdrawApproval(tx.id),
|
||||
onClose: () => {
|
||||
if ([ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)) {
|
||||
deleteTx(tx.id);
|
||||
} else {
|
||||
dismissTx(tx.id);
|
||||
}
|
||||
remove(`withdrawal-${tx.id}`);
|
||||
},
|
||||
loader: tx.status === ApprovalStatus.Pending,
|
||||
content: <EthWithdrawalApprovalToastContent tx={tx} />,
|
||||
closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined,
|
||||
}),
|
||||
[dismissWithdrawApproval]
|
||||
[deleteTx, dismissTx, remove]
|
||||
);
|
||||
|
||||
const toasts = useMemo(() => {
|
||||
return [...compact(withdrawApprovals).map(fromWithdrawalApproval)];
|
||||
}, [fromWithdrawalApproval, withdrawApprovals]);
|
||||
|
||||
return toasts;
|
||||
useEthWithdrawApprovalsStore.subscribe(
|
||||
(state) =>
|
||||
compact(
|
||||
state.transactions.filter((transaction) => transaction?.dialogOpen)
|
||||
),
|
||||
(txs) => {
|
||||
txs.forEach((tx) => {
|
||||
setToast(fromWithdrawalApproval(tx));
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -134,23 +134,7 @@ const submitOrder: VegaStoredTxState = {
|
||||
type: OrderType.TYPE_MARKET,
|
||||
price: '1234',
|
||||
createdAt: new Date(),
|
||||
market: {
|
||||
id: 'market-1',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'M1',
|
||||
name: 'M1',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
decimals: 2,
|
||||
symbol: '$A',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
marketId: 'market-1',
|
||||
status: OrderStatus.STATUS_ACTIVE,
|
||||
},
|
||||
};
|
||||
@@ -181,23 +165,7 @@ const editOrder: VegaStoredTxState = {
|
||||
type: OrderType.TYPE_MARKET,
|
||||
price: '1234',
|
||||
createdAt: new Date(),
|
||||
market: {
|
||||
id: 'market-1',
|
||||
decimalPlaces: 2,
|
||||
positionDecimalPlaces: 2,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'M1',
|
||||
name: 'M1',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
decimals: 2,
|
||||
symbol: '$A',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
marketId: 'market-1',
|
||||
status: OrderStatus.STATUS_ACTIVE,
|
||||
},
|
||||
};
|
||||
@@ -292,7 +260,7 @@ describe('VegaTransactionDetails', () => {
|
||||
const { queryByTestId } = render(
|
||||
<VegaTransactionDetails tx={unsupportedTransaction} />
|
||||
);
|
||||
expect(queryByTestId('vega-tx-details')).toBeNull();
|
||||
expect(queryByTestId('toast-panel')).toBeNull();
|
||||
});
|
||||
it.each([
|
||||
{ tx: withdraw, details: 'Withdraw 12.34 $A' },
|
||||
@@ -307,6 +275,6 @@ describe('VegaTransactionDetails', () => {
|
||||
{ tx: batch, details: 'Batch market instruction' },
|
||||
])('display details for transaction', ({ tx, details }) => {
|
||||
const { queryByTestId } = render(<VegaTransactionDetails tx={tx} />);
|
||||
expect(queryByTestId('vega-tx-details')?.textContent).toEqual(details);
|
||||
expect(queryByTestId('toast-panel')?.textContent).toEqual(details);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import first from 'lodash/first';
|
||||
import compact from 'lodash/compact';
|
||||
import type {
|
||||
BatchMarketInstructionSubmissionBody,
|
||||
OrderAmendment,
|
||||
OrderBusEventFieldsFragment,
|
||||
OrderTxUpdateFieldsFragment,
|
||||
OrderCancellationBody,
|
||||
OrderSubmission,
|
||||
VegaStoredTxState,
|
||||
WithdrawalBusEventFieldsFragment,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { isBatchMarketInstructionsTransaction } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
isTransferTransaction,
|
||||
isBatchMarketInstructionsTransaction,
|
||||
ClientErrors,
|
||||
useReconnectVegaWallet,
|
||||
WalletError,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
isOrderAmendmentTransaction,
|
||||
isOrderCancellationTransaction,
|
||||
isOrderSubmissionTransaction,
|
||||
@@ -25,6 +24,10 @@ import {
|
||||
VegaTxStatus,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||
import { CLOSE_AFTER } from '@vegaprotocol/ui-toolkit';
|
||||
import { useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
@@ -32,14 +35,15 @@ import {
|
||||
Size,
|
||||
t,
|
||||
toBigNum,
|
||||
truncateByChars,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
|
||||
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
|
||||
import { getRejectionReason, useOrderByIdQuery } from '@vegaprotocol/orders';
|
||||
import { useMarketList } from '@vegaprotocol/market-list';
|
||||
import first from 'lodash/first';
|
||||
import type { Side } from '@vegaprotocol/types';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { OrderStatusMapping } from '@vegaprotocol/types';
|
||||
|
||||
const intentMap: { [s in VegaTxStatus]: Intent } = {
|
||||
@@ -78,29 +82,17 @@ const isTransactionTypeSupported = (tx: VegaStoredTxState) => {
|
||||
const cancelOrder = isOrderCancellationTransaction(tx.body);
|
||||
const editOrder = isOrderAmendmentTransaction(tx.body);
|
||||
const batchMarketInstructions = isBatchMarketInstructionsTransaction(tx.body);
|
||||
const transfer = isTransferTransaction(tx.body);
|
||||
return (
|
||||
withdraw ||
|
||||
submitOrder ||
|
||||
cancelOrder ||
|
||||
editOrder ||
|
||||
batchMarketInstructions
|
||||
batchMarketInstructions ||
|
||||
transfer
|
||||
);
|
||||
};
|
||||
|
||||
const Details = ({
|
||||
children,
|
||||
title = '',
|
||||
}: {
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
}) => (
|
||||
<div className="pt-[5px]" data-testid="vega-tx-details" title={title}>
|
||||
<div className="font-mono text-xs p-2 bg-neutral-100 rounded dark:bg-neutral-700 dark:text-white">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
type SizeAtPriceProps = {
|
||||
side: Side;
|
||||
size: string;
|
||||
@@ -130,12 +122,10 @@ const SubmitOrderDetails = ({
|
||||
order,
|
||||
}: {
|
||||
data: OrderSubmission;
|
||||
order?: OrderBusEventFieldsFragment;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const { data: markets } = useMarketList();
|
||||
const market = order
|
||||
? order.market
|
||||
: markets?.find((m) => m.id === data.marketId);
|
||||
const market = markets?.find((m) => m.id === order?.marketId);
|
||||
if (!market) return null;
|
||||
|
||||
const price = order ? order.price : data.price;
|
||||
@@ -143,8 +133,8 @@ const SubmitOrderDetails = ({
|
||||
const side = order ? order.side : data.side;
|
||||
|
||||
return (
|
||||
<Details>
|
||||
<h4 className="font-bold">
|
||||
<Panel>
|
||||
<h4>
|
||||
{order
|
||||
? t(
|
||||
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||
@@ -166,10 +156,7 @@ const SubmitOrderDetails = ({
|
||||
price={price}
|
||||
/>
|
||||
</p>
|
||||
{order && order.rejectionReason && (
|
||||
<p className="italic">{getRejectionReason(order)}</p>
|
||||
)}
|
||||
</Details>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -178,16 +165,17 @@ const EditOrderDetails = ({
|
||||
order,
|
||||
}: {
|
||||
data: OrderAmendment;
|
||||
order?: OrderBusEventFieldsFragment;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const { data: orderById } = useOrderByIdQuery({
|
||||
variables: { orderId: data.orderId },
|
||||
});
|
||||
const { data: markets } = useMarketList();
|
||||
|
||||
const originalOrder = orderById?.orderByID;
|
||||
const originalOrder = order || orderById?.orderByID;
|
||||
const marketId = order?.marketId || orderById?.orderByID.market.id;
|
||||
if (!originalOrder) return null;
|
||||
const market = markets?.find((m) => m.id === originalOrder.market.id);
|
||||
const market = markets?.find((m) => m.id === marketId);
|
||||
if (!market) return null;
|
||||
|
||||
const original = (
|
||||
@@ -219,8 +207,8 @@ const EditOrderDetails = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Details title={data.orderId}>
|
||||
<h4 className="font-bold">
|
||||
<Panel title={data.orderId}>
|
||||
<h4>
|
||||
{order
|
||||
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
|
||||
: t('Edit order')}
|
||||
@@ -230,10 +218,7 @@ const EditOrderDetails = ({
|
||||
<s>{original}</s>
|
||||
</p>
|
||||
<p>{edited}</p>
|
||||
{order && order.rejectionReason && (
|
||||
<p className="italic">{getRejectionReason(order)}</p>
|
||||
)}
|
||||
</Details>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -242,7 +227,7 @@ const CancelOrderDetails = ({
|
||||
order,
|
||||
}: {
|
||||
orderId: string;
|
||||
order?: OrderBusEventFieldsFragment;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const { data: orderById } = useOrderByIdQuery({
|
||||
variables: { orderId },
|
||||
@@ -268,8 +253,8 @@ const CancelOrderDetails = ({
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Details title={orderId}>
|
||||
<h4 className="font-bold">
|
||||
<Panel title={orderId}>
|
||||
<h4>
|
||||
{order
|
||||
? t(
|
||||
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||
@@ -280,10 +265,7 @@ const CancelOrderDetails = ({
|
||||
<p>
|
||||
<s>{original}</s>
|
||||
</p>
|
||||
{order && order.rejectionReason && (
|
||||
<p className="italic">{getRejectionReason(order)}</p>
|
||||
)}
|
||||
</Details>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -302,9 +284,11 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
asset.decimals
|
||||
);
|
||||
return (
|
||||
<Details>
|
||||
{t('Withdraw')} {num} {asset.symbol}
|
||||
</Details>
|
||||
<Panel>
|
||||
<strong>
|
||||
{t('Withdraw')} {num} {asset.symbol}
|
||||
</strong>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -321,7 +305,7 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
tx.body.orderCancellation.marketId === undefined &&
|
||||
tx.body.orderCancellation.orderId === undefined
|
||||
) {
|
||||
return <Details>{t('Cancel all orders')}</Details>;
|
||||
return <Panel>{t('Cancel all orders')}</Panel>;
|
||||
}
|
||||
|
||||
// CANCEL
|
||||
@@ -344,11 +328,15 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
m.id === (tx.body as OrderCancellationBody).orderCancellation.marketId
|
||||
)?.tradableInstrument.instrument.code;
|
||||
return (
|
||||
<Details>
|
||||
{marketName
|
||||
? `${t('Cancel all orders for')} ${marketName}`
|
||||
: t('Cancel all orders')}
|
||||
</Details>
|
||||
<Panel>
|
||||
{marketName ? (
|
||||
<>
|
||||
{t('Cancel all orders for')} <strong>{marketName}</strong>
|
||||
</>
|
||||
) : (
|
||||
t('Cancel all orders')
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -370,15 +358,36 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
const market = marketId && markets?.find((m) => m.id === marketId);
|
||||
if (market) {
|
||||
return (
|
||||
<Details>
|
||||
{t('Close position for')} {market.tradableInstrument.instrument.code}
|
||||
</Details>
|
||||
<Panel>
|
||||
{t('Close position for')}{' '}
|
||||
<strong>{market.tradableInstrument.instrument.code}</strong>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBatchMarketInstructionsTransaction(tx.body)) {
|
||||
return <Details>{t('Batch market instruction')}</Details>;
|
||||
return <Panel>{t('Batch market instruction')}</Panel>;
|
||||
}
|
||||
|
||||
if (isTransferTransaction(tx.body)) {
|
||||
const { amount, to, asset } = tx.body.transfer;
|
||||
const transferAsset = assets?.find((a) => a.id === asset);
|
||||
// only render if we have an asset to avoid unformatted amounts showing
|
||||
if (transferAsset) {
|
||||
const value = addDecimalsFormatNumber(amount, transferAsset.decimals);
|
||||
return (
|
||||
<Panel>
|
||||
<h4>{t('Transfer')}</h4>
|
||||
<p>
|
||||
{t('To')} {truncateByChars(to)}
|
||||
</p>
|
||||
<p>
|
||||
{value} {transferAsset.symbol}
|
||||
</p>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -387,22 +396,22 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
type VegaTxToastContentProps = { tx: VegaStoredTxState };
|
||||
|
||||
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Action required')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Action required')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Please go to your Vega wallet application and approve or reject the transaction.'
|
||||
)}
|
||||
</p>
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
|
||||
const explorerLink = useLinks(DApp.Explorer);
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Awaiting confirmation')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
|
||||
<p>{t('Please wait for your transaction to be confirmed')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
@@ -415,7 +424,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
|
||||
</p>
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -426,9 +435,10 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
})
|
||||
);
|
||||
const explorerLink = useLinks(DApp.Explorer);
|
||||
|
||||
if (isWithdrawTransaction(tx.body)) {
|
||||
const completeWithdrawalButton = tx.withdrawal && (
|
||||
<div className="mt-[10px]">
|
||||
<p className="mt-1">
|
||||
<Button
|
||||
data-testid="toast-complete-withdrawal"
|
||||
size="xs"
|
||||
@@ -441,11 +451,11 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
>
|
||||
{t('Complete withdrawal')}
|
||||
</Button>
|
||||
</div>
|
||||
</p>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Funds unlocked')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
|
||||
<p>{t('Your funds have been unlocked for withdrawal')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
@@ -459,13 +469,68 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
{completeWithdrawalButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (tx.order && tx.order.rejectionReason) {
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Order rejected')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Your order has been rejected because: %s',
|
||||
getRejectionReason(tx.order) || ''
|
||||
)}
|
||||
</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
<ExternalLink
|
||||
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('View in block explorer')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Order rejected')}</h3>
|
||||
<p>{t('Your order was rejected.')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
<ExternalLink
|
||||
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('View in block explorer')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isTransferTransaction(tx.body)) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Transfer complete')}</h3>
|
||||
<p>{t('Your transaction has been confirmed ')}</p>
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Confirmed')}</h3>
|
||||
<>
|
||||
<ToastHeading>{t('Confirmed')}</ToastHeading>
|
||||
<p>{t('Your transaction has been confirmed ')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
@@ -478,7 +543,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
</p>
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -501,7 +566,10 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
walletNoConnectionCodes.includes(tx.error.code);
|
||||
if (orderRejection) {
|
||||
label = t('Order rejected');
|
||||
errorMessage = orderRejection;
|
||||
errorMessage = t(
|
||||
'Your order has been rejected because: %s',
|
||||
orderRejection
|
||||
);
|
||||
}
|
||||
if (walletError) {
|
||||
label = t('Wallet disconnected');
|
||||
@@ -509,60 +577,87 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{label}</h3>
|
||||
<p>{errorMessage}</p>
|
||||
<>
|
||||
<ToastHeading>{label}</ToastHeading>
|
||||
<p className="first-letter:uppercase">{errorMessage}</p>
|
||||
{walletError && (
|
||||
<Button size="xs" onClick={reconnectVegaWallet}>
|
||||
{t('Connect vega wallet')}
|
||||
</Button>
|
||||
)}
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isFinal = (tx: VegaStoredTxState) =>
|
||||
[VegaTxStatus.Error, VegaTxStatus.Complete].includes(tx.status);
|
||||
|
||||
export const useVegaTransactionToasts = () => {
|
||||
const vegaTransactions = useVegaTransactionStore((state) =>
|
||||
state.transactions.filter((transaction) => transaction?.dialogOpen)
|
||||
);
|
||||
const dismissVegaTransaction = useVegaTransactionStore(
|
||||
(state) => state.dismiss
|
||||
);
|
||||
const [setToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.remove,
|
||||
]);
|
||||
|
||||
const fromVegaTransaction = useCallback(
|
||||
(tx: VegaStoredTxState): Toast => {
|
||||
let content: ToastContent;
|
||||
if (tx.status === VegaTxStatus.Requested) {
|
||||
content = <VegaTxRequestedToastContent tx={tx} />;
|
||||
const [dismissTx, deleteTx] = useVegaTransactionStore((state) => [
|
||||
state.dismiss,
|
||||
state.delete,
|
||||
]);
|
||||
|
||||
const onClose = useCallback(
|
||||
(tx: VegaStoredTxState) => () => {
|
||||
const safeToDelete = isFinal(tx);
|
||||
if (safeToDelete) {
|
||||
deleteTx(tx.id);
|
||||
} else {
|
||||
dismissTx(tx.id);
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Pending) {
|
||||
content = <VegaTxPendingToastContentProps tx={tx} />;
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Complete) {
|
||||
content = <VegaTxCompleteToastsContent tx={tx} />;
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Error) {
|
||||
content = <VegaTxErrorToastContent tx={tx} />;
|
||||
}
|
||||
return {
|
||||
id: `vega-${tx.id}`,
|
||||
intent: intentMap[tx.status],
|
||||
onClose: () => dismissVegaTransaction(tx.id),
|
||||
loader: tx.status === VegaTxStatus.Pending,
|
||||
content,
|
||||
};
|
||||
removeToast(`vega-${tx.id}`);
|
||||
},
|
||||
[dismissVegaTransaction]
|
||||
[deleteTx, dismissTx, removeToast]
|
||||
);
|
||||
|
||||
const toasts = useMemo(() => {
|
||||
return [
|
||||
...compact(vegaTransactions)
|
||||
.filter((tx) => isTransactionTypeSupported(tx))
|
||||
.map(fromVegaTransaction),
|
||||
];
|
||||
}, [fromVegaTransaction, vegaTransactions]);
|
||||
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
|
||||
let content: ToastContent;
|
||||
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
|
||||
if (tx.status === VegaTxStatus.Requested) {
|
||||
content = <VegaTxRequestedToastContent tx={tx} />;
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Pending) {
|
||||
content = <VegaTxPendingToastContentProps tx={tx} />;
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Complete) {
|
||||
content = <VegaTxCompleteToastsContent tx={tx} />;
|
||||
}
|
||||
if (tx.status === VegaTxStatus.Error) {
|
||||
content = <VegaTxErrorToastContent tx={tx} />;
|
||||
}
|
||||
|
||||
return toasts;
|
||||
// Transaction can be successful but the order can be rejected by the network
|
||||
const intent =
|
||||
tx.order && [OrderStatus.STATUS_REJECTED].includes(tx.order.status)
|
||||
? Intent.Danger
|
||||
: intentMap[tx.status];
|
||||
|
||||
return {
|
||||
id: `vega-${tx.id}`,
|
||||
intent,
|
||||
onClose: onClose(tx),
|
||||
loader: tx.status === VegaTxStatus.Pending,
|
||||
content,
|
||||
closeAfter,
|
||||
};
|
||||
};
|
||||
|
||||
useVegaTransactionStore.subscribe(
|
||||
(state) =>
|
||||
compact(
|
||||
state.transactions.filter(
|
||||
(tx) => tx?.dialogOpen && isTransactionTypeSupported(tx)
|
||||
)
|
||||
),
|
||||
(txs) => {
|
||||
txs.forEach((tx) => setToast(fromVegaTransaction(tx)));
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,6 +33,8 @@ import ToastsManager from './toasts-manager';
|
||||
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { Banner } from '../components/banner';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -70,6 +72,11 @@ function AppBody({ Component }: AppProps) {
|
||||
const location = useLocation();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid-rows-[repeat(3,min-content),1fr,min-content]'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full dark:bg-black dark:text-white">
|
||||
<Head>
|
||||
@@ -80,20 +87,21 @@ function AppBody({ Component }: AppProps) {
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<Web3Provider>
|
||||
<div className="h-full relative z-0 grid grid-rows-[min-content,min-content,1fr,min-content]">
|
||||
<div className={gridClasses}>
|
||||
<Navbar
|
||||
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
|
||||
/>
|
||||
<Banner />
|
||||
<ViewingBanner />
|
||||
<main data-testid={location.pathname}>
|
||||
<Component />
|
||||
</main>
|
||||
<Footer />
|
||||
<DialogsContainer />
|
||||
<ToastsManager />
|
||||
<TransactionsHandler />
|
||||
<MaybeConnectEagerly />
|
||||
</div>
|
||||
<DialogsContainer />
|
||||
<ToastsManager />
|
||||
<TransactionsHandler />
|
||||
<MaybeConnectEagerly />
|
||||
</Web3Provider>
|
||||
</AppLoader>
|
||||
</VegaWalletProvider>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user