mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-21 17:37:01 +01:00
New BlockaidBannerAlert component (#20051)
This commit is contained in:
parent
e030f28ba0
commit
737173ed5a
24
app/_locales/en/messages.json
generated
24
app/_locales/en/messages.json
generated
@ -565,6 +565,30 @@
|
||||
"blockaid": {
|
||||
"message": "Blockaid"
|
||||
},
|
||||
"blockaidDescriptionApproveFarming": {
|
||||
"message": "If you approve this request, a third party known for scams might take all your assets."
|
||||
},
|
||||
"blockaidDescriptionBlurFarming": {
|
||||
"message": "If you approve this request, someone can steal your assets listed on Blur."
|
||||
},
|
||||
"blockaidDescriptionMaliciousDomain": {
|
||||
"message": "You're interacting with a malicious domain. If you approve this request, you might lose your assets."
|
||||
},
|
||||
"blockaidDescriptionMightLoseAssets": {
|
||||
"message": "If you approve this request, you might lose your assets."
|
||||
},
|
||||
"blockaidDescriptionSeaportFarming": {
|
||||
"message": "If you approve this request, someone can steal your assets listed on OpenSea."
|
||||
},
|
||||
"blockaidDescriptionTransferFarming": {
|
||||
"message": "If you approve this request, a third party known for scams will take all your assets."
|
||||
},
|
||||
"blockaidTitleDeceptive": {
|
||||
"message": "This is a deceptive request"
|
||||
},
|
||||
"blockaidTitleSuspicious": {
|
||||
"message": "This is a suspicious request"
|
||||
},
|
||||
"blockies": {
|
||||
"message": "Blockies"
|
||||
},
|
||||
|
@ -19,6 +19,42 @@ export const SECURITY_PROVIDER_CONFIG: Readonly<SecurityProviderConfig> = {
|
||||
},
|
||||
};
|
||||
|
||||
/** The reason, also referred to as the attack type, provided in the PPOM Response */
|
||||
export enum BlockaidReason {
|
||||
/** Approval for a malicious spender */
|
||||
approvalFarming = 'approval_farming',
|
||||
/** Malicious signature on Blur order */
|
||||
blurFarming = 'blur_farming',
|
||||
/** A known malicous site invoked that transaction */
|
||||
maliciousDomain = 'malicious_domain',
|
||||
/** Malicious signature on a Permit order */
|
||||
permitFarming = 'permit_farming',
|
||||
/** Direct theft of native assets (ETH/MATIC/AVAX/ etc …) */
|
||||
rawNativeTokenTransfer = 'raw_native_token_transfer',
|
||||
/** Malicious raw signature from the user */
|
||||
rawSignatureFarming = 'raw_signature_farming',
|
||||
/** Malicious signature on a Seaport order */
|
||||
seaportFarming = 'seaport_farming',
|
||||
/** setApprovalForAll for a malicious operator */
|
||||
setApprovalForAll = 'set_approval_for_all',
|
||||
/** Malicious signature on other type of trade order (Zero-X / Rarible / etc..) */
|
||||
tradeOrderFarming = 'trade_order_farming',
|
||||
/** Direct theft of assets using transfer */
|
||||
transferFarming = 'transfer_farming',
|
||||
/** Direct theft of assets using transferFrom */
|
||||
transferFromFarming = 'transfer_from_farming',
|
||||
/** Malicious trade that results in the victim being rained */
|
||||
unfairTrade = 'unfair_trade',
|
||||
|
||||
other = 'other',
|
||||
}
|
||||
|
||||
export enum BlockaidResultType {
|
||||
Malicious = 'Malicious',
|
||||
Warning = 'Warning',
|
||||
Benign = 'Benign',
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} SecurityProviderMessageSeverity
|
||||
* @property {0} NOT_MALICIOUS - Indicates message is not malicious
|
||||
|
@ -0,0 +1,179 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Blockaid Banner Alert should render 'danger' UI when ppomResponse.resultType is 'Malicious 1`] = `
|
||||
<div
|
||||
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-danger box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-error-muted box--rounded-sm"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-error-default"
|
||||
style="mask-image: url('./images/icons/danger.svg');"
|
||||
/>
|
||||
<div>
|
||||
<h5
|
||||
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
|
||||
data-testid="mm-banner-base-title"
|
||||
>
|
||||
This is a deceptive request
|
||||
</h5>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
|
||||
>
|
||||
If you approve this request, a third party known for scams might take all your assets.
|
||||
</p>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
|
||||
>
|
||||
<span
|
||||
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/security-tick.svg');"
|
||||
/>
|
||||
<span>
|
||||
|
||||
Security advice by
|
||||
<a
|
||||
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
|
||||
href="https://blockaid.io/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Blockaid
|
||||
</a>
|
||||
|
||||
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Blockaid Banner Alert should render 'warning' UI when ppomResponse.resultType is 'Warning 1`] = `
|
||||
<div
|
||||
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-warning box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-warning-muted box--rounded-sm"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-warning-default"
|
||||
style="mask-image: url('./images/icons/warning.svg');"
|
||||
/>
|
||||
<div>
|
||||
<h5
|
||||
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
|
||||
data-testid="mm-banner-base-title"
|
||||
>
|
||||
This is a deceptive request
|
||||
</h5>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
|
||||
>
|
||||
If you approve this request, a third party known for scams might take all your assets.
|
||||
</p>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
|
||||
>
|
||||
<span
|
||||
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/security-tick.svg');"
|
||||
/>
|
||||
<span>
|
||||
|
||||
Security advice by
|
||||
<a
|
||||
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
|
||||
href="https://blockaid.io/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Blockaid
|
||||
</a>
|
||||
|
||||
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Blockaid Banner Alert should render details when provided 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-warning box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-warning-muted box--rounded-sm"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-warning-default"
|
||||
style="mask-image: url('./images/icons/warning.svg');"
|
||||
/>
|
||||
<div>
|
||||
<h5
|
||||
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
|
||||
data-testid="mm-banner-base-title"
|
||||
>
|
||||
This is a deceptive request
|
||||
</h5>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
|
||||
>
|
||||
If you approve this request, a third party known for scams might take all your assets.
|
||||
</p>
|
||||
<div
|
||||
class="disclosure"
|
||||
>
|
||||
<details>
|
||||
<summary
|
||||
class="disclosure__summary is-arrow"
|
||||
>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-md mm-box--color-primary-default"
|
||||
>
|
||||
See details
|
||||
</p>
|
||||
<span
|
||||
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-start-2 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/arrow-up.svg');"
|
||||
/>
|
||||
</summary>
|
||||
<div
|
||||
class="disclosure__content normal"
|
||||
>
|
||||
<ul
|
||||
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
|
||||
>
|
||||
<li>
|
||||
•
|
||||
Operator is an EOA
|
||||
</li>
|
||||
<li>
|
||||
•
|
||||
Operator is untrusted according to previous activity
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="disclosure__footer"
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
<p
|
||||
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
|
||||
>
|
||||
<span
|
||||
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/security-tick.svg');"
|
||||
/>
|
||||
<span>
|
||||
|
||||
Security advice by
|
||||
<a
|
||||
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
|
||||
href="https://blockaid.io/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Blockaid
|
||||
</a>
|
||||
|
||||
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,90 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { captureException } from '@sentry/browser';
|
||||
|
||||
import { Text } from '../../../component-library';
|
||||
import { Severity } from '../../../../helpers/constants/design-system';
|
||||
import { I18nContext } from '../../../../contexts/i18n';
|
||||
|
||||
import {
|
||||
BlockaidReason,
|
||||
BlockaidResultType,
|
||||
SecurityProvider,
|
||||
} from '../../../../../shared/constants/security-provider';
|
||||
import SecurityProviderBannerAlert from '../security-provider-banner-alert';
|
||||
|
||||
/** Reason to description translation key mapping. Grouped by translations. */
|
||||
const REASON_TO_DESCRIPTION_TKEY = Object.freeze({
|
||||
[BlockaidReason.approvalFarming]: 'blockaidDescriptionApproveFarming',
|
||||
[BlockaidReason.permitFarming]: 'blockaidDescriptionApproveFarming',
|
||||
[BlockaidReason.setApprovalForAll]: 'blockaidDescriptionApproveFarming',
|
||||
|
||||
[BlockaidReason.blurFarming]: 'blockaidDescriptionBlurFarming',
|
||||
|
||||
[BlockaidReason.seaportFarming]: 'blockaidDescriptionSeaportFarming',
|
||||
|
||||
[BlockaidReason.maliciousDomain]: 'blockaidDescriptionMaliciousDomain',
|
||||
|
||||
[BlockaidReason.rawSignatureFarming]: 'blockaidDescriptionMightLoseAssets',
|
||||
[BlockaidReason.tradeOrderFarming]: 'blockaidDescriptionMightLoseAssets',
|
||||
[BlockaidReason.unfairTrade]: 'blockaidDescriptionMightLoseAssets',
|
||||
|
||||
[BlockaidReason.rawNativeTokenTransfer]: 'blockaidDescriptionTransferFarming',
|
||||
[BlockaidReason.transferFarming]: 'blockaidDescriptionTransferFarming',
|
||||
[BlockaidReason.transferFromFarming]: 'blockaidDescriptionTransferFarming',
|
||||
|
||||
[BlockaidReason.other]: 'blockaidDescriptionMightLoseAssets',
|
||||
});
|
||||
|
||||
/** List of suspicious reason(s). Other reasons will be deemed as deceptive. */
|
||||
const SUSPCIOUS_REASON = [BlockaidReason.rawSignatureFarming];
|
||||
|
||||
function BlockaidBannerAlert({
|
||||
ppomResponse: { reason, resultType, features },
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
if (resultType === BlockaidResultType.Benign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!REASON_TO_DESCRIPTION_TKEY[reason]) {
|
||||
captureException(`BlockaidBannerAlert: Unidentified reason '${reason}'`);
|
||||
}
|
||||
|
||||
const description = t(REASON_TO_DESCRIPTION_TKEY[reason] || 'other');
|
||||
|
||||
const details = Boolean(features?.length) && (
|
||||
<Text as="ul">
|
||||
{features.map((feature, i) => (
|
||||
<li key={`blockaid-detail-${i}`}>• {feature}</li>
|
||||
))}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const severity =
|
||||
resultType === BlockaidResultType.Malicious
|
||||
? Severity.Danger
|
||||
: Severity.Warning;
|
||||
|
||||
const title =
|
||||
SUSPCIOUS_REASON.indexOf(reason) > -1
|
||||
? t('blockaidTitleSuspicious')
|
||||
: t('blockaidTitleDeceptive');
|
||||
|
||||
return (
|
||||
<SecurityProviderBannerAlert
|
||||
description={description}
|
||||
details={details}
|
||||
provider={SecurityProvider.Blockaid}
|
||||
severity={severity}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
BlockaidBannerAlert.propTypes = {
|
||||
ppomResponse: PropTypes.object,
|
||||
};
|
||||
|
||||
export default BlockaidBannerAlert;
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
BlockaidReason,
|
||||
BlockaidResultType,
|
||||
} from '../../../../../shared/constants/security-provider';
|
||||
import BlockaidBannerAlert from '.';
|
||||
|
||||
const mockFeatures = [
|
||||
'Operator is an EOA',
|
||||
'Operator is untrusted according to previous activity',
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Components/App/SecurityProviderBannerAlert/BlockaidBannerAlert',
|
||||
argTypes: {
|
||||
features: {
|
||||
control: 'array',
|
||||
description:
|
||||
'ppomResponse.features value which is a list displayed as SecurityProviderBannerAlert details',
|
||||
},
|
||||
reason: {
|
||||
control: 'select',
|
||||
options: Object.values(BlockaidReason),
|
||||
description: 'ppomResponse.reason value',
|
||||
},
|
||||
resultType: {
|
||||
control: 'select',
|
||||
options: Object.values(BlockaidResultType),
|
||||
description: 'ppomResponse.resultType value',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
features: mockFeatures,
|
||||
reason: BlockaidReason.setApprovalForAll,
|
||||
resultType: BlockaidResultType.Warning,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
<BlockaidBannerAlert ppomResponse={args} />
|
||||
);
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { renderWithLocalization } from '../../../../../test/lib/render-helpers';
|
||||
import { Severity } from '../../../../helpers/constants/design-system';
|
||||
import {
|
||||
BlockaidReason,
|
||||
BlockaidResultType,
|
||||
} from '../../../../../shared/constants/security-provider';
|
||||
import BlockaidBannerAlert from '.';
|
||||
|
||||
const mockPpomResponse = {
|
||||
resultType: BlockaidResultType.Warning,
|
||||
reason: BlockaidReason.setApprovalForAll,
|
||||
description:
|
||||
'A SetApprovalForAll request was made on {contract}. We found the operator {operator} to be malicious',
|
||||
args: {
|
||||
contract: '0xa7206d878c5c3871826dfdb42191c49b1d11f466',
|
||||
operator: '0x92a3b9773b1763efa556f55ccbeb20441962d9b2',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Blockaid Banner Alert', () => {
|
||||
it(`should not render when ppomResponse.resultType is '${BlockaidResultType.Benign}'`, () => {
|
||||
const { container } = renderWithLocalization(
|
||||
<BlockaidBannerAlert
|
||||
ppomResponse={{
|
||||
...mockPpomResponse,
|
||||
resultType: BlockaidResultType.Benign,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.mm-banner-alert')).toBeNull();
|
||||
});
|
||||
|
||||
it(`should render '${Severity.Danger}' UI when ppomResponse.resultType is '${BlockaidResultType.Malicious}`, () => {
|
||||
const { container } = renderWithLocalization(
|
||||
<BlockaidBannerAlert
|
||||
ppomResponse={{
|
||||
...mockPpomResponse,
|
||||
resultType: BlockaidResultType.Malicious,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const dangerBannerAlert = container.querySelector(
|
||||
'.mm-banner-alert--severity-danger',
|
||||
);
|
||||
|
||||
expect(dangerBannerAlert).toBeInTheDocument();
|
||||
expect(dangerBannerAlert).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`should render '${Severity.Warning}' UI when ppomResponse.resultType is '${BlockaidResultType.Warning}`, () => {
|
||||
const { container } = renderWithLocalization(
|
||||
<BlockaidBannerAlert ppomResponse={mockPpomResponse} />,
|
||||
);
|
||||
const warningBannerAlert = container.querySelector(
|
||||
'.mm-banner-alert--severity-warning',
|
||||
);
|
||||
|
||||
expect(warningBannerAlert).toBeInTheDocument();
|
||||
expect(warningBannerAlert).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render title, "This is a deceptive request"', () => {
|
||||
const { getByText } = renderWithLocalization(
|
||||
<BlockaidBannerAlert ppomResponse={mockPpomResponse} />,
|
||||
);
|
||||
|
||||
expect(getByText('This is a deceptive request')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render title, "This is a suspicious request", when the reason is "raw_signature_farming"', () => {
|
||||
const { getByText } = renderWithLocalization(
|
||||
<BlockaidBannerAlert
|
||||
ppomResponse={{
|
||||
...mockPpomResponse,
|
||||
reason: BlockaidReason.rawSignatureFarming,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('This is a suspicious request')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render details when provided', () => {
|
||||
const mockFeatures = [
|
||||
'Operator is an EOA',
|
||||
'Operator is untrusted according to previous activity',
|
||||
];
|
||||
|
||||
const { container, getByText } = renderWithLocalization(
|
||||
<BlockaidBannerAlert
|
||||
ppomResponse={{ ...mockPpomResponse, features: mockFeatures }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
expect(container.querySelector('.disclosure')).toBeInTheDocument();
|
||||
mockFeatures.forEach((feature) => {
|
||||
expect(getByText(`• ${feature}`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when rendering description', () => {
|
||||
Object.entries({
|
||||
[BlockaidReason.approvalFarming]:
|
||||
'If you approve this request, a third party known for scams might take all your assets.',
|
||||
[BlockaidReason.blurFarming]:
|
||||
'If you approve this request, someone can steal your assets listed on Blur.',
|
||||
[BlockaidReason.maliciousDomain]:
|
||||
"You're interacting with a malicious domain. If you approve this request, you might lose your assets.",
|
||||
[BlockaidReason.other]:
|
||||
'If you approve this request, you might lose your assets.',
|
||||
[BlockaidReason.permitFarming]:
|
||||
'If you approve this request, a third party known for scams might take all your assets.',
|
||||
[BlockaidReason.rawNativeTokenTransfer]:
|
||||
'If you approve this request, a third party known for scams will take all your assets.',
|
||||
[BlockaidReason.rawSignatureFarming]:
|
||||
'If you approve this request, you might lose your assets.',
|
||||
[BlockaidReason.seaportFarming]:
|
||||
'If you approve this request, someone can steal your assets listed on OpenSea.',
|
||||
[BlockaidReason.setApprovalForAll]:
|
||||
'If you approve this request, a third party known for scams might take all your assets.',
|
||||
[BlockaidReason.tradeOrderFarming]:
|
||||
'If you approve this request, you might lose your assets.',
|
||||
[BlockaidReason.transferFromFarming]:
|
||||
'If you approve this request, a third party known for scams will take all your assets.',
|
||||
[BlockaidReason.transferFarming]:
|
||||
'If you approve this request, a third party known for scams will take all your assets.',
|
||||
[BlockaidReason.unfairTrade]:
|
||||
'If you approve this request, you might lose your assets.',
|
||||
}).forEach(([reason, expectedDescription]) => {
|
||||
it(`should render for '${reason}' correctly`, () => {
|
||||
const { getByText } = renderWithLocalization(
|
||||
<BlockaidBannerAlert
|
||||
ppomResponse={{ ...mockPpomResponse, reason }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText(expectedDescription)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './blockaid-banner-alert';
|
Loading…
Reference in New Issue
Block a user