feat(explorer): batch proposal support (#5711)
This commit is contained in:
parent
94e7ad489f
commit
f62e29c67f
@ -1,4 +1,4 @@
|
|||||||
export type HashProps = {
|
export type HashProps = React.HTMLProps<HTMLSpanElement> & {
|
||||||
text: string;
|
text: string;
|
||||||
truncate?: boolean;
|
truncate?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -2,4 +2,5 @@ export { default as BlockLink } from './block-link/block-link';
|
|||||||
export { default as PartyLink } from './party-link/party-link';
|
export { default as PartyLink } from './party-link/party-link';
|
||||||
export { default as NodeLink } from './node-link/node-link';
|
export { default as NodeLink } from './node-link/node-link';
|
||||||
export { default as MarketLink } from './market-link/market-link';
|
export { default as MarketLink } from './market-link/market-link';
|
||||||
|
export { default as NetworkParameterLink } from './network-parameter-link/network-parameter-link';
|
||||||
export * from './asset-link/asset-link';
|
export * from './asset-link/asset-link';
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Routes } from '../../../routes/route-names';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import Hash from '../hash';
|
||||||
|
|
||||||
|
export type NetworkParameterLinkProps = Partial<ComponentProps<typeof Link>> & {
|
||||||
|
parameter: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links a given network parameter to the relevant page and anchor on the page
|
||||||
|
*/
|
||||||
|
const NetworkParameterLink = ({
|
||||||
|
parameter,
|
||||||
|
...props
|
||||||
|
}: NetworkParameterLinkProps) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="underline"
|
||||||
|
{...props}
|
||||||
|
to={`/${Routes.NETWORK_PARAMETERS}#${parameter}`}
|
||||||
|
>
|
||||||
|
<Hash text={parameter} />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetworkParameterLink;
|
@ -26,7 +26,7 @@ const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const base = ENV.dataSources.governanceUrl;
|
const base = ENV.dataSources.governanceUrl;
|
||||||
const label = proposal?.rationale.title || id;
|
const label = proposal?.rationale?.title || id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||||
|
@ -5,5 +5,10 @@ query ExplorerProposalStatus($id: ID!) {
|
|||||||
state
|
state
|
||||||
rejectionReason
|
rejectionReason
|
||||||
}
|
}
|
||||||
|
... on BatchProposal {
|
||||||
|
id
|
||||||
|
state
|
||||||
|
rejectionReason
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export type ExplorerProposalStatusQueryVariables = Types.Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal' } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'BatchProposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
||||||
|
|
||||||
|
|
||||||
export const ExplorerProposalStatusDocument = gql`
|
export const ExplorerProposalStatusDocument = gql`
|
||||||
@ -19,6 +19,11 @@ export const ExplorerProposalStatusDocument = gql`
|
|||||||
state
|
state
|
||||||
rejectionReason
|
rejectionReason
|
||||||
}
|
}
|
||||||
|
... on BatchProposal {
|
||||||
|
id
|
||||||
|
state
|
||||||
|
rejectionReason
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -0,0 +1,257 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { BatchItem } from './batch-item';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
type Item = components['schemas']['vegaBatchProposalTermsChange'];
|
||||||
|
|
||||||
|
describe('BatchItem', () => {
|
||||||
|
it('Renders "Unknown proposal type" by default', () => {
|
||||||
|
const item = {};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Unknown proposal type" for unknown items', () => {
|
||||||
|
const item = {
|
||||||
|
newLochNessMonster: {
|
||||||
|
location: 'Loch Ness',
|
||||||
|
},
|
||||||
|
} as unknown as Item;
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Unknown proposal type')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "New spot market"', () => {
|
||||||
|
const item = {
|
||||||
|
newSpotMarket: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('New spot market')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Cancel transfer"', () => {
|
||||||
|
const item = {
|
||||||
|
cancelTransfer: {
|
||||||
|
changes: {
|
||||||
|
transferId: 'transfer123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('transf')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Cancel transfer" without an id', () => {
|
||||||
|
const item = {
|
||||||
|
cancelTransfer: {
|
||||||
|
changes: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Cancel transfer')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "New freeform"', () => {
|
||||||
|
const item = {
|
||||||
|
newFreeform: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('New freeform proposal')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "New market"', () => {
|
||||||
|
const item = {
|
||||||
|
newMarket: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('New market')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "New transfer"', () => {
|
||||||
|
const item = {
|
||||||
|
newTransfer: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('New transfer')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update asset" with assetId', () => {
|
||||||
|
const item = {
|
||||||
|
updateAsset: {
|
||||||
|
assetId: 'asset123',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update asset')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('asset123')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update asset" even if assetId is not set', () => {
|
||||||
|
const item = {
|
||||||
|
updateAsset: {
|
||||||
|
assetId: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Update asset')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update market state" with marketId', () => {
|
||||||
|
const item = {
|
||||||
|
updateMarketState: {
|
||||||
|
changes: {
|
||||||
|
marketId: 'market123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update market state')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update market state" even if marketId is not set', () => {
|
||||||
|
const item = {
|
||||||
|
updateMarketState: {
|
||||||
|
changes: {
|
||||||
|
marketId: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update market state')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update network parameter" with parameter', () => {
|
||||||
|
const item = {
|
||||||
|
updateNetworkParameter: {
|
||||||
|
changes: {
|
||||||
|
key: 'parameter123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MockedProvider>
|
||||||
|
<MemoryRouter>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MemoryRouter>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('parameter123')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update network parameter" even if parameter is not set', () => {
|
||||||
|
const item = {
|
||||||
|
updateNetworkParameter: {
|
||||||
|
changes: {
|
||||||
|
key: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Update network parameter')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update referral program"', () => {
|
||||||
|
const item = {
|
||||||
|
updateReferralProgram: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(screen.getByText('Update referral program')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update spot market" with marketId', () => {
|
||||||
|
const item = {
|
||||||
|
updateSpotMarket: {
|
||||||
|
marketId: 'market123',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update spot market')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update spot market" even if marketId is not set', () => {
|
||||||
|
const item = {
|
||||||
|
updateSpotMarket: {
|
||||||
|
marketId: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update spot market')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Renders "Update market" with marketId', () => {
|
||||||
|
const item = {
|
||||||
|
updateMarket: {
|
||||||
|
marketId: 'market123',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update market')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('market123')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update market" even if marketId is not set', () => {
|
||||||
|
const item = {
|
||||||
|
updateMarket: {
|
||||||
|
marketId: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<MockedProvider>
|
||||||
|
<BatchItem item={item} />
|
||||||
|
</MockedProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByText('Update market')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders "Update volume discount program"', () => {
|
||||||
|
const item = {
|
||||||
|
updateVolumeDiscountProgram: {},
|
||||||
|
};
|
||||||
|
render(<BatchItem item={item} />);
|
||||||
|
expect(
|
||||||
|
screen.getByText('Update volume discount program')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,87 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { AssetLink, MarketLink, NetworkParameterLink } from '../../../links';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import Hash from '../../../links/hash';
|
||||||
|
|
||||||
|
type Item = components['schemas']['vegaBatchProposalTermsChange'];
|
||||||
|
|
||||||
|
export interface BatchItemProps {
|
||||||
|
item: Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a one line summary for an item in a batch proposal. Could
|
||||||
|
* easily be adapted to summarise individual proposals, but there is no
|
||||||
|
* place for that yet.
|
||||||
|
*
|
||||||
|
* Details (like IDs) should be shown and linked if available, but handled
|
||||||
|
* if not available. This is adequate as the ProposalSummary component contains
|
||||||
|
* a JSON viewer for the full proposal.
|
||||||
|
*/
|
||||||
|
export const BatchItem = ({ item }: BatchItemProps) => {
|
||||||
|
if (item.cancelTransfer) {
|
||||||
|
const transferId = item?.cancelTransfer?.changes?.transferId || false;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Cancel transfer')}
|
||||||
|
{transferId && (
|
||||||
|
<Hash className="ml-1" truncate={true} text={transferId} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.newFreeform) {
|
||||||
|
return <span>{t('New freeform proposal')}</span>;
|
||||||
|
} else if (item.newMarket) {
|
||||||
|
return <span>{t('New market')}</span>;
|
||||||
|
} else if (item.newSpotMarket) {
|
||||||
|
return <span>{t('New spot market')}</span>;
|
||||||
|
} else if (item.newTransfer) {
|
||||||
|
return <span>{t('New transfer')}</span>;
|
||||||
|
} else if (item.updateAsset) {
|
||||||
|
const assetId = item?.updateAsset?.assetId || false;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Update asset')}
|
||||||
|
{assetId && <AssetLink className="ml-1" assetId={assetId} />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.updateMarket) {
|
||||||
|
const marketId = item?.updateMarket?.marketId || false;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Update market')}{' '}
|
||||||
|
{marketId && <MarketLink className="ml-1" id={marketId} />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.updateMarketState) {
|
||||||
|
const marketId = item?.updateMarketState?.changes?.marketId || false;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Update market state')}
|
||||||
|
{marketId && <MarketLink className="ml-1" id={marketId} />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.updateNetworkParameter) {
|
||||||
|
const param = item?.updateNetworkParameter?.changes?.key || false;
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Update network parameter')}
|
||||||
|
{param && <NetworkParameterLink className="ml-1" parameter={param} />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.updateReferralProgram) {
|
||||||
|
return <span>{t('Update referral program')}</span>;
|
||||||
|
} else if (item.updateSpotMarket) {
|
||||||
|
const marketId = item?.updateSpotMarket?.marketId || '';
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{t('Update spot market')}
|
||||||
|
<MarketLink className="ml-1" id={marketId} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (item.updateVolumeDiscountProgram) {
|
||||||
|
return <span>{t('Update volume discount program')}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{t('Unknown proposal type')}</span>;
|
||||||
|
};
|
@ -1,6 +1,4 @@
|
|||||||
import type { ProposalTerms } from '../tx-proposal';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { components } from '../../../../../types/explorer';
|
|
||||||
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
|
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
|
||||||
import ProposalLink from '../../../links/proposal-link/proposal-link';
|
import ProposalLink from '../../../links/proposal-link/proposal-link';
|
||||||
import truncate from 'lodash/truncate';
|
import truncate from 'lodash/truncate';
|
||||||
@ -9,7 +7,12 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import { ProposalDate } from './proposal-date';
|
import { ProposalDate } from './proposal-date';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
|
import type { ProposalTerms } from '../tx-proposal';
|
||||||
|
import type { components } from '../../../../../types/explorer';
|
||||||
|
import { BatchItem } from './batch-item';
|
||||||
|
|
||||||
type Rationale = components['schemas']['vegaProposalRationale'];
|
type Rationale = components['schemas']['vegaProposalRationale'];
|
||||||
|
type Batch = components['schemas']['v1BatchProposalSubmissionTerms']['changes'];
|
||||||
|
|
||||||
type ProposalTermsDialog = {
|
type ProposalTermsDialog = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -21,6 +24,7 @@ interface ProposalSummaryProps {
|
|||||||
id: string;
|
id: string;
|
||||||
rationale?: Rationale;
|
rationale?: Rationale;
|
||||||
terms?: ProposalTerms;
|
terms?: ProposalTerms;
|
||||||
|
batch?: Batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +35,7 @@ export const ProposalSummary = ({
|
|||||||
id,
|
id,
|
||||||
rationale,
|
rationale,
|
||||||
terms,
|
terms,
|
||||||
|
batch,
|
||||||
}: ProposalSummaryProps) => {
|
}: ProposalSummaryProps) => {
|
||||||
const [dialog, setDialog] = useState<ProposalTermsDialog>({
|
const [dialog, setDialog] = useState<ProposalTermsDialog>({
|
||||||
open: false,
|
open: false,
|
||||||
@ -72,6 +77,18 @@ export const ProposalSummary = ({
|
|||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{batch && (
|
||||||
|
<section className="pt-2 text-sm leading-tight my-3">
|
||||||
|
<h2 className="text-lg pb-1">{t('Changes')}</h2>
|
||||||
|
<ol>
|
||||||
|
{batch.map((change, index) => (
|
||||||
|
<li className="ml-4 list-decimal" key={`batch-${index}`}>
|
||||||
|
<BatchItem item={change} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
<div className="pt-5">
|
<div className="pt-5">
|
||||||
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
|
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
|
||||||
{t('View terms')}
|
{t('View terms')}
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||||
|
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||||
|
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
|
||||||
|
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||||
|
import { ProposalSummary } from './proposal/summary';
|
||||||
|
import Hash from '../../links/hash';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
|
export type Proposal = components['schemas']['v1BatchProposalSubmission'];
|
||||||
|
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||||
|
|
||||||
|
interface TxBatchProposalProps {
|
||||||
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
|
pubKey: string | undefined;
|
||||||
|
blockData: TendermintBlocksResponse | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const TxBatchProposal = ({
|
||||||
|
txData,
|
||||||
|
pubKey,
|
||||||
|
blockData,
|
||||||
|
}: TxBatchProposalProps) => {
|
||||||
|
if (!txData || !txData.command.batchProposalSubmission) {
|
||||||
|
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||||
|
}
|
||||||
|
let deterministicId = '';
|
||||||
|
|
||||||
|
const proposal: Proposal = txData.command.batchProposalSubmission;
|
||||||
|
const sig = txData?.signature?.value;
|
||||||
|
if (sig) {
|
||||||
|
deterministicId = txSignatureToDeterministicId(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||||
|
<TableCell>{t('Batch proposal')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TxDetailsShared
|
||||||
|
txData={txData}
|
||||||
|
pubKey={pubKey}
|
||||||
|
blockData={blockData}
|
||||||
|
hideTypeRow={true}
|
||||||
|
/>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Batch size')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{proposal.terms?.changes?.length || t('No changes')}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableCell>{t('Proposal ID')}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Hash text={deterministicId} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableWithTbody>
|
||||||
|
{proposal && (
|
||||||
|
<ProposalSummary
|
||||||
|
id={deterministicId}
|
||||||
|
rationale={proposal?.rationale}
|
||||||
|
terms={proposal.terms}
|
||||||
|
batch={proposal.terms?.changes}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -33,6 +33,7 @@ import { TxDetailsApplyReferralCode } from './tx-apply-referral-code';
|
|||||||
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
import { TxDetailsUpdateReferralSet } from './tx-update-referral-set';
|
||||||
import { TxDetailsJoinTeam } from './tx-join-team';
|
import { TxDetailsJoinTeam } from './tx-join-team';
|
||||||
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
|
import { TxDetailsUpdateMarginMode } from './tx-update-margin-mode';
|
||||||
|
import { TxBatchProposal } from './tx-batch-proposal';
|
||||||
|
|
||||||
interface TxDetailsWrapperProps {
|
interface TxDetailsWrapperProps {
|
||||||
txData: BlockExplorerTransactionResult | undefined;
|
txData: BlockExplorerTransactionResult | undefined;
|
||||||
@ -136,6 +137,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
|||||||
return TxDetailsJoinTeam;
|
return TxDetailsJoinTeam;
|
||||||
case 'Update Margin Mode':
|
case 'Update Margin Mode':
|
||||||
return TxDetailsUpdateMarginMode;
|
return TxDetailsUpdateMarginMode;
|
||||||
|
case 'Batch Proposal':
|
||||||
|
return TxBatchProposal;
|
||||||
default:
|
default:
|
||||||
return TxDetailsGeneric;
|
return TxDetailsGeneric;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ export type FilterOption =
|
|||||||
| 'Amend Order'
|
| 'Amend Order'
|
||||||
| 'Apply Referral Code'
|
| 'Apply Referral Code'
|
||||||
| 'Batch Market Instructions'
|
| 'Batch Market Instructions'
|
||||||
|
| 'Batch Proposal'
|
||||||
| 'Cancel LiquidityProvision Order'
|
| 'Cancel LiquidityProvision Order'
|
||||||
| 'Cancel Order'
|
| 'Cancel Order'
|
||||||
| 'Cancel Transfer Funds'
|
| 'Cancel Transfer Funds'
|
||||||
@ -67,7 +68,13 @@ export const filterOptions: Record<string, FilterOption[]> = {
|
|||||||
'Cancel Transfer Funds',
|
'Cancel Transfer Funds',
|
||||||
'Withdraw',
|
'Withdraw',
|
||||||
],
|
],
|
||||||
Governance: ['Delegate', 'Undelegate', 'Vote on Proposal', 'Proposal'],
|
Governance: [
|
||||||
|
'Batch Proposal',
|
||||||
|
'Delegate',
|
||||||
|
'Undelegate',
|
||||||
|
'Vote on Proposal',
|
||||||
|
'Proposal',
|
||||||
|
],
|
||||||
Referrals: [
|
Referrals: [
|
||||||
'Apply Referral Code',
|
'Apply Referral Code',
|
||||||
'Create Referral Set',
|
'Create Referral Set',
|
||||||
|
Loading…
Reference in New Issue
Block a user