fix(explorer): refactor chain event view error handling (#2233)

* feat(explorer): change chain event tx error handling
This commit is contained in:
Edd 2022-11-30 09:40:49 +00:00 committed by GitHub
parent af7a5630ac
commit 96843cd2be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1683 additions and 179 deletions

View File

@ -0,0 +1,453 @@
import { render } from '@testing-library/react';
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
import { ChainEvent } from '.';
const baseMock: Partial<BlockExplorerTransactionResult> = {
block: '1',
index: 1,
hash: '123',
submitter: '123',
type: '1',
code: 1,
cursor: '1',
};
jest.mock('./tx-builtin-deposit', () => ({
TxDetailsChainEventBuiltinDeposit: () => (
<span>TxDetailsChainEventBuiltinDeposit</span>
),
}));
jest.mock('./tx-builtin-withdrawal', () => ({
TxDetailsChainEventBuiltinWithdrawal: () => (
<span>TxDetailsChainEventBuiltinWithdrawal</span>
),
}));
jest.mock('./tx-erc20-deposit', () => ({
TxDetailsChainEventDeposit: () => <span>TxDetailsChainEventDeposit</span>,
}));
jest.mock('./tx-erc20-withdrawal', () => ({
TxDetailsChainEventWithdrawal: () => (
<span>TxDetailsChainEventWithdrawal</span>
),
}));
jest.mock('./tx-erc20-asset-list', () => ({
TxDetailsChainEventErc20AssetList: () => (
<span>TxDetailsChainEventErc20AssetList</span>
),
}));
jest.mock('./tx-erc20-asset-delist', () => ({
TxDetailsChainEventErc20AssetDelist: () => (
<span>TxDetailsChainEventErc20AssetDelist</span>
),
}));
jest.mock('./tx-erc20-asset-limits-updated', () => ({
TxDetailsChainEventErc20AssetLimitsUpdated: () => (
<span>TxDetailsChainEventErc20LimitsUpdated</span>
),
}));
jest.mock('./tx-erc20-bridge-pause', () => ({
TxDetailsChainEventErc20BridgePause: () => (
<span>TxDetailsChainEventErc20BridgeEvent</span>
),
}));
jest.mock('./tx-multisig-signer', () => ({
TxDetailsChainMultisigSigner: () => <span>TxDetailsChainMultisigSigner</span>,
}));
jest.mock('./tx-multisig-threshold', () => ({
TxDetailsChainMultisigThreshold: () => (
<span>TxDetailsChainMultisigThreshold</span>
),
}));
jest.mock('./tx-stake-deposit', () => ({
TxDetailsChainEventStakeDeposit: () => (
<span>TxDetailsChainStakeDeposit</span>
),
}));
jest.mock('./tx-stake-remove', () => ({
TxDetailsChainEventStakeRemove: () => <span>TxDetailsChainStakeRemove</span>,
}));
jest.mock('./tx-stake-totalsupply', () => ({
TxDetailsChainEventStakeTotalSupply: () => (
<span>TxDetailsChainStakeTotalSupply</span>
),
}));
describe('Chain Event: Chain Event selects the right component for the event', () => {
it('Returns a Built In Deposit event for a built in deposit', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
builtin: {
deposit: {
amount: '',
partyId: '',
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainEventBuiltinDeposit')).toBeVisible();
});
it('Returns a Built In Deposit event for a built in withdrawal', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
builtin: {
withdrawal: {
amount: '',
partyId: '',
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(
screen.getByText('TxDetailsChainEventBuiltinWithdrawal')
).toBeVisible();
});
it('Returns a Built In Deposit event for an ERC20 deposit', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
deposit: {
amount: '',
targetPartyId: '',
sourceEthereumAddress: '',
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainEventDeposit')).toBeVisible();
});
it('Returns a Built In Deposit event for an ERC20 withdrawal', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
withdrawal: {
referenceNonce: '',
targetEthereumAddress: '',
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainEventWithdrawal')).toBeVisible();
});
it('Returns a Asset List event view for a list', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
assetList: {
assetSource: '',
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainEventErc20AssetList')).toBeVisible();
});
it('Returns a Asset Delist event view for a delist', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
assetDelist: {
vegaAssetId: '',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(
screen.getByText('TxDetailsChainEventErc20AssetDelist')
).toBeVisible();
});
it('Returns a Asset Limits Update event view for a update', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
assetLimitsUpdated: {
lifetimeLimits: '100',
withdrawThreshold: '100',
vegaAssetId: '',
sourceEthereumAddress: '0x000',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(
screen.getByText('TxDetailsChainEventErc20LimitsUpdated')
).toBeVisible();
});
it('Returns a Bridge Pause event view when a pause event happens', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
bridgeStopped: true,
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(
screen.getByText('TxDetailsChainEventErc20BridgeEvent')
).toBeVisible();
});
it('Returns a Bridge Pause event view when a resume event happens', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20: {
bridgeResumed: true,
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(
screen.getByText('TxDetailsChainEventErc20BridgeEvent')
).toBeVisible();
});
it('Returns a multsig signer view when a multisig signer is added', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20Multisig: {
signerAdded: {
blockTime: '100',
newSigner: '0x000',
nonce: '123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainMultisigSigner')).toBeVisible();
});
it('Returns a multsig signer view when a multisig signer is removed', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20Multisig: {
signerRemoved: {
blockTime: '100',
oldSigner: '0x000',
nonce: '123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainMultisigSigner')).toBeVisible();
});
it('Returns a multsig signer view when a multisig threshold change event occurs', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
erc20Multisig: {
thresholdSet: {
blockTime: '100',
newThreshold: 100,
nonce: '123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainMultisigThreshold')).toBeVisible();
});
it('Returns a stake deposit view when a stake arrives', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
stakingEvent: {
stakeDeposited: {
blockTime: '100',
amount: '100',
vegaPublicKey: '12345',
ethereumAddress: '0x123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainStakeDeposit')).toBeVisible();
});
it('Returns a stake removed view when a stake goes', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
stakingEvent: {
stakeRemoved: {
blockTime: '100',
amount: '100',
vegaPublicKey: '12345',
ethereumAddress: '0x123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainStakeRemove')).toBeVisible();
});
it('Returns a stake total supply view when the supply of the staking asset changes', () => {
const commandMock: Partial<BlockExplorerTransactionResult> = {
command: {
chainEvent: {
stakingEvent: {
totalSupply: {
totalSupply: '12345',
tokenAddress: '0x123',
},
},
},
},
};
const mock = Object.assign(
{},
baseMock,
commandMock
) as BlockExplorerTransactionResult;
const screen = render(<ChainEvent txData={mock} />);
expect(screen.getByText('TxDetailsChainStakeTotalSupply')).toBeVisible();
});
});

View File

@ -1,10 +1,10 @@
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
import { TxDetailsChainEventBuiltinDeposit } from './tx-builtin-deposit';
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
import { TxDetailsChainEventStakeDeposit } from './tx-stake-deposit';
import { TxDetailsChainEventStakeRemove } from './tx-stake-remove';
import { TxDetailsChainEventStakeTotalSupply } from './tx-stake-totalsupply';
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
import { TxDetailsChainEventErc20AssetList } from './tx-erc20-asset-list';
import { TxDetailsChainEventErc20AssetLimitsUpdated } from './tx-erc20-asset-limits-updated';
import { TxDetailsChainEventErc20BridgePause } from './tx-erc20-bridge-pause';
@ -13,6 +13,8 @@ import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
import isUndefined from 'lodash/isUndefined';
import type { BlockExplorerTransactionResult } from '../../../../routes/types/block-explorer-response';
import { TxDetailsChainEventWithdrawal } from './tx-erc20-withdrawal';
import { TxDetailsChainEventErc20AssetDelist } from './tx-erc20-asset-delist';
interface ChainEventProps {
txData: BlockExplorerTransactionResult | undefined;
@ -32,54 +34,58 @@ interface ChainEventProps {
* @returns React.JSXElement
*/
export const ChainEvent = ({ txData }: ChainEventProps) => {
const e = txData?.command.chainEvent;
if (!e) {
if (!txData?.command.chainEvent) {
return null;
}
const { builtin, erc20, erc20Multisig, stakingEvent } =
txData.command.chainEvent;
// Builtin Asset events
if (e.builtin) {
if (e.builtin.deposit) {
return <TxDetailsChainEventBuiltinDeposit deposit={e.builtin.deposit} />;
if (builtin) {
if (builtin.deposit) {
return <TxDetailsChainEventBuiltinDeposit deposit={builtin.deposit} />;
}
if (e.builtin?.withdrawal) {
if (builtin?.withdrawal) {
return (
<TxDetailsChainEventBuiltinWithdrawal
withdrawal={e.builtin?.withdrawal}
withdrawal={builtin?.withdrawal}
/>
);
}
}
// ERC20 asset events
if (e.erc20) {
if (e.erc20.deposit) {
return <TxDetailsChainEventDeposit deposit={e.erc20.deposit} />;
if (erc20) {
if (erc20.deposit) {
return <TxDetailsChainEventDeposit deposit={erc20.deposit} />;
}
if (e.erc20.withdrawal) {
if (erc20.withdrawal) {
return <TxDetailsChainEventWithdrawal withdrawal={erc20.withdrawal} />;
}
if (erc20.assetList) {
return <TxDetailsChainEventErc20AssetList assetList={erc20.assetList} />;
}
if (erc20.assetDelist) {
return (
<TxDetailsChainEventBuiltinWithdrawal withdrawal={e.erc20.withdrawal} />
<TxDetailsChainEventErc20AssetDelist assetDelist={erc20.assetDelist} />
);
}
if (e.erc20.assetList) {
return (
<TxDetailsChainEventErc20AssetList assetList={e.erc20.assetList} />
);
}
if (e.erc20.assetLimitsUpdated) {
if (erc20.assetLimitsUpdated) {
return (
<TxDetailsChainEventErc20AssetLimitsUpdated
assetLimitsUpdated={e.erc20.assetLimitsUpdated}
assetLimitsUpdated={erc20.assetLimitsUpdated}
/>
);
}
const bridgeStopped = e.erc20.bridgeStopped;
const bridgeResumed = e.erc20.bridgeResumed;
const bridgeStopped = erc20.bridgeStopped;
const bridgeResumed = erc20.bridgeResumed;
if (!isUndefined(bridgeStopped) || !isUndefined(bridgeResumed)) {
const isPaused = bridgeStopped === false || bridgeResumed === true;
return <TxDetailsChainEventErc20BridgePause isPaused={isPaused} />;
@ -87,48 +93,48 @@ export const ChainEvent = ({ txData }: ChainEventProps) => {
}
// ERC20 multisig events
if (e.erc20Multisig) {
if (e.erc20Multisig.thresholdSet) {
if (erc20Multisig) {
if (erc20Multisig.thresholdSet) {
return (
<TxDetailsChainMultisigThreshold
thresholdSet={e.erc20Multisig.thresholdSet}
thresholdSet={erc20Multisig.thresholdSet}
/>
);
}
if (e.erc20Multisig.signerAdded) {
if (erc20Multisig.signerAdded) {
return (
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerAdded} />
<TxDetailsChainMultisigSigner signer={erc20Multisig.signerAdded} />
);
}
if (e.erc20Multisig.signerRemoved) {
if (erc20Multisig.signerRemoved) {
return (
<TxDetailsChainMultisigSigner signer={e.erc20Multisig.signerRemoved} />
<TxDetailsChainMultisigSigner signer={erc20Multisig.signerRemoved} />
);
}
}
// Staking events
if (e.stakingEvent) {
if (e.stakingEvent.stakeDeposited) {
if (stakingEvent) {
if (stakingEvent.stakeDeposited) {
return (
<TxDetailsChainEventStakeDeposit
deposit={e.stakingEvent.stakeDeposited}
deposit={stakingEvent.stakeDeposited}
/>
);
}
if (e.stakingEvent.stakeRemoved) {
if (stakingEvent.stakeRemoved) {
return (
<TxDetailsChainEventStakeRemove remove={e.stakingEvent.stakeRemoved} />
<TxDetailsChainEventStakeRemove remove={stakingEvent.stakeRemoved} />
);
}
if (e.stakingEvent.totalSupply) {
if (stakingEvent.totalSupply) {
return (
<TxDetailsChainEventStakeTotalSupply
update={e.stakingEvent.totalSupply}
update={stakingEvent.totalSupply}
/>
);
}

View File

@ -0,0 +1,14 @@
import { getBlockTime } from './get-block-time';
describe('Lib: getBlockTime', () => {
it('- gets returned if nothing is provided', () => {
const res = getBlockTime();
expect(res).toEqual('-');
});
it('Returns a known date string', () => {
const mockBlockTime = '1669223762';
const usRes = getBlockTime(mockBlockTime, 'en-US');
expect(usRes).toEqual('11/23/2022, 5:16:02 PM');
});
});

View File

@ -4,13 +4,22 @@
* @param date String or null date
* @returns String date in locale time
*/
export function getBlockTime(date?: string) {
if (!date) {
export function getBlockTime(date?: string, locale?: Intl.LocalesArgument) {
try {
if (!date) {
throw new Error('No date provided');
}
const timeInSeconds = parseInt(date, 10);
if (isNaN(timeInSeconds)) {
throw new Error('Invalid date');
}
const timeInMs = timeInSeconds * 1000;
return new Date(timeInMs).toLocaleString(locale);
} catch (e) {
return '-';
}
const timeInSeconds = parseInt(date, 10);
const timeInMs = timeInSeconds * 1000;
return new Date(timeInMs).toLocaleString();
}

View File

@ -0,0 +1,78 @@
import { render } from '@testing-library/react';
import { TxDetailsChainEventBuiltinDeposit } from './tx-builtin-deposit';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
type Deposit = components['schemas']['vegaBuiltinAssetDeposit'];
const fullMock: Deposit = {
partyId: 'party123',
vegaAssetId: 'asset123',
amount: 'amount123',
};
describe('Chain Event: Builtin asset deposit', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Deposit;
const screen = render(<TxDetailsChainEventBuiltinDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Deposit = {};
const screen = render(<TxDetailsChainEventBuiltinDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventBuiltinDeposit deposit={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventBuiltinDeposit deposit={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('Built-in asset deposit'))).toBeInTheDocument();
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
});

View File

@ -15,26 +15,26 @@ interface TxDetailsChainEventBuiltinDepositProps {
export const TxDetailsChainEventBuiltinDeposit = ({
deposit,
}: TxDetailsChainEventBuiltinDepositProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!deposit || !deposit.partyId || !deposit.vegaAssetId || !deposit.amount) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('Built-in asset deposit')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={deposit.partyId || ''} />
<PartyLink id={deposit.partyId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId || ''} /> ({t('built in asset')})
<AssetLink id={deposit.vegaAssetId} /> ({t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">

View File

@ -0,0 +1,84 @@
import { render } from '@testing-library/react';
import { TxDetailsChainEventBuiltinWithdrawal } from './tx-builtin-withdrawal';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
type Withdrawal = components['schemas']['vegaBuiltinAssetWithdrawal'];
const fullMock: Withdrawal = {
partyId: 'party123',
vegaAssetId: 'asset123',
amount: 'amount123',
};
describe('Chain Event: Builtin asset withdrawal', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Withdrawal;
const screen = render(
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Withdrawal = {};
const screen = render(
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventBuiltinWithdrawal withdrawal={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventBuiltinWithdrawal withdrawal={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('Built-in asset withdrawal'))
).toBeInTheDocument();
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
});

View File

@ -15,14 +15,19 @@ interface TxDetailsChainEventBuiltinDepositProps {
export const TxDetailsChainEventBuiltinWithdrawal = ({
withdrawal,
}: TxDetailsChainEventBuiltinDepositProps) => {
if (!withdrawal) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!withdrawal ||
!withdrawal.partyId ||
!withdrawal.vegaAssetId ||
!withdrawal.amount
) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('Built-in asset withdrawal')}</TableCell>
</TableRow>
<TableRow modifier="bordered">

View File

@ -0,0 +1,68 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventErc20AssetDelist } from './tx-erc20-asset-delist';
type Delist = components['schemas']['vegaERC20AssetDelist'];
const fullMock: Delist = {
vegaAssetId: 'asset123',
};
describe('Chain Event: ERC20 Asset Delist', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Delist;
const screen = render(
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Delist = {};
const screen = render(
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventErc20AssetDelist assetDelist={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventErc20AssetDelist assetDelist={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 asset removed'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
});
});

View File

@ -16,8 +16,8 @@ interface TxDetailsChainEventErc20AssetDelistProps {
export const TxDetailsChainEventErc20AssetDelist = ({
assetDelist,
}: TxDetailsChainEventErc20AssetDelistProps) => {
if (!assetDelist) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!assetDelist || !assetDelist.vegaAssetId) {
return null;
}
return (

View File

@ -0,0 +1,90 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventErc20AssetLimitsUpdated } from './tx-erc20-asset-limits-updated';
type AssetLimitsUpdated = components['schemas']['vegaERC20AssetLimitsUpdated'];
const fullMock: AssetLimitsUpdated = {
sourceEthereumAddress: 'eth123',
vegaAssetId: 'asset123',
lifetimeLimits: '100',
withdrawThreshold: '60',
};
describe('Chain Event: ERC20 Asset limits updated', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as AssetLimitsUpdated;
const screen = render(
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: AssetLimitsUpdated = {};
const screen = render(
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventErc20AssetLimitsUpdated assetLimitsUpdated={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventErc20AssetLimitsUpdated
assetLimitsUpdated={fullMock}
/>
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('ERC20 asset limits update'))
).toBeInTheDocument();
expect(screen.getByText(t('Total lifetime limit'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.lifetimeLimits}`)).toBeInTheDocument();
expect(
screen.getByText(t('Asset withdrawal threshold'))
).toBeInTheDocument();
expect(
screen.getByText(`${fullMock.withdrawThreshold}`)
).toBeInTheDocument();
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
});

View File

@ -23,33 +23,35 @@ interface TxDetailsChainEventErc20AssetLimitsUpdatedProps {
export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
assetLimitsUpdated,
}: TxDetailsChainEventErc20AssetLimitsUpdatedProps) => {
if (!assetLimitsUpdated) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!assetLimitsUpdated ||
!assetLimitsUpdated.sourceEthereumAddress ||
!assetLimitsUpdated.vegaAssetId ||
!assetLimitsUpdated.lifetimeLimits ||
!assetLimitsUpdated.withdrawThreshold
) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 asset limits updated')}</TableCell>
<TableCell>{t('ERC20 asset limits update')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
{assetLimitsUpdated.sourceEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('ERC20 asset')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetLimitsUpdated.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetLimitsUpdated.vegaAssetId || ''} />
<AssetLink id={assetLimitsUpdated.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">

View File

@ -0,0 +1,76 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventErc20AssetList } from './tx-erc20-asset-list';
type List = components['schemas']['vegaERC20AssetList'];
const fullMock: List = {
vegaAssetId: 'asset123',
assetSource: 'eth123',
};
describe('Chain Event: ERC20 Asset List', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as List;
const screen = render(
<TxDetailsChainEventErc20AssetList assetList={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: List = {};
const screen = render(
<TxDetailsChainEventErc20AssetList assetList={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventErc20AssetList assetList={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventErc20AssetList assetList={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 asset added'))).toBeInTheDocument();
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.assetSource}`
);
});
});

View File

@ -19,8 +19,8 @@ interface TxDetailsChainEventErc20AssetListProps {
export const TxDetailsChainEventErc20AssetList = ({
assetList,
}: TxDetailsChainEventErc20AssetListProps) => {
if (!assetList) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!assetList || !assetList.assetSource || !assetList.vegaAssetId) {
return null;
}
return (
@ -29,21 +29,19 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 asset added')}</TableCell>
</TableRow>
{assetList.assetSource ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetList.assetSource}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={assetList.assetSource}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Added Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetList.vegaAssetId || ''} />
<AssetLink id={assetList.vegaAssetId} />
</TableCell>
</TableRow>
</>

View File

@ -0,0 +1,31 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import { TxDetailsChainEventErc20BridgePause } from './tx-erc20-bridge-pause';
describe('Chain Event: ERC20 bridge pause', () => {
it('Renders pause if paused', () => {
const screen = render(
<table>
<tbody>
<TxDetailsChainEventErc20BridgePause isPaused={true} />
</tbody>
</table>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 bridge pause'))).toBeInTheDocument();
});
it('Renders unpause if resumed', () => {
const screen = render(
<table>
<tbody>
<TxDetailsChainEventErc20BridgePause isPaused={false} />
</tbody>
</table>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 bridge unpause'))).toBeInTheDocument();
});
});

View File

@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
interface TxDetailsChainEventErc20BridgePauseProps {
export interface TxDetailsChainEventErc20BridgePauseProps {
isPaused: boolean;
}
@ -13,7 +13,7 @@ interface TxDetailsChainEventErc20BridgePauseProps {
export const TxDetailsChainEventErc20BridgePause = ({
isPaused,
}: TxDetailsChainEventErc20BridgePauseProps) => {
const event = isPaused ? 'pause' : 'unpaused';
const event = isPaused ? 'pause' : 'unpause';
return (
<TableRow modifier="bordered">

View File

@ -0,0 +1,83 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventDeposit } from './tx-erc20-deposit';
type Deposit = components['schemas']['vegaERC20Deposit'];
const fullMock: Deposit = {
vegaAssetId: 'asset123',
amount: 'amount123',
sourceEthereumAddress: 'eth123',
targetPartyId: 'vega123',
};
describe('Chain Event: ERC20 asset deposit', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Deposit;
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Deposit = {};
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(<TxDetailsChainEventDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventDeposit deposit={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 deposit'))).toBeInTheDocument();
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.vegaAssetId}`)).toBeInTheDocument();
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
`/parties/${fullMock.targetPartyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
});

View File

@ -17,8 +17,14 @@ interface TxDetailsChainEventProps {
export const TxDetailsChainEventDeposit = ({
deposit,
}: TxDetailsChainEventProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!deposit ||
!deposit.sourceEthereumAddress ||
!deposit.targetPartyId ||
!deposit.vegaAssetId ||
!deposit.amount
) {
return null;
}
return (
@ -27,27 +33,25 @@ export const TxDetailsChainEventDeposit = ({
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 deposit')}</TableCell>
</TableRow>
{deposit.sourceEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={deposit.sourceEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={deposit.targetPartyId || ''} />
<PartyLink id={deposit.targetPartyId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId || ''} />
<AssetLink id={deposit.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">

View File

@ -0,0 +1,71 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventWithdrawal } from './tx-erc20-withdrawal';
type Withdrawal = components['schemas']['vegaERC20Withdrawal'];
const fullMock: Partial<Withdrawal> = {
vegaAssetId: 'asset123',
targetEthereumAddress: 'eth123',
};
describe('Chain Event: ERC20 asset deposit', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Withdrawal;
const screen = render(<TxDetailsChainEventWithdrawal withdrawal={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Withdrawal = {};
const screen = render(<TxDetailsChainEventWithdrawal withdrawal={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventWithdrawal withdrawal={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventWithdrawal withdrawal={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('ERC20 withdrawal'))).toBeInTheDocument();
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.targetEthereumAddress}`
);
});
});

View File

@ -17,8 +17,12 @@ interface TxDetailsChainEventWithdrawalProps {
export const TxDetailsChainEventWithdrawal = ({
withdrawal,
}: TxDetailsChainEventWithdrawalProps) => {
if (!withdrawal) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!withdrawal ||
!withdrawal.targetEthereumAddress ||
!withdrawal.vegaAssetId
) {
return null;
}
return (
@ -28,22 +32,20 @@ export const TxDetailsChainEventWithdrawal = ({
<TableCell>{t('ERC20 withdrawal')}</TableCell>
</TableRow>
{withdrawal.targetEthereumAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<EthExplorerLink
id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<EthExplorerLink
id={withdrawal.targetEthereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} />
<AssetLink id={withdrawal.vegaAssetId} />
</TableCell>
</TableRow>
</>

View File

@ -0,0 +1,101 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainMultisigSigner } from './tx-multisig-signer';
import { getBlockTime } from './lib/get-block-time';
type Added = components['schemas']['vegaERC20SignerAdded'];
type Removed = components['schemas']['vegaERC20SignerRemoved'];
const mockBlockTime = '1669631323';
describe('Chain Event: multisig signer change', () => {
it('Copes with a poorly formatted time prop', () => {
const addedMock: Added = {
newSigner: 'eth123',
nonce: 'nonce123',
blockTime: 'you shall not parse',
};
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainMultisigSigner signer={addedMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
expect(screen.getByText('-')).toBeInTheDocument();
});
it('Renders addedSigner correctly', () => {
const addedMock: Added = {
newSigner: 'eth123',
nonce: 'nonce123',
blockTime: mockBlockTime,
};
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainMultisigSigner signer={addedMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('Add ERC20 bridge multisig signer'))
).toBeInTheDocument();
expect(screen.getByText(t('Add signer'))).toBeInTheDocument();
expect(screen.getByText(`${addedMock.newSigner}`)).toBeInTheDocument();
const expectedDate = getBlockTime(mockBlockTime);
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
expect(screen.getByText(expectedDate)).toBeInTheDocument();
});
it('Renders TableRows if all data is provided', () => {
const removedMock: Removed = {
oldSigner: 'eth123',
nonce: 'nonce123',
blockTime: mockBlockTime,
};
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainMultisigSigner signer={removedMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('Remove ERC20 bridge multisig signer'))
).toBeInTheDocument();
expect(screen.getByText(t('Remove signer'))).toBeInTheDocument();
expect(screen.getByText(`${removedMock.oldSigner}`)).toBeInTheDocument();
const expectedDate = getBlockTime(mockBlockTime);
expect(screen.getByText(t('Signer change at'))).toBeInTheDocument();
expect(screen.getByText(expectedDate)).toBeInTheDocument();
});
});

View File

@ -15,8 +15,8 @@ interface TxDetailsChainMultisigSignerProps {
export const TxDetailsChainMultisigSigner = ({
signer,
}: TxDetailsChainMultisigSignerProps) => {
if (!signer) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!signer || !signer.blockTime) {
return null;
}
const blockTime = getBlockTime(signer.blockTime);
@ -30,10 +30,12 @@ export const TxDetailsChainMultisigSigner = ({
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
{'newSigner' in signer
? t('Add ERC20 bridge multisig signer')
: t('Remove ERC20 bridge multsig signer')}
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>
{'newSigner' in signer
? t('Add ERC20 bridge multisig signer')
: t('Remove ERC20 bridge multisig signer')}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>

View File

@ -0,0 +1,82 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainMultisigThreshold } from './tx-multisig-threshold';
import omit from 'lodash/omit';
import { getBlockTime } from './lib/get-block-time';
type Threshold =
components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
const mockBlockTime = '1669631323';
// Note: nonce is missing from this partial because the component does not render
// the nonce currently. It could render the nonce, at which point it can be added
// here.
const fullMock: Partial<Threshold> = {
blockTime: mockBlockTime,
newThreshold: 667,
};
describe('Chain Event: multisig threshold change', () => {
it('Copes with a poorly formatted time prop', () => {
const mockWithBadTime: Threshold = {
blockTime: '-',
newThreshold: 1000,
nonce: 'nonce123',
};
const screen = render(
<table>
<tbody>
<TxDetailsChainMultisigThreshold thresholdSet={mockWithBadTime} />
</tbody>
</table>
);
expect(screen.getByText(t('Threshold change date'))).toBeInTheDocument();
expect(screen.getByText('-')).toBeInTheDocument();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainMultisigThreshold thresholdSet={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const mock: Threshold = Object.assign({}, fullMock, {
nonce: 'nonce123',
});
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainMultisigThreshold thresholdSet={mock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('ERC20 multisig threshold set'))
).toBeInTheDocument();
expect(screen.getByText(t('Threshold'))).toBeInTheDocument();
expect(screen.getByText(`66.7%`)).toBeInTheDocument();
const expectedDate = getBlockTime(mockBlockTime);
expect(screen.getByText(t('Threshold change date'))).toBeInTheDocument();
expect(screen.getByText(expectedDate)).toBeInTheDocument();
});
});

View File

@ -2,23 +2,7 @@ import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import isNumber from 'lodash/isNumber';
/**
* Returns a reasonably formatted time from unix timestamp of block height
*
* @param date String or null date
* @returns String date in locale time
*/
function getBlockTime(date?: string) {
if (!date) {
return '-';
}
const timeInSeconds = parseInt(date, 10);
const timeInMs = timeInSeconds * 1000;
return new Date(timeInMs).toLocaleString();
}
import { getBlockTime } from './lib/get-block-time';
interface TxDetailsChainMultisigThresholdProps {
thresholdSet: components['schemas']['vegaERC20MultiSigEvent']['thresholdSet'];
@ -27,12 +11,15 @@ interface TxDetailsChainMultisigThresholdProps {
/**
* Someone updated multsig threshold value on the smart contract.
* It's a percentage, with 1000 being 100% and 0 being 0%.
*
* - Nonce is not rendered. It's in the full transaction details thing
* in case anyone really wants it, but for now it feels like detail we don't need
*/
export const TxDetailsChainMultisigThreshold = ({
thresholdSet,
}: TxDetailsChainMultisigThresholdProps) => {
if (!thresholdSet) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!thresholdSet || !thresholdSet.blockTime || !thresholdSet.newThreshold) {
return null;
}
const blockTime = getBlockTime(thresholdSet.blockTime);
@ -43,7 +30,7 @@ export const TxDetailsChainMultisigThreshold = ({
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('ERC20 multisig threshold set')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
@ -51,7 +38,7 @@ export const TxDetailsChainMultisigThreshold = ({
<TableCell>{threshold}%</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Threshold set from')}</TableCell>
<TableCell>{t('Threshold change date')}</TableCell>
<TableCell>{blockTime}</TableCell>
</TableRow>
</>

View File

@ -0,0 +1,78 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventStakeDeposit } from './tx-stake-deposit';
type Deposit = components['schemas']['vegaStakeDeposited'];
const fullMock: Deposit = {
amount: 'amount123',
blockTime: 'block123',
ethereumAddress: 'eth123',
vegaPublicKey: 'vega123',
};
describe('Chain Event: Stake deposit', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Deposit;
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Deposit = {};
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(<TxDetailsChainEventStakeDeposit deposit={mock} />);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventStakeDeposit deposit={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('Stake deposit'))).toBeInTheDocument();
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
expect(screen.getByText(t('Deposited at'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.blockTime}`)).toBeInTheDocument();
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
});

View File

@ -2,33 +2,48 @@ import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeDepositProps {
deposit: components['schemas']['vegaStakeDeposited'];
}
/**
* Someone addedd some stake for a particular party
* Someone added some stake for a particular party
* This should link to the Governance asset, but doesn't
* as that would require checking the Network Paramters
* as that would require checking the Network Parameters
* Ethereum address should also be a link to an ETH block explorer
*/
export const TxDetailsChainEventStakeDeposit = ({
deposit,
}: TxDetailsChainEventStakeDepositProps) => {
if (!deposit) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!deposit ||
!deposit.ethereumAddress ||
!deposit.vegaPublicKey ||
!deposit.amount ||
!deposit.blockTime
) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Stake deposited')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('Stake deposit')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>{deposit.ethereumAddress || ''}</TableCell>
<TableCell>
<EthExplorerLink
id={deposit.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
@ -42,7 +57,7 @@ export const TxDetailsChainEventStakeDeposit = ({
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Deposited at')}</TableCell>
<TableCell>{deposit.amount}</TableCell>
<TableCell>{deposit.blockTime}</TableCell>
</TableRow>
</>
);

View File

@ -0,0 +1,78 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventStakeRemove } from './tx-stake-remove';
type Remove = components['schemas']['vegaStakeRemoved'];
const fullMock: Remove = {
amount: 'amount123',
blockTime: 'block123',
ethereumAddress: 'eth123',
vegaPublicKey: 'vega123',
};
describe('Chain Event: Stake remove', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as Remove;
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: Remove = {};
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(<TxDetailsChainEventStakeRemove remove={mock} />);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventStakeRemove remove={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(screen.getByText(t('Stake remove'))).toBeInTheDocument();
expect(screen.getByText(t('Amount'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.amount}`)).toBeInTheDocument();
expect(screen.getByText(t('Removed at'))).toBeInTheDocument();
expect(screen.getByText(`${fullMock.blockTime}`)).toBeInTheDocument();
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
});

View File

@ -2,6 +2,10 @@ import { t } from '@vegaprotocol/react-helpers';
import { TableRow, TableCell } from '../../../table';
import type { components } from '../../../../../types/explorer';
import { PartyLink } from '../../../links';
import {
EthExplorerLink,
EthExplorerLinkTypes,
} from '../../../links/eth-explorer-link/eth-explorer-link';
interface TxDetailsChainEventStakeRemoveProps {
remove: components['schemas']['vegaStakeRemoved'];
@ -16,24 +20,35 @@ interface TxDetailsChainEventStakeRemoveProps {
export const TxDetailsChainEventStakeRemove = ({
remove,
}: TxDetailsChainEventStakeRemoveProps) => {
if (!remove) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (
!remove ||
!remove.ethereumAddress ||
!remove.vegaPublicKey ||
!remove.amount ||
!remove.blockTime
) {
return null;
}
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Stake removed')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('Stake remove')}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>{remove.ethereumAddress || ''}</TableCell>
<TableCell>
<EthExplorerLink
id={remove.ethereumAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Recipient')}</TableCell>
<TableCell>
<PartyLink id={remove.vegaPublicKey || ''} />
<PartyLink id={remove.vegaPublicKey} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@ -41,7 +56,7 @@ export const TxDetailsChainEventStakeRemove = ({
<TableCell>{remove.amount}</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Deposited at')}</TableCell>
<TableCell>{t('Removed at')}</TableCell>
<TableCell>{remove.blockTime}</TableCell>
</TableRow>
</>

View File

@ -0,0 +1,73 @@
import { render } from '@testing-library/react';
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import omit from 'lodash/omit';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
import { TxDetailsChainEventStakeTotalSupply } from './tx-stake-totalsupply';
type TotalSupply = components['schemas']['vegaStakeTotalSupply'];
const fullMock: TotalSupply = {
totalSupply: '123000000000000000000000',
tokenAddress: 'eth123',
};
describe('Chain Event: Stake total supply change', () => {
it('Renders nothing if no good data is provided', () => {
const mock = undefined as unknown as TotalSupply;
const screen = render(
<TxDetailsChainEventStakeTotalSupply update={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Renders nothing if correct type with no data is provided', () => {
const mock: TotalSupply = {};
const screen = render(
<TxDetailsChainEventStakeTotalSupply update={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it(`Renders nothing if correct type with partial data is provided`, () => {
for (const key in fullMock) {
const mock = omit(fullMock, key);
const screen = render(
<TxDetailsChainEventStakeTotalSupply update={mock} />
);
expect(screen.container).toBeEmptyDOMElement();
}
});
it('Renders TableRows if all data is provided', () => {
const screen = render(
<MockedProvider>
<MemoryRouter>
<table>
<tbody>
<TxDetailsChainEventStakeTotalSupply update={fullMock} />
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
expect(screen.getByText(t('Chain event type'))).toBeInTheDocument();
expect(
screen.getByText(t('Stake total supply update'))
).toBeInTheDocument();
expect(screen.getByText(t('Total supply'))).toBeInTheDocument();
expect(screen.getByText('123,000')).toBeInTheDocument();
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
expect(ethLink.getAttribute('href')).toContain(
`/address/${fullMock.tokenAddress}`
);
});
});

View File

@ -18,33 +18,32 @@ interface TxDetailsChainEventStakeTotalSupplyProps {
export const TxDetailsChainEventStakeTotalSupply = ({
update,
}: TxDetailsChainEventStakeTotalSupplyProps) => {
if (!update) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
if (!update || !update.tokenAddress || !update.totalSupply) {
return null;
}
let totalSupply = update.totalSupply || '';
if (totalSupply.length > 0) {
totalSupply = formatNumber(toBigNum(totalSupply, 18));
}
const totalSupply =
update.totalSupply.length > 0
? formatNumber(toBigNum(update.totalSupply, 18))
: update.totalSupply;
return (
<>
<TableRow modifier="bordered">
<TableCell>{t('Chain Event type')}</TableCell>
<TableCell>{t('Chain event type')}</TableCell>
<TableCell>{t('Stake total supply update')}</TableCell>
</TableRow>
{update.tokenAddress ? (
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={update.tokenAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
) : null}
<TableRow modifier="bordered">
<TableCell>{t('Source')}</TableCell>
<TableCell>
<EthExplorerLink
id={update.tokenAddress}
type={EthExplorerLinkTypes.address}
/>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Total supply')}</TableCell>
<TableCell>{totalSupply}</TableCell>

View File

@ -75,7 +75,7 @@ export function getLabelForChainEvent(
return t('Staking event');
} else if (chainEvent.erc20Multisig) {
if (chainEvent.erc20Multisig.signerAdded) {
return t('Signer adde');
return t('Signer added');
} else if (chainEvent.erc20Multisig.signerRemoved) {
return t('Signer remove');
} else if (chainEvent.erc20Multisig.thresholdSet) {