Compare commits

...
Author SHA1 Message Date
Edd 86d234b6eb test(explorer): fix capitalisation of test value 2023-02-08 10:34:58 +00:00
Edd 587065beb4 chore(explorer): remove accidental changes to shared lib 2023-02-08 10:34:58 +00:00
Edd 3a1522bbf5 test(explorer): finish tests for risk factors 2023-02-08 10:34:57 +00:00
Edd fc0ae5478e test(explorer): add tests for state variable views 2023-02-08 10:34:57 +00:00
Edd cf89c4f902 feat(explorer): monospace state variable numbers 2023-02-08 10:34:57 +00:00
Edd 5e9009eaf1 feat(explorer): all known state variables render meaningfully 2023-02-08 10:34:57 +00:00
Edd 7f77fceeab feat(explorer): basic state variable proposal view 2023-02-08 10:34:55 +00:00
Sam Keen da551f9d3c feat(governance): replace proposal and vote busEvents with subscriptions (#2862) 2023-02-08 09:42:20 +00:00
Edd 47e4861fdb fix(explorer): use position decimal places to format size in order (#2861) 2023-02-08 08:22:27 +00:00
mattrussell36 1a85a89440 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 06:06:13 +00:00
mattrussell36 7ab412a8f7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 00:12:40 +00:00
Edd 5a14174a81 feat(explorer): add issuesignatures tx view (#2867) 2023-02-07 18:17:00 +00:00
mattrussell36 5e00b93783 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 18:06:49 +00:00
Edd 31d9b023e6 feat(explorer): change party to pubkey (#2868) 2023-02-07 18:00:26 +00:00
mattrussell36 103e503a47 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 12:07:30 +00:00
Maciek 1952cb0e78 fix(trading): inform the user his connection to wallet is lost (#2863) 2023-02-07 11:31:15 +01:00
mattrussell36 69340f4ddf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 06:06:15 +00:00
mattrussell36 c52bf2200e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 00:10:59 +00:00
Matthew Russell 8dccee69f2 chore(trading): remove unnecessary sentry capture from asyncrenderer component (#2869) 2023-02-06 15:21:39 -08:00
Art c7a6fdd879 chore(trading): new toasts designs (#2779) 2023-02-06 20:09:56 +00:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
127 changed files with 3531 additions and 752 deletions
+2 -2
View File
@@ -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
@@ -2,6 +2,7 @@ query ExplorerMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -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: {
@@ -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">
@@ -20,6 +20,8 @@ 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;
@@ -67,6 +69,8 @@ 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':
@@ -99,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}
@@ -37,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,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
+408 -74
View File
@@ -65,13 +65,46 @@
}
]
},
{
"tranche_id": 52,
"tranche_start": "2024-02-01T00:00:00.000Z",
"tranche_end": "2024-08-01T00:00:00.000Z",
"total_added": "7500",
"total_removed": "0",
"locked_amount": "7500",
"deposits": [
{
"amount": "7500",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"users": [
{
"address": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"deposits": [
{
"amount": "7500",
"user": "0xA530ac2B576eF27C09B7b55C83b3E163D167e9AE",
"tranche_id": 52,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
}
]
},
{
"tranche_id": 49,
"tranche_start": "2022-12-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "72240.7528737295524210837",
"locked_amount": "71172.1900799821150969076",
"deposits": [
{
"amount": "86666.297",
@@ -137,7 +170,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1610.5220734126985",
"locked_amount": "1548.704594017094",
"deposits": [
{
"amount": "2500",
@@ -331,14 +364,24 @@
"tranche_id": 42,
"tranche_start": "2023-07-01T00:00:00.000Z",
"tranche_end": "2024-01-01T00:00:00.000Z",
"total_added": "2500",
"total_added": "17500",
"total_removed": "0",
"locked_amount": "2500",
"locked_amount": "17500",
"deposits": [
{
"amount": "2500",
"user": "0x6ad69157EfB1E17EBf6614D352D098589EaE3060",
"tx": "0x0e229459260c2e74579e12e05ffd21cb4e470e3a4eacf85b7f924778b4f1c8a6"
},
{
"amount": "7500",
"user": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
},
{
"amount": "7500",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
@@ -357,6 +400,36 @@
"total_tokens": "2500",
"withdrawn_tokens": "0",
"remaining_tokens": "2500"
},
{
"address": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"deposits": [
{
"amount": "7500",
"user": "0xd481bCC42265089abEA41Efdb655EDCF8fF9068c",
"tranche_id": 42,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
},
{
"address": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"deposits": [
{
"amount": "7500",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 42,
"tx": "0x713ef0adf9a54def857e76c39b10651c5b466296953126cc4661338ea42793fd"
}
],
"withdrawals": [],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
}
]
},
@@ -484,8 +557,8 @@
"tranche_start": "2023-02-01T00:00:00.000Z",
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "0",
"locked_amount": "36929.42132826887625",
"total_removed": "183.137181525",
"locked_amount": "35997.0361418047875",
"deposits": [
{
"amount": "7500",
@@ -498,7 +571,13 @@
"tx": "0x302591debd812f93121a17dd0413ae5084f3743a868b4325f81990eac58f8292"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "183.137181525",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x326ded5446d14472f79d487ece43dd7db760fec5df52e310342930db38fb5de1"
}
],
"users": [
{
"address": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -510,10 +589,17 @@
"tx": "0x1a8578ff6c2531d666b92690b7fc02e0512588dae82a44a163f25010224b6272"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "183.137181525",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 34,
"tx": "0x326ded5446d14472f79d487ece43dd7db760fec5df52e310342930db38fb5de1"
}
],
"total_tokens": "7500",
"withdrawn_tokens": "0",
"remaining_tokens": "7500"
"withdrawn_tokens": "183.137181525",
"remaining_tokens": "7316.862818475"
},
{
"address": "0x0B4e6fcE839B01ef43DA6F890FAC7B1Afb004600",
@@ -538,7 +624,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "72174.84227641233489699",
"locked_amount": "71107.254411762464072205",
"deposits": [
{
"amount": "129999.45",
@@ -604,7 +690,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "36230.13708143074368",
"locked_amount": "35458.30266362252656",
"deposits": [
{
"amount": "10000",
@@ -797,7 +883,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "3085.5617389649925",
"locked_amount": "3023.913622526636",
"deposits": [
{
"amount": "5000",
@@ -1008,7 +1094,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "14018.9985053947849677588",
"locked_amount": "12971.7942604813033430448",
"deposits": [
{
"amount": "97499.58",
@@ -1041,7 +1127,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "98230.390980249184455396",
"locked_amount": "19161.550677751127587504116564",
"locked_amount": "17730.203267225049376980811848",
"deposits": [
{
"amount": "135173.4239508",
@@ -1087,7 +1173,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "5897.550576064675833666",
"locked_amount": "5457.0098344795841878884",
"deposits": [
{
"amount": "32499.86",
@@ -1120,7 +1206,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "1919.5954840328841511976",
"locked_amount": "1776.2037475531047139428",
"deposits": [
{
"amount": "10833.29",
@@ -1153,7 +1239,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "0",
"locked_amount": "7175.876653223167891302",
"locked_amount": "6639.846316295812322976",
"deposits": [
{
"amount": "6500",
@@ -1291,8 +1377,8 @@
"tranche_start": "2022-11-01T00:00:00.000Z",
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "3853.26264195",
"locked_amount": "10721.188708563537",
"total_removed": "3995.28612255",
"locked_amount": "10161.75759668508375",
"deposits": [
{
"amount": "7500",
@@ -1316,6 +1402,11 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x702e76a6868be327dcce3416fa163759a406b68600063473106afdb555f03db4"
},
{
"amount": "142.0234806",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x3f253311d975a353930c10981b742ac3b0df52da085d437244f077b63ec32953"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1391,6 +1482,12 @@
"tranche_id": 33,
"tx": "0x702e76a6868be327dcce3416fa163759a406b68600063473106afdb555f03db4"
},
{
"amount": "142.0234806",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 33,
"tx": "0x3f253311d975a353930c10981b742ac3b0df52da085d437244f077b63ec32953"
},
{
"amount": "305.3119245",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -1453,8 +1550,8 @@
}
],
"total_tokens": "7500",
"withdrawn_tokens": "3853.26264195",
"remaining_tokens": "3646.73735805"
"withdrawn_tokens": "3995.28612255",
"remaining_tokens": "3504.71387745"
},
{
"address": "0x2539b51EbDE65a75672aBcfE9439a706a99D18D1",
@@ -1479,7 +1576,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "928642.9598472029154",
"locked_amount": "628462.4238169996415687432",
"locked_amount": "604543.837686537201986533",
"deposits": [
{
"amount": "1852091.69",
@@ -2779,7 +2876,7 @@
"tranche_start": "2021-09-05T00:00:00.000Z",
"tranche_end": "2022-09-30T00:00:00.000Z",
"total_added": "60916.66666633337",
"total_removed": "34173.43587066019652061",
"total_removed": "39088.634764730198755268",
"locked_amount": "0",
"deposits": [
{
@@ -2879,6 +2976,16 @@
}
],
"withdrawals": [
{
"amount": "3395.631843574",
"user": "0xa0fF757077B5D796259582b2b9Db99c906277007",
"tx": "0x335be76fc5ff37d4272a624f47822e6163d395e0d8a3a9cfcbcc5f7207239361"
},
{
"amount": "1519.567050496002234658",
"user": "0x6E14D4e15E245d6ECfe3181B08c2FF30d2c1Da19",
"tx": "0x3d0b5ec97be95410571cbc03dd4edd1406243cafbafc40d7c4740f88287efcd3"
},
{
"amount": "1366.363766241335342692",
"user": "0xef633C319801eB899354030DFF2BBD0024C0b39c",
@@ -3509,6 +3616,12 @@
}
],
"withdrawals": [
{
"amount": "1519.567050496002234658",
"user": "0x6E14D4e15E245d6ECfe3181B08c2FF30d2c1Da19",
"tranche_id": 13,
"tx": "0x3d0b5ec97be95410571cbc03dd4edd1406243cafbafc40d7c4740f88287efcd3"
},
{
"amount": "747.099616170667765342",
"user": "0x6E14D4e15E245d6ECfe3181B08c2FF30d2c1Da19",
@@ -3517,8 +3630,8 @@
}
],
"total_tokens": "2266.66666666667",
"withdrawn_tokens": "747.099616170667765342",
"remaining_tokens": "1519.567050496002234658"
"withdrawn_tokens": "2266.66666666667",
"remaining_tokens": "0"
},
{
"address": "0xb14025f7eB7717cFF43e7a33b66d86eaBd6bC7d7",
@@ -3561,6 +3674,12 @@
}
],
"withdrawals": [
{
"amount": "3395.631843574",
"user": "0xa0fF757077B5D796259582b2b9Db99c906277007",
"tranche_id": 13,
"tx": "0x335be76fc5ff37d4272a624f47822e6163d395e0d8a3a9cfcbcc5f7207239361"
},
{
"amount": "4.368156426",
"user": "0xa0fF757077B5D796259582b2b9Db99c906277007",
@@ -3569,8 +3688,8 @@
}
],
"total_tokens": "3400",
"withdrawn_tokens": "4.368156426",
"remaining_tokens": "3395.631843574"
"withdrawn_tokens": "3400",
"remaining_tokens": "0"
},
{
"address": "0x2D69BAB9738b05048be16DE3E5E0A945b8EeEf3a",
@@ -33440,7 +33559,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "589730.8667090699299",
"locked_amount": "990229.262759807396386176935",
"locked_amount": "953474.62891778338628861474",
"deposits": [
{
"amount": "1998.95815",
@@ -34754,8 +34873,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "554892.28455460542150452",
"locked_amount": "8810976.9725916725460603705886099833292782",
"total_removed": "560317.67585805941693952",
"locked_amount": "8680647.4008609011096496602104470331211138",
"deposits": [
{
"amount": "16249.93",
@@ -35304,6 +35423,31 @@
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0x0817f7d1dd8b4931d3d72c2200528a68931366595a0ab42c1f02506552ae9f02"
},
{
"amount": "983.56229747023875",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xcc454df031cba4b8a902fb6751d904e19e53a12263d500dcfe3c0bea079c9de8"
},
{
"amount": "730.09381677206056",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0xb0d4e11c4f5aab1c4c65994c14d5272ecb4df9972ddee36ae5389de3731a3e35"
},
{
"amount": "527.364937076912",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xa344428c2b1bf9b4685959441983da46bcd434ac0ee5ada325876c8733eba603"
},
{
"amount": "2667.021494",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
"tx": "0x94fe358ee7c973dd1d28ca4c56250dfaccbcb718673872c8e9cf8651a22a1108"
},
{
"amount": "517.348758134784125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0xb6a33b8b8855789fbc2d0f9545ba5c2258e52f143d90bc75bbe2411bd2f9b4a5"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -36911,6 +37055,24 @@
"tranche_id": 2,
"tx": "0xe01c0b55950dd8fbc43b42e45e486ae30b2114869a93294f66a0395d9700a157"
},
{
"amount": "983.56229747023875",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xcc454df031cba4b8a902fb6751d904e19e53a12263d500dcfe3c0bea079c9de8"
},
{
"amount": "527.364937076912",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xa344428c2b1bf9b4685959441983da46bcd434ac0ee5ada325876c8733eba603"
},
{
"amount": "517.348758134784125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0xb6a33b8b8855789fbc2d0f9545ba5c2258e52f143d90bc75bbe2411bd2f9b4a5"
},
{
"amount": "858.360074993579125",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -37999,8 +38161,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "115455.816383410294",
"remaining_tokens": "144543.071116589706"
"withdrawn_tokens": "117484.092376092228875",
"remaining_tokens": "142514.795123907771125"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -38239,6 +38401,12 @@
"tranche_id": 2,
"tx": "0x0817f7d1dd8b4931d3d72c2200528a68931366595a0ab42c1f02506552ae9f02"
},
{
"amount": "730.09381677206056",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0xb0d4e11c4f5aab1c4c65994c14d5272ecb4df9972ddee36ae5389de3731a3e35"
},
{
"amount": "1293.67099136315494",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -38439,8 +38607,8 @@
}
],
"total_tokens": "150551.801",
"withdrawn_tokens": "66942.28125411332103",
"remaining_tokens": "83609.51974588667897"
"withdrawn_tokens": "67672.37507088538159",
"remaining_tokens": "82879.42592911461841"
},
{
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
@@ -38648,6 +38816,12 @@
"tranche_id": 2,
"tx": "0x606ddaa3882cccb0062bc2827cfcfceb64cef8a6c4df41d06747ae651e5dd52e"
},
{
"amount": "2667.021494",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
"tranche_id": 2,
"tx": "0x94fe358ee7c973dd1d28ca4c56250dfaccbcb718673872c8e9cf8651a22a1108"
},
{
"amount": "1099.300488",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
@@ -38830,8 +39004,8 @@
}
],
"total_tokens": "200000",
"withdrawn_tokens": "87428.726512",
"remaining_tokens": "112571.273488"
"withdrawn_tokens": "90095.748006",
"remaining_tokens": "109904.251994"
},
{
"address": "0x1b956E6c00E238194B331eddEFF72Cb5f28A8d01",
@@ -40177,8 +40351,8 @@
"tranche_start": "2021-11-05T00:00:00.000Z",
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3918895.392626520786315032",
"locked_amount": "2412792.456187176914589231922233303",
"total_removed": "3921707.576409189114087282",
"locked_amount": "2292473.027727501476908220604423147",
"deposits": [
{
"amount": "129284.449",
@@ -40427,6 +40601,21 @@
"user": "0x85E14d90C63f70421a098AC26a5aCaB3854a1ee8",
"tx": "0x188c440997a39f63e688188099b8ac5ab0829e77386de239026f0152f2de4f78"
},
{
"amount": "1363.61315668407184225",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xf85cd1afadb4ab0505bd1a1c7accfaf89e466772a241ee0990deb678334bc27b"
},
{
"amount": "731.091047499105007",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0xc73c47dcfac4093bb6325fe92986dd010d5a773af4bb97e188ad57359a309a52"
},
{
"amount": "717.479578485150923",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tx": "0x0c292f2355b5cdc3b389ed35e6e138e593b1db9d91fcd58f132835343724bf90"
},
{
"amount": "8950.14985089483210984",
"user": "0x66827bCD635f2bB1779d68c46aEB16541bCA6ba8",
@@ -43252,6 +43441,24 @@
"tranche_id": 3,
"tx": "0x2f991e6e03148ea102fe9c5586aa0e652e2dcc0b92a4db18edd58ba1a52eb00d"
},
{
"amount": "1363.61315668407184225",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xf85cd1afadb4ab0505bd1a1c7accfaf89e466772a241ee0990deb678334bc27b"
},
{
"amount": "731.091047499105007",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0xc73c47dcfac4093bb6325fe92986dd010d5a773af4bb97e188ad57359a309a52"
},
{
"amount": "717.479578485150923",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
"tranche_id": 3,
"tx": "0x0c292f2355b5cdc3b389ed35e6e138e593b1db9d91fcd58f132835343724bf90"
},
{
"amount": "1192.05386354121365675",
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
@@ -45642,8 +45849,8 @@
}
],
"total_tokens": "359123.469575",
"withdrawn_tokens": "299497.1998559720857145",
"remaining_tokens": "59626.2697190279142855"
"withdrawn_tokens": "302309.38363864041348675",
"remaining_tokens": "56814.08593635958651325"
},
{
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
@@ -46980,8 +47187,8 @@
"tranche_start": "2021-10-05T00:00:00.000Z",
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2720584.477569657792162642",
"locked_amount": "636405.488346656758325570814427566",
"total_removed": "2730068.739915456784546642",
"locked_amount": "588866.554476394378569370609352176",
"deposits": [
{
"amount": "552496.6455",
@@ -47155,6 +47362,16 @@
"user": "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D",
"tx": "0x47eb4c897187630f558845180b019d4c5c1816efd91abf3db4964ed14eea5efd"
},
{
"amount": "5240.955854504870917",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
"tx": "0x936296f4bc21595778c766441b849f6f5a53f5b52fded7866703d276f137ff10"
},
{
"amount": "4243.306491294121467",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
"tx": "0x3dee2b22695a6bd2cf3925945ecac7b84cb3cf5b65113fc8af355a8fcce3c49d"
},
{
"amount": "3634.58269967002683",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
@@ -48040,6 +48257,12 @@
}
],
"withdrawals": [
{
"amount": "5240.955854504870917",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
"tranche_id": 4,
"tx": "0x936296f4bc21595778c766441b849f6f5a53f5b52fded7866703d276f137ff10"
},
{
"amount": "11863.408335178645863",
"user": "0xBc934494675a6ceB639B9EfEe5b9C0f017D35a75",
@@ -48132,8 +48355,8 @@
}
],
"total_tokens": "110499.5291",
"withdrawn_tokens": "93566.506422699926443",
"remaining_tokens": "16933.022677300073557"
"withdrawn_tokens": "98807.46227720479736",
"remaining_tokens": "11692.06682279520264"
},
{
"address": "0xdbC5d439F373EB646345e1c67D1d46231ACE7dD3",
@@ -48615,6 +48838,12 @@
"tranche_id": 4,
"tx": "0x687f74292db1a9d0c79bedca56eb5dd57d5d338a2d536b355cd2d1022dc4889b"
},
{
"amount": "4243.306491294121467",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
"tranche_id": 4,
"tx": "0x3dee2b22695a6bd2cf3925945ecac7b84cb3cf5b65113fc8af355a8fcce3c49d"
},
{
"amount": "3634.58269967002683",
"user": "0xafa64cCa337eFEE0AD827F6C2684e69275226e90",
@@ -48845,8 +49074,8 @@
}
],
"total_tokens": "331498.5873",
"withdrawn_tokens": "292494.46862336024442",
"remaining_tokens": "39004.11867663975558"
"withdrawn_tokens": "296737.775114654365887",
"remaining_tokens": "34760.812185345634113"
},
{
"address": "0x16da609341ed67750A8BCC5AAa2005471006Cd77",
@@ -48941,8 +49170,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "31923.1301021472685",
"locked_amount": "156907.48707607482461837162252664",
"total_removed": "32476.7515062512685",
"locked_amount": "151083.50524536200128704962151192",
"deposits": [
{
"amount": "3000",
@@ -55581,6 +55810,36 @@
"user": "0x7C8D2D8BcFffcD48dBcb65C5Bb7588B66dcD22bb",
"tx": "0x2883648c0fa0f5a27defb406ab22bc0120d43e3a98b16fd7a97d99f9e79dc24c"
},
{
"amount": "133.62069381",
"user": "0xfFdd56a550Cd388071a485f9331c46BeF652eC28",
"tx": "0xd3f93705d1ddd14d480fa9e298d4f942efe21feebba8a64d1ba5bd2d01c8ceb3"
},
{
"amount": "133.64984145",
"user": "0x3616Dc96aBA2113B5E96E3B27fA61E0F0444cDe0",
"tx": "0x45a4a7b7d6ba5248f574539e60389d8f758ac92791d8e7ac1d6c6f8fbc0b9e9c"
},
{
"amount": "133.654027142",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tx": "0xcf9db8b982712a77cec727a423116897853a3180515f285d07ecc16c1d7e1634"
},
{
"amount": "37.412024352",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
"tx": "0x6df8666e49f7d491c740d6a36df031aac232a6cf37fa197aa1f6b2bb481be99c"
},
{
"amount": "99.47735921",
"user": "0x2E32F49389CF3039ab6365ca59329002922Cc01D",
"tx": "0x7de3c1703d23970f04ce1bebe2cece8a2fe3fbe6b876502da1812c312151b954"
},
{
"amount": "15.80745814",
"user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A",
"tx": "0xc0f953d7ecc45bfc1d7b69ecd491a63e8b00b0d732ff006bee008e917a04179f"
},
{
"amount": "78.261187214",
"user": "0x479F059c46c6873C6B970A92911084b3eA036d56",
@@ -62468,10 +62727,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.64984145",
"user": "0x3616Dc96aBA2113B5E96E3B27fA61E0F0444cDe0",
"tranche_id": 5,
"tx": "0x45a4a7b7d6ba5248f574539e60389d8f758ac92791d8e7ac1d6c6f8fbc0b9e9c"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.64984145",
"remaining_tokens": "66.35015855"
},
{
"address": "0x55795BCA099a03A3619447c351BBd1f4E20Ee15f",
@@ -62498,10 +62764,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.62069381",
"user": "0xfFdd56a550Cd388071a485f9331c46BeF652eC28",
"tranche_id": 5,
"tx": "0xd3f93705d1ddd14d480fa9e298d4f942efe21feebba8a64d1ba5bd2d01c8ceb3"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.62069381",
"remaining_tokens": "66.37930619"
},
{
"address": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
@@ -62513,10 +62786,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "133.654027142",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tranche_id": 5,
"tx": "0xcf9db8b982712a77cec727a423116897853a3180515f285d07ecc16c1d7e1634"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "133.654027142",
"remaining_tokens": "66.345972858"
},
{
"address": "0x31410FE9199081BAA0a119E2c78Ecd42ca5130ce",
@@ -67815,6 +68095,12 @@
}
],
"withdrawals": [
{
"amount": "99.47735921",
"user": "0x2E32F49389CF3039ab6365ca59329002922Cc01D",
"tranche_id": 5,
"tx": "0x7de3c1703d23970f04ce1bebe2cece8a2fe3fbe6b876502da1812c312151b954"
},
{
"amount": "35.842421358",
"user": "0x2E32F49389CF3039ab6365ca59329002922Cc01D",
@@ -67823,8 +68109,8 @@
}
],
"total_tokens": "200",
"withdrawn_tokens": "35.842421358",
"remaining_tokens": "164.157578642"
"withdrawn_tokens": "135.319780568",
"remaining_tokens": "64.680219432"
},
{
"address": "0x2F2588aCd44253312b4A94bF6753bE67514A5Cc6",
@@ -68899,6 +69185,12 @@
}
],
"withdrawals": [
{
"amount": "15.80745814",
"user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A",
"tranche_id": 5,
"tx": "0xc0f953d7ecc45bfc1d7b69ecd491a63e8b00b0d732ff006bee008e917a04179f"
},
{
"amount": "32.761796044",
"user": "0x4eD2b3c68BB4fda084ce1591a210F4aC8b71234A",
@@ -68973,8 +69265,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "254.83529934",
"remaining_tokens": "145.16470066"
"withdrawn_tokens": "270.64275748",
"remaining_tokens": "129.35724252"
},
{
"address": "0x5c90765F50629570738fEe7b7FA82ae118f81Ed1",
@@ -72689,6 +72981,12 @@
}
],
"withdrawals": [
{
"amount": "37.412024352",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
"tranche_id": 5,
"tx": "0x6df8666e49f7d491c740d6a36df031aac232a6cf37fa197aa1f6b2bb481be99c"
},
{
"amount": "32.098173516",
"user": "0x0e199b123f71f964d6567869B6B849C1f255A855",
@@ -72739,8 +73037,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "232.628602232",
"remaining_tokens": "167.371397768"
"withdrawn_tokens": "270.040626584",
"remaining_tokens": "129.959373416"
},
{
"address": "0x74Da54F44975a1C224ABb5CAB8e5fc38a268B425",
@@ -77473,7 +77771,7 @@
"tranche_start": "2021-12-05T00:00:00.000Z",
"tranche_end": "2022-06-05T00:00:00.000Z",
"total_added": "171288.42",
"total_removed": "64726.1049690697989",
"total_removed": "65326.1049690697989",
"locked_amount": "0",
"deposits": [
{
@@ -81713,6 +82011,21 @@
"user": "0x68091918EaDB98f54E8fC179870aB6F755736174",
"tx": "0x6957558e2c90b17cd1ef8673ca4a6c949dfcdf0772e3b580e7dda2655b647c64"
},
{
"amount": "250",
"user": "0x398596bD9Bb33db205B84744DFB20B6Ffbab20a3",
"tx": "0x84eb91957cae169a0d11ed3205f39642ed71d8d185bd505ad80a02c8af48742d"
},
{
"amount": "250",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tx": "0x5dad4ba5a483a5807481d36612ff51aad8a99d4381a99674f3cf10d7b0936af3"
},
{
"amount": "100",
"user": "0x15E1A3cA96D92e621cdFb305fF86672A91309652",
"tx": "0xbe66b488288b3c8422f192bdeb196e84361215b5125257e57c30cb3568bd6d3e"
},
{
"amount": "250",
"user": "0xbd09687340A09BeB0B5EE0D3C2bCa8d78eBF6E63",
@@ -88862,10 +89175,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x398596bD9Bb33db205B84744DFB20B6Ffbab20a3",
"tranche_id": 6,
"tx": "0x84eb91957cae169a0d11ed3205f39642ed71d8d185bd505ad80a02c8af48742d"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x52D4356fb92e1b42E7eb00e3d146e71715D6a3B8",
@@ -88892,10 +89212,17 @@
"tx": "0x75de6ca47e0da361181d14aceb5d08e572754861465caa67a12b528886d55307"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "250",
"user": "0x7D7219bA97d4d9F5135cd7c489e407bB1EAa0A1E",
"tranche_id": 6,
"tx": "0x5dad4ba5a483a5807481d36612ff51aad8a99d4381a99674f3cf10d7b0936af3"
}
],
"total_tokens": "250",
"withdrawn_tokens": "0",
"remaining_tokens": "250"
"withdrawn_tokens": "250",
"remaining_tokens": "0"
},
{
"address": "0x9B99271c944B54e5cCc646526B990a34570A2660",
@@ -89175,10 +89502,17 @@
"tx": "0xc7dd4c2b995cc486fcd8b7892cd79f8fb393ada004dc68cf66ec82d99b35763c"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "100",
"user": "0x15E1A3cA96D92e621cdFb305fF86672A91309652",
"tranche_id": 6,
"tx": "0xbe66b488288b3c8422f192bdeb196e84361215b5125257e57c30cb3568bd6d3e"
}
],
"total_tokens": "100",
"withdrawn_tokens": "0",
"remaining_tokens": "100"
"withdrawn_tokens": "100",
"remaining_tokens": "0"
},
{
"address": "0x460d9FAC558e889A62670DE75f42602BEfd5352D",
+3 -2
View File
@@ -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}
/>
+12 -7
View File
@@ -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>
+1 -2
View File
@@ -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/'
+10 -4
View File
@@ -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

@@ -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')}:{' '}
+6
View File
@@ -13,3 +13,9 @@
@apply mb-2 text-neutral-400;
}
}
@layer components {
.border-default {
@apply border-vega-dark-200;
}
}
+186 -63
View File
@@ -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) {
@@ -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
});
+13
View File
@@ -0,0 +1,13 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/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>
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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" />
@@ -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));
});
}
);
};
@@ -260,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' },
@@ -275,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,5 +1,5 @@
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,
@@ -10,13 +10,12 @@ import type {
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;
@@ -141,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()}`
@@ -164,10 +156,7 @@ const SubmitOrderDetails = ({
price={price}
/>
</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -183,9 +172,10 @@ const EditOrderDetails = ({
});
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 = (
@@ -217,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')}
@@ -228,10 +218,7 @@ const EditOrderDetails = ({
<s>{original}</s>
</p>
<p>{edited}</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -266,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()}`
@@ -278,10 +265,7 @@ const CancelOrderDetails = ({
<p>
<s>{original}</s>
</p>
{order && order.rejectionReason && (
<p className="italic">{getRejectionReason(order)}</p>
)}
</Details>
</Panel>
);
};
@@ -300,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>
);
}
}
@@ -319,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
@@ -342,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>
);
}
}
@@ -368,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;
@@ -385,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">
@@ -413,7 +424,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
</p>
)}
<VegaTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -424,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"
@@ -439,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">
@@ -457,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">
@@ -476,7 +543,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
</p>
)}
<VegaTransactionDetails tx={tx} />
</div>
</>
);
};
@@ -499,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');
@@ -507,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)));
}
);
};
+2
View File
@@ -8,6 +8,7 @@ import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
@@ -25,6 +26,7 @@ const DialogsContainer = () => {
<DepositDialog />
<Web3ConnectUncontrolledDialog />
<CreateWithdrawalDialog />
<TransferDialog />
</>
);
};
+6 -25
View File
@@ -1,35 +1,16 @@
import { ToastsContainer } from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import sortBy from 'lodash/sortBy';
import { ToastsContainer, useToasts } from '@vegaprotocol/ui-toolkit';
import { useUpdateNetworkParametersToasts } from '@vegaprotocol/governance';
import { useVegaTransactionToasts } from '../lib/hooks/use-vega-transaction-toasts';
import { useEthereumTransactionToasts } from '../lib/hooks/use-ethereum-transaction-toasts';
import { useEthereumWithdrawApprovalsToasts } from '../lib/hooks/use-ethereum-withdraw-approval-toasts';
export const ToastsManager = () => {
const updateNetworkParametersToasts = useUpdateNetworkParametersToasts();
const vegaTransactionToasts = useVegaTransactionToasts();
const ethTransactionToasts = useEthereumTransactionToasts();
const withdrawApprovalToasts = useEthereumWithdrawApprovalsToasts();
const toasts = useMemo(() => {
return sortBy(
[
...vegaTransactionToasts,
...ethTransactionToasts,
...withdrawApprovalToasts,
...updateNetworkParametersToasts,
],
['createdBy']
);
}, [
vegaTransactionToasts,
ethTransactionToasts,
withdrawApprovalToasts,
updateNetworkParametersToasts,
]);
useUpdateNetworkParametersToasts();
useVegaTransactionToasts();
useEthereumTransactionToasts();
useEthereumWithdrawApprovalsToasts();
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
};
+1
View File
@@ -7,3 +7,4 @@ export * from './breakdown-table';
export * from './use-account-balance';
export * from './get-settlement-account';
export * from './use-market-account-balance';
export * from './transfer-dialog';
@@ -0,0 +1,68 @@
import * as Schema from '@vegaprotocol/types';
import {
addDecimal,
NetworkParams,
t,
truncateByChars,
useDataProvider,
useNetworkParam,
} from '@vegaprotocol/react-helpers';
import type { Transfer } from '@vegaprotocol/wallet';
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
import { useCallback, useMemo } from 'react';
import { accountsDataProvider } from './accounts-data-provider';
import { TransferForm } from './transfer-form';
import { useTransferDialog } from './transfer-dialog';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
export const TransferContainer = () => {
const { pubKey, pubKeys } = useVegaWallet();
const open = useTransferDialog((store) => store.open);
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
const { data } = useDataProvider({
dataProvider: accountsDataProvider,
variables: { partyId: pubKey },
skip: !pubKey,
});
const create = useVegaTransactionStore((store) => store.create);
const transfer = useCallback(
(transfer: Transfer) => {
create({ transfer });
open(false);
},
[create, open]
);
const assets = useMemo(() => {
if (!data) return [];
return data
.filter(
(account) => account.type === Schema.AccountType.ACCOUNT_TYPE_GENERAL
)
.map((account) => ({
id: account.asset.id,
symbol: account.asset.symbol,
name: account.asset.name,
decimals: account.asset.decimals,
balance: addDecimal(account.balance, account.asset.decimals),
}));
}, [data]);
return (
<>
<p className="text-sm mb-4">
{t('Transfer funds to another Vega key from')}{' '}
<Lozenge className="font-mono">{truncateByChars(pubKey || '')}</Lozenge>{' '}
{t('If you are at all unsure, stop and seek advice.')}
</p>
<TransferForm
pubKey={pubKey}
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
assets={assets}
feeFactor={param}
submitTransfer={transfer}
/>
</>
);
};
+28
View File
@@ -0,0 +1,28 @@
import { t } from '@vegaprotocol/react-helpers';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { create } from 'zustand';
import { TransferContainer } from './transfer-container';
interface State {
isOpen: boolean;
}
interface Actions {
open: (open?: boolean) => void;
}
export const useTransferDialog = create<State & Actions>((set) => ({
isOpen: false,
open: (open = true) => {
set(() => ({ isOpen: open }));
},
}));
export const TransferDialog = () => {
const { isOpen, open } = useTransferDialog();
return (
<Dialog title={t('Transfer')} open={isOpen} onChange={open} size="small">
<TransferContainer />
</Dialog>
);
};
@@ -0,0 +1,177 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { AddressField, TransferFee, TransferForm } from './transfer-form';
import { AccountType } from '@vegaprotocol/types';
import { formatNumber, removeDecimal } from '@vegaprotocol/react-helpers';
describe('TransferForm', () => {
const submit = () => fireEvent.submit(screen.getByTestId('transfer-form'));
const amount = '100';
const pubKey =
'70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680';
const asset = {
id: 'asset-0',
symbol: 'ASSET 0',
name: 'Asset 0',
decimals: 2,
balance: '1000',
};
const props = {
pubKey,
pubKeys: [
pubKey,
'a4b6e3de5d7ef4e31ae1b090be49d1a2ef7bcefff60cccf7658a0d4922651cce',
],
assets: [asset],
feeFactor: '0.001',
submitTransfer: jest.fn(),
};
it('validates fields and submits', async () => {
render(<TransferForm {...props} />);
// check current pubkey not shown
const keySelect: HTMLSelectElement = screen.getByLabelText('Vega key');
expect(keySelect.children).toHaveLength(2);
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual([
'',
props.pubKeys[1],
]);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
// Select a pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: props.pubKeys[1] },
});
// Select asset
fireEvent.change(
// Bypass RichSelect and target hidden native select
// eslint-disable-next-line
document.querySelector('select[name="asset"]')!,
{ target: { value: asset.id } }
);
// assert rich select as updated
expect(await screen.findByTestId('select-asset')).toHaveTextContent(
asset.name
);
expect(screen.getByTestId('asset-balance')).toHaveTextContent(
formatNumber(asset.balance, asset.decimals)
);
// Test amount validation
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '0.00000001' },
});
expect(
await screen.findByText('Value is below minimum')
).toBeInTheDocument();
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: '9999999' },
});
expect(
await screen.findByText(/cannot transfer more/i)
).toBeInTheDocument();
// set valid amount
fireEvent.change(screen.getByLabelText('Amount'), {
target: { value: amount },
});
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(
new BigNumber(props.feeFactor).times(amount).toFixed()
);
submit();
await waitFor(() => {
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
expect(props.submitTransfer).toHaveBeenCalledWith({
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
to: props.pubKeys[1],
asset: asset.id,
amount: removeDecimal(amount, asset.decimals),
oneOff: {},
});
});
});
it('validates a manually entered address', async () => {
render(<TransferForm {...props} />);
submit();
expect(await screen.findAllByText('Required')).toHaveLength(3);
const toggle = screen.getByText('Enter manually');
fireEvent.click(toggle);
// has switched to input
expect(toggle).toHaveTextContent('Select from wallet');
expect(screen.getByLabelText('Vega key')).toHaveAttribute('type', 'text');
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: 'invalid-address' },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Invalid Vega key');
});
// same pubkey
fireEvent.change(screen.getByLabelText('Vega key'), {
target: { value: pubKey },
});
await waitFor(() => {
const errors = screen.getAllByTestId('input-error-text');
expect(errors[0]).toHaveTextContent('Vega key is the same');
});
});
});
describe('AddressField', () => {
const props = {
pubKeys: ['pubkey-1', 'pubkey-2'],
select: <div>select</div>,
input: <div>input</div>,
onChange: jest.fn(),
};
it('toggles content and calls onChange', async () => {
const mockOnChange = jest.fn();
render(<AddressField {...props} onChange={mockOnChange} />);
// select should be shown as multiple pubkeys provided
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
fireEvent.click(screen.getByText('Enter manually'));
expect(screen.queryByText('select')).not.toBeInTheDocument();
expect(screen.getByText('input')).toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('Select from wallet'));
expect(screen.getByText('select')).toBeInTheDocument();
expect(screen.queryByText('input')).not.toBeInTheDocument();
expect(mockOnChange).toHaveBeenCalledTimes(2);
});
it('Does not provide select option if there is only a single key', () => {
render(<AddressField {...props} pubKeys={['single-pubKey']} />);
expect(screen.getByText('input')).toBeInTheDocument();
expect(screen.queryByText('Select from wallet')).not.toBeInTheDocument();
});
});
describe('TransferFee', () => {
const props = {
amount: '200',
feeFactor: '0.001',
};
it('calculates and renders the transfer fee', () => {
render(<TransferFee {...props} />);
const expected = new BigNumber(props.amount)
.times(props.feeFactor)
.toFixed();
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expected);
});
});
+296
View File
@@ -0,0 +1,296 @@
import {
t,
minSafe,
maxSafe,
required,
vegaPublicKey,
addDecimal,
formatNumber,
} from '@vegaprotocol/react-helpers';
import {
Button,
FormGroup,
Input,
InputError,
Option,
RichSelect,
Select,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
import BigNumber from 'bignumber.js';
import type { ReactNode } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
interface FormFields {
toAddress: string;
asset: string;
amount: string;
}
interface TransferFormProps {
pubKey: string | null;
pubKeys: string[] | null;
assets: Array<{
id: string;
symbol: string;
name: string;
decimals: number;
balance: string;
}>;
feeFactor: string | null;
submitTransfer: (transfer: Transfer) => void;
}
export const TransferForm = ({
pubKey,
pubKeys,
assets,
feeFactor,
submitTransfer,
}: TransferFormProps) => {
const {
control,
register,
watch,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormFields>();
const amount = watch('amount');
const assetId = watch('asset');
const asset = useMemo(() => {
return assets.find((a) => a.id === assetId);
}, [assets, assetId]);
const onSubmit = useCallback(
(fields: FormFields) => {
if (!asset) {
throw new Error('Submitted transfer with no asset selected');
}
const transfer = normalizeTransfer(fields.toAddress, fields.amount, {
id: asset.id,
decimals: asset.decimals,
});
submitTransfer(transfer);
},
[asset, submitTransfer]
);
const min = useMemo(() => {
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
const minViableAmount = asset
? new BigNumber(addDecimal('1', asset.decimals))
: new BigNumber(0);
return minViableAmount;
}, [asset]);
const max = useMemo(() => {
const maxAmount = asset ? new BigNumber(asset.balance) : new BigNumber(0);
return maxAmount;
}, [asset]);
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="text-sm"
data-testid="transfer-form"
>
<FormGroup label="Vega key" labelFor="to-address">
<AddressField
pubKeys={pubKeys}
onChange={() => setValue('toAddress', '')}
select={
<Select {...register('toAddress')} id="to-address" defaultValue="">
<option value="" disabled={true}>
{t('Please select')}
</option>
{pubKeys?.length &&
pubKeys
.filter((pk) => pk !== pubKey) // remove currently selected pubkey
.map((pk) => (
<option key={pk} value={pk}>
{pk}
</option>
))}
</Select>
}
input={
<Input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true} // focus input immediately after is shown
id="to-address"
type="text"
{...register('toAddress', {
validate: {
required,
vegaPublicKey,
sameKey: (value) => {
if (value === pubKey) {
return t('Vega key is the same as current key');
}
return true;
},
},
})}
/>
}
/>
{errors.toAddress?.message && (
<InputError forInput="to-address">
{errors.toAddress.message}
</InputError>
)}
</FormGroup>
<FormGroup label="Asset" labelFor="asset">
<Controller
control={control}
name="asset"
rules={{
validate: {
required,
},
}}
render={({ field }) => (
<RichSelect
data-testid="select-asset"
id={field.name}
name={field.name}
onValueChange={(value) => {
field.onChange(value);
}}
placeholder={t('Please select')}
value={field.value}
>
{assets.map((a) => (
<Option key={a.id} value={a.id}>
<div className="text-left" data-testid={`asset-${a.id}`}>
<div>{a.name}</div>
<div className="text-xs">
<span className="font-mono" data-testid="asset-balance">
{formatNumber(a.balance, a.decimals)}
</span>{' '}
<span>{a.symbol}</span>
</div>
</div>
</Option>
))}
</RichSelect>
)}
/>
{errors.asset?.message && (
<InputError forInput="asset">{errors.asset.message}</InputError>
)}
</FormGroup>
<FormGroup label="Amount" labelFor="amount">
<Input
id="amount"
autoComplete="off"
appendElement={
asset && <span className="text-xs">{asset.symbol}</span>
}
{...register('amount', {
validate: {
required,
minSafe: (value) => minSafe(new BigNumber(min))(value),
maxSafe: (v) => {
const value = new BigNumber(v);
if (value.isGreaterThan(max)) {
return t(
'You cannot transfer more than your available collateral'
);
}
return maxSafe(max)(v);
},
},
})}
/>
{errors.amount?.message && (
<InputError forInput="amount">{errors.amount.message}</InputError>
)}
</FormGroup>
<TransferFee amount={amount} feeFactor={feeFactor} />
<Button type="submit" variant="primary" fill={true}>
{t('Confirm transfer')}
</Button>
</form>
);
};
export const TransferFee = ({
amount,
feeFactor,
}: {
amount: string;
feeFactor: string | null;
}) => {
if (!feeFactor || !amount) return null;
// using toFixed without an argument will always return a
// number in normal notation without rounding, formatting functions
// arent working in a way which won't round the decimal places
const value = new BigNumber(amount).times(feeFactor).toFixed();
return (
<div className="mb-4 flex justify-between items-center gap-4 flex-wrap">
<div>
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to ${feeFactor}`
)}
>
<div>{t('Transfer fee')}</div>
</Tooltip>
</div>
<div
data-testid="transfer-fee"
className="text-neutral-500 dark:text-neutral-300"
>
{value}
</div>
</div>
);
};
interface AddressInputProps {
pubKeys: string[] | null;
select: ReactNode;
input: ReactNode;
onChange: () => void;
}
export const AddressField = ({
pubKeys,
select,
input,
onChange,
}: AddressInputProps) => {
const [isInput, setIsInput] = useState(() => {
if (pubKeys && pubKeys.length <= 1) {
return true;
}
return false;
});
return (
<>
{isInput ? input : select}
{pubKeys && pubKeys.length > 1 && (
<button
type="button"
onClick={() => {
setIsInput((curr) => !curr);
onChange();
}}
className="ml-auto text-sm absolute top-0 right-0 underline"
>
{isInput ? t('Select from wallet') : t('Enter manually')}
</button>
)}
</>
);
};
@@ -10,9 +10,9 @@ interface Props {
export const DealTicketButton = ({ disabled, variant }: Props) => {
const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
return pubKey ? (
<div className="mb-4">
<Button
@@ -31,8 +31,6 @@ import {
usePersistedOrderStoreSubscription,
} from '@vegaprotocol/orders';
export type TransactionStatus = 'default' | 'pending';
export interface DealTicketProps {
market: MarketDealTicket;
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
@@ -20,14 +20,19 @@ export const AssetProposalNotification = ({
const proposalLink = tokenLink(
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
);
const message = (
<>
{t('Changes have been proposed for this asset.')}{' '}
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</>
);
return (
<Notification
intent={Intent.Warning}
message={t('Changes have been proposed for this asset')}
message={message}
testId="asset-proposal-notification"
>
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</Notification>
className="mb-2"
/>
);
}
@@ -20,14 +20,23 @@ export const MarketProposalNotification = ({
const proposalLink = tokenLink(
TOKEN_PROPOSAL.replace(':id', proposal.id || '')
);
const message = (
<div className="flex flex-col text-sm">
{t('Changes have been proposed for this market.')}{' '}
<ExternalLink href={proposalLink} className="w-fit">
{t('View proposal')}
</ExternalLink>
</div>
);
return (
<Notification
intent={Intent.Warning}
message={t('Changes have been proposed for this market')}
testId="market-proposal-notification"
>
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</Notification>
<div className="border-l border-default pl-1 pr-1 pb-1 min-w-min whitespace-nowrap">
<Notification
intent={Intent.Warning}
message={message}
testId="market-proposal-notification"
className="px-2 py-1"
/>
</div>
);
}
@@ -7,13 +7,8 @@ fragment ProposalEventFields on Proposal {
}
subscription ProposalEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Proposal]) {
type
event {
... on Proposal {
...ProposalEventFields
}
}
proposals(partyId: $partyId) {
...ProposalEventFields
}
}
@@ -10,7 +10,7 @@ export type ProposalEventSubscriptionVariables = Types.Exact<{
}>;
export type ProposalEventSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', type: Types.BusEventType, event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote' } | { __typename?: 'Withdrawal' } }> | null };
export type ProposalEventSubscription = { __typename?: 'Subscription', proposals: { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null } };
export type UpdateNetworkParameterFieldsFragment = { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, datetime: any, terms: { __typename?: 'ProposalTerms', enactmentDatetime?: any | null, change: { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } } };
@@ -55,13 +55,8 @@ export const UpdateNetworkParameterFieldsFragmentDoc = gql`
`;
export const ProposalEventDocument = gql`
subscription ProposalEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Proposal]) {
type
event {
... on Proposal {
...ProposalEventFields
}
}
proposals(partyId: $partyId) {
...ProposalEventFields
}
}
${ProposalEventFieldsFragmentDoc}`;
@@ -28,24 +28,15 @@ export const useProposalEvent = (transaction: VegaTxState) => {
variables: { partyId },
})
.subscribe(({ data }) => {
if (!data?.busEvents?.length) {
if (!data?.proposals) {
return;
}
// No types available for the subscription result
const matchingProposalEvent = data.busEvents.find((e) => {
if (e.event.__typename !== 'Proposal') {
return false;
}
// typo in 'proposals' - this should be singular
const proposal = data.proposals;
return e.event.id === id;
});
if (
matchingProposalEvent &&
matchingProposalEvent.event.__typename === 'Proposal'
) {
callback(matchingProposalEvent.event);
if (proposal.id === id) {
callback(proposal);
subRef.current?.unsubscribe();
}
});
@@ -4,6 +4,7 @@ import type { UpdateNetworkParameter } from '@vegaprotocol/types';
import { ProposalStateMapping } from '@vegaprotocol/types';
import { ProposalState } from '@vegaprotocol/types';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { ExternalLink, Intent } from '@vegaprotocol/ui-toolkit';
import compact from 'lodash/compact';
@@ -28,7 +29,7 @@ const UpdateNetworkParameterToastContent = ({
const enactment = Date.parse(proposal.terms.enactmentDatetime);
return (
<div>
<h3 className="font-bold">{title}</h3>
<ToastHeading>{title}</ToastHeading>
<p className="italic">
'
{t(
@@ -52,9 +53,8 @@ const UpdateNetworkParameterToastContent = ({
);
};
export const useUpdateNetworkParametersToasts = (): Toast[] => {
const { proposalToasts, setToast, remove } = useToasts((store) => ({
proposalToasts: store.toasts,
export const useUpdateNetworkParametersToasts = () => {
const { setToast, remove } = useToasts((store) => ({
setToast: store.setToast,
remove: store.remove,
}));
@@ -66,7 +66,9 @@ export const useUpdateNetworkParametersToasts = (): Toast[] => {
id: `update-network-param-proposal-${proposal.id}`,
intent: Intent.Warning,
content: <UpdateNetworkParameterToastContent proposal={proposal} />,
onClose: () => remove(id),
onClose: () => {
remove(id);
},
closeAfter: CLOSE_AFTER,
};
},
@@ -96,6 +98,4 @@ export const useUpdateNetworkParametersToasts = (): Toast[] => {
}
},
});
return proposalToasts;
};
@@ -1,6 +1,6 @@
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react-hooks';
import { ProposalState } from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import { useUpdateNetworkParametersToasts } from './use-update-network-paramaters-toasts';
@@ -9,8 +9,8 @@ import type {
OnUpdateNetworkParametersSubscription,
} from './__generated__/Proposal';
import { OnUpdateNetworkParametersDocument } from './__generated__/Proposal';
import waitForNextTick from 'flush-promises';
import { useToasts } from '@vegaprotocol/ui-toolkit';
import { waitFor } from '@testing-library/react';
const render = (mocks?: MockedResponse[]) => {
const wrapper = ({ children }: { children: ReactNode }) => (
@@ -92,11 +92,10 @@ const mockedEvent: MockedResponse<OnUpdateNetworkParametersSubscription> = {
},
};
const INITIAL = useToasts.getState();
const clear = () => {
const { result: clearer } = renderHook(() =>
useToasts((store) => store.removeAll)
);
act(() => clearer.current());
useToasts.setState(INITIAL);
};
describe('useUpdateNetworkParametersToasts', () => {
@@ -104,29 +103,23 @@ describe('useUpdateNetworkParametersToasts', () => {
afterAll(clear);
it('returns toast for update network parameters bus event', async () => {
const { waitForNextUpdate, result } = render([mockedEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(1);
});
expect(result.current.length).toBe(1);
});
it('does not return toast for empty event', async () => {
const { waitForNextUpdate, result } = render([mockedEmptyEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedEmptyEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(0);
});
expect(result.current.length).toBe(0);
});
it('does not return toast for wrong event', async () => {
const { waitForNextUpdate, result } = render([mockedWrongEvent]);
await act(async () => {
waitForNextUpdate();
await waitForNextTick();
render([mockedWrongEvent]);
await waitFor(() => {
expect(useToasts.getState().count).toBe(0);
});
expect(result.current.length).toBe(0);
});
});
@@ -1,18 +1,13 @@
fragment VoteEventFields on Vote {
fragment VoteEventFields on ProposalVote {
proposalId
value
datetime
vote {
value
datetime
}
}
subscription VoteEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Vote]) {
type
event {
... on Vote {
proposalId
value
datetime
}
}
votes(partyId: $partyId) {
...VoteEventFields
}
}
@@ -3,36 +3,31 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type VoteEventFieldsFragment = { __typename?: 'Vote', proposalId: string, value: Types.VoteValue, datetime: any };
export type VoteEventFieldsFragment = { __typename?: 'ProposalVote', proposalId: string, vote: { __typename?: 'Vote', value: Types.VoteValue, datetime: any } };
export type VoteEventSubscriptionVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
}>;
export type VoteEventSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', type: Types.BusEventType, event: { __typename?: 'AccountEvent' } | { __typename?: 'Asset' } | { __typename?: 'AuctionEvent' } | { __typename?: 'Deposit' } | { __typename?: 'LiquidityProvision' } | { __typename?: 'LossSocialization' } | { __typename?: 'MarginLevels' } | { __typename?: 'Market' } | { __typename?: 'MarketData' } | { __typename?: 'MarketEvent' } | { __typename?: 'MarketTick' } | { __typename?: 'NodeSignature' } | { __typename?: 'OracleSpec' } | { __typename?: 'Order' } | { __typename?: 'Party' } | { __typename?: 'PositionResolution' } | { __typename?: 'Proposal' } | { __typename?: 'RiskFactor' } | { __typename?: 'SettleDistressed' } | { __typename?: 'SettlePosition' } | { __typename?: 'TimeUpdate' } | { __typename?: 'Trade' } | { __typename?: 'TransactionResult' } | { __typename?: 'TransferResponses' } | { __typename?: 'Vote', proposalId: string, value: Types.VoteValue, datetime: any } | { __typename?: 'Withdrawal' } }> | null };
export type VoteEventSubscription = { __typename?: 'Subscription', votes: { __typename?: 'ProposalVote', proposalId: string, vote: { __typename?: 'Vote', value: Types.VoteValue, datetime: any } } };
export const VoteEventFieldsFragmentDoc = gql`
fragment VoteEventFields on Vote {
fragment VoteEventFields on ProposalVote {
proposalId
value
datetime
vote {
value
datetime
}
}
`;
export const VoteEventDocument = gql`
subscription VoteEvent($partyId: ID!) {
busEvents(partyId: $partyId, batchSize: 0, types: [Vote]) {
type
event {
... on Vote {
proposalId
value
datetime
}
}
votes(partyId: $partyId) {
...VoteEventFields
}
}
`;
${VoteEventFieldsFragmentDoc}`;
/**
* __useVoteEventSubscription__
@@ -25,23 +25,14 @@ export const useVoteEvent = (transaction: VegaTxState) => {
variables: { partyId },
})
.subscribe(({ data }) => {
if (!data?.busEvents?.length) {
if (!data?.votes) {
return;
}
const matchingVoteEvent = data.busEvents.find((e) => {
if (e.event.__typename !== 'Vote') {
return false;
}
const vote = data.votes;
return e.event.proposalId === proposalId;
});
if (
matchingVoteEvent &&
matchingVoteEvent.event.__typename === 'Vote'
) {
callback(matchingVoteEvent.event);
if (vote.proposalId === proposalId) {
callback(vote);
subRef.current?.unsubscribe();
}
});
+1 -4
View File
@@ -19,7 +19,6 @@ import {
signedNumberCssClassRules,
DateRangeFilter,
addDecimalsFormatNumber,
PriceCell,
} from '@vegaprotocol/react-helpers';
import { AgGridDynamic as AgGrid, Link } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
@@ -85,7 +84,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
filterParams: { buttons: ['reset'] },
tooltipComponent: TooltipCellComponent,
}}
components={{ AmountCell, PriceFlashCell, PriceCell, ProgressBarCell }}
components={{ AmountCell, PriceFlashCell, ProgressBarCell }}
{...props}
>
<AgGridColumn
@@ -336,7 +335,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
? undefined
: addDecimalsFormatNumber(data.realisedPNL, data.decimals);
}}
cellRenderer="PriceCell"
headerTooltip={t(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
)}
@@ -361,7 +359,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
? undefined
: addDecimalsFormatNumber(data.unrealisedPNL, data.decimals)
}
cellRenderer="PriceCell"
headerTooltip={t(
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
)}
@@ -2,6 +2,7 @@ import { renderHook, waitFor } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { NetworkParamsKey } from './use-network-params';
import { toRealKey } from './use-network-params';
import {
NetworkParams,
useNetworkParam,
@@ -18,7 +19,7 @@ describe('useNetworkParam', () => {
request: {
query: NetworkParamDocument,
variables: {
key: arg,
key: toRealKey(arg),
},
},
result: {
@@ -104,10 +104,11 @@ export const NetworkParams = {
market_liquidity_stakeToCcyVolume: 'market_liquidity_stakeToCcyVolume',
market_liquidity_targetstake_triggering_ratio:
'market_liquidity_targetstake_triggering_ratio',
transfer_fee_factor: 'transfer_fee_factor',
} as const;
type Params = typeof NetworkParams;
export type NetworkParamsKey = Params[keyof Params];
export type NetworkParamsKey = keyof Params;
type Result = {
[key in keyof Params]: string;
};
@@ -120,7 +121,7 @@ export const useNetworkParams = <T extends NetworkParamsKey[]>(params?: T) => {
return compact(data.networkParametersConnection.edges)
.map((p) => ({
...p.node,
key: p.node.key.split('.').join('_'),
key: toInternalKey(p.node.key),
}))
.filter((p) => {
if (params === undefined || params.length === 0) return true;
@@ -143,7 +144,7 @@ export const useNetworkParams = <T extends NetworkParamsKey[]>(params?: T) => {
export const useNetworkParam = (param: NetworkParamsKey) => {
const { data, loading, error } = useNetworkParamQuery({
variables: {
key: param,
key: toRealKey(param),
},
});
@@ -153,3 +154,11 @@ export const useNetworkParam = (param: NetworkParamsKey) => {
error,
};
};
export const toRealKey = (key: NetworkParamsKey) => {
return key.split('_').join('.');
};
export const toInternalKey = (key: string) => {
return key.split('.').join('_');
};
+5
View File
@@ -174,11 +174,16 @@ module.exports = {
'60%': { transform: 'rotate( 0.0deg)' },
'100%': { transform: 'rotate( 0.0deg)' },
},
progress: {
from: { width: '0' },
to: { width: '100%' },
},
},
animation: {
rotate: 'rotate 2s linear alternate infinite',
'rotate-back': 'rotate 2s linear reverse infinite',
wave: 'wave 2s linear infinite',
progress: 'progress 5s cubic-bezier(.39,.58,.57,1) 1',
},
data: {
selected: 'state~="checked"',
@@ -1,7 +1,6 @@
import { Splash } from '../splash';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import * as Sentry from '@sentry/react';
interface AsyncRendererProps<T> {
loading: boolean;
@@ -27,7 +26,6 @@ export function AsyncRenderer<T = object>({
render,
}: AsyncRendererProps<T>) {
if (error) {
Sentry.captureException(`Error rendering data: ${error.message}`);
if (!data) {
return (
<Splash>
@@ -151,7 +151,7 @@ export const DropdownMenuSeparator = forwardRef<
{...separatorProps}
ref={forwardedRef}
className={classNames(
'h-px my-1 mx-2 bg-neutral-700 dark:bg-black',
'h-px my-1 mx-2 bg-neutral-400 dark:bg-neutral-300',
className
)}
/>

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