1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

OpenSea security provider metrics (#17688)

* Added metrics for the OpenSea security provider

* Fixed tests

* Fixed a test

* Fixed metrics

* Code refactor

* Lint fixed

* Removed unnecessary code

* Fix build

* Fix e2e

* Cleanup

* Fix e2e

* Code refactor

* Removed unnecessary code

* rpc middleware: catch securityProviderCheck errors
to not block dapp rpc requests

* Fixed an issue

* Added aditional test

* Applied some changes

* Fixed a test

* Fixed a test

* Code refactor

* Covered more code with tests

* Updated a test

* Fixed an issue

---------

Co-authored-by: Jyoti Puri <jyotipuri@gmail.com>
Co-authored-by: digiwand <20778143+digiwand@users.noreply.github.com>
Co-authored-by: Brad Decker <bhdecker84@gmail.com>
This commit is contained in:
Filip Sekulic 2023-03-23 18:01:51 +01:00 committed by GitHub
parent 196b8408d0
commit 0351309227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 426 additions and 42 deletions

View File

@ -721,6 +721,9 @@ export default class MetaMetricsController {
///: BEGIN:ONLY_INCLUDE_IN(flask)
[TRAITS.DESKTOP_ENABLED]: metamaskState.desktopEnabled || false,
///: END:ONLY_INCLUDE_IN
[TRAITS.SECURITY_PROVIDERS]: metamaskState.transactionSecurityCheckEnabled
? ['opensea']
: [],
};
if (!previousUserTraits) {

View File

@ -952,6 +952,7 @@ describe('MetaMetricsController', function () {
theme: 'default',
useTokenDetection: true,
desktopEnabled: false,
security_providers: [],
});
assert.deepEqual(traits, {
@ -970,6 +971,7 @@ describe('MetaMetricsController', function () {
[TRAITS.THEME]: 'default',
[TRAITS.TOKEN_DETECTION_ENABLED]: true,
[TRAITS.DESKTOP_ENABLED]: false,
[TRAITS.SECURITY_PROVIDERS]: [],
});
});

View File

@ -2147,6 +2147,7 @@ export default class TransactionController extends EventEmitter {
originalApprovalAmount,
finalApprovalAmount,
contractMethodName,
securityProviderResponse,
} = txMeta;
const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp';
@ -2298,6 +2299,16 @@ export default class TransactionController extends EventEmitter {
}
}
let uiCustomizations;
if (securityProviderResponse?.flagAsDangerous === 1) {
uiCustomizations = ['flagged_as_malicious'];
} else if (securityProviderResponse?.flagAsDangerous === 2) {
uiCustomizations = ['flagged_as_safety_unknown'];
} else {
uiCustomizations = null;
}
let properties = {
chain_id: chainId,
referrer,
@ -2312,6 +2323,7 @@ export default class TransactionController extends EventEmitter {
token_standard: tokenStandard,
transaction_type: transactionType,
transaction_speed_up: type === TransactionType.retry,
ui_customizations: uiCustomizations,
};
if (transactionContractMethod === contractMethodNames.APPROVE) {

View File

@ -1740,6 +1740,9 @@ describe('Transaction Controller', function () {
gas: '0x7b0d',
gasPrice: '0x77359400',
},
securityProviderResponse: {
flagAsDangerous: 0,
},
};
});
@ -1766,6 +1769,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -1852,6 +1856,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -1921,6 +1926,9 @@ describe('Transaction Controller', function () {
gas: '0x7b0d',
gasPrice: '0x77359400',
},
securityProviderResponse: {
flagAsDangerous: 0,
},
};
});
@ -1947,6 +1955,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2035,6 +2044,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
default_gas: '0.000031501',
@ -2099,6 +2109,9 @@ describe('Transaction Controller', function () {
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
securityProviderResponse: {
flagAsDangerous: 0,
},
};
const expectedPayload = {
@ -2122,6 +2135,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
gas_price: '2',
@ -2167,6 +2181,9 @@ describe('Transaction Controller', function () {
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
securityProviderResponse: {
flagAsDangerous: 0,
},
};
const expectedPayload = {
actionId,
@ -2190,6 +2207,155 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
baz: 3.0,
foo: 'bar',
gas_price: '2',
gas_limit: '0x7b0d',
transaction_contract_method: undefined,
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
await txController._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.added,
actionId,
{
baz: 3.0,
foo: 'bar',
},
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is malicious', async function () {
const txMeta = {
id: 1,
status: TransactionStatus.unapproved,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TransactionType.simpleSend,
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
securityProviderResponse: {
flagAsDangerous: 1,
},
};
const expectedPayload = {
actionId,
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: EVENT.CATEGORIES.TRANSACTIONS,
properties: {
network: '5',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
transaction_type: TransactionType.simpleSend,
chain_id: '0x5',
eip_1559_version: '0',
gas_edit_attempted: 'none',
gas_edit_type: 'none',
account_type: 'MetaMask',
asset_type: AssetType.native,
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: ['flagged_as_malicious'],
},
sensitiveProperties: {
baz: 3.0,
foo: 'bar',
gas_price: '2',
gas_limit: '0x7b0d',
transaction_contract_method: undefined,
transaction_replaced: undefined,
first_seen: 1624408066355,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
status: 'unapproved',
},
};
await txController._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.added,
actionId,
{
baz: 3.0,
foo: 'bar',
},
);
assert.equal(createEventFragmentSpy.callCount, 1);
assert.equal(finalizeEventFragmentSpy.callCount, 0);
assert.deepEqual(
createEventFragmentSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is unknown', async function () {
const txMeta = {
id: 1,
status: TransactionStatus.unapproved,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: TransactionType.simpleSend,
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
securityProviderResponse: {
flagAsDangerous: 2,
},
};
const expectedPayload = {
actionId,
initialEvent: 'Transaction Added',
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: EVENT.CATEGORIES.TRANSACTIONS,
properties: {
network: '5',
referrer: 'other',
source: EVENT.SOURCE.TRANSACTION.DAPP,
transaction_type: TransactionType.simpleSend,
chain_id: '0x5',
eip_1559_version: '0',
gas_edit_attempted: 'none',
gas_edit_type: 'none',
account_type: 'MetaMask',
asset_type: AssetType.native,
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: ['flagged_as_safety_unknown'],
},
sensitiveProperties: {
baz: 3.0,
@ -2245,6 +2411,9 @@ describe('Transaction Controller', function () {
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
},
securityProviderResponse: {
flagAsDangerous: 0,
},
};
const expectedPayload = {
actionId,
@ -2268,6 +2437,7 @@ describe('Transaction Controller', function () {
token_standard: TokenStandard.none,
device_model: 'N/A',
transaction_speed_up: false,
ui_customizations: null,
},
sensitiveProperties: {
baz: 3.0,

View File

@ -107,14 +107,16 @@ const rateLimitTimeouts = {};
* MetaMetricsController
* @param {number} [opts.rateLimitSeconds] - number of seconds to wait before
* allowing another set of events to be tracked.
* @param opts.securityProviderRequest
* @returns {Function}
*/
export default function createRPCMethodTrackingMiddleware({
trackEvent,
getMetricsState,
rateLimitSeconds = 60 * 5,
securityProviderRequest,
}) {
return function rpcMethodTrackingMiddleware(
return async function rpcMethodTrackingMiddleware(
/** @type {any} */ req,
/** @type {any} */ res,
/** @type {Function} */ next,
@ -162,22 +164,65 @@ export default function createRPCMethodTrackingMiddleware({
const properties = {};
let msgParams;
if (event === EVENT_NAMES.SIGNATURE_REQUESTED) {
properties.signature_type = method;
const data = req?.params?.[0];
const from = req?.params?.[1];
const paramsExamplePassword = req?.params?.[2];
msgParams = {
...paramsExamplePassword,
from,
data,
origin,
};
const msgData = {
msgParams,
status: 'unapproved',
type: req.method,
};
try {
const securityProviderResponse = await securityProviderRequest(
msgData,
req.method,
);
if (securityProviderResponse?.flagAsDangerous === 1) {
properties.ui_customizations = ['flagged_as_malicious'];
} else if (securityProviderResponse?.flagAsDangerous === 2) {
properties.ui_customizations = ['flagged_as_safety_unknown'];
} else {
properties.ui_customizations = null;
}
if (method === MESSAGE_TYPE.PERSONAL_SIGN) {
const { isSIWEMessage } = detectSIWE({ data });
if (isSIWEMessage) {
properties.ui_customizations === null
? (properties.ui_customizations = [
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS]
.SIWE,
])
: properties.ui_customizations.push(
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS]
.SIWE,
);
}
}
} catch (e) {
console.warn(
`createRPCMethodTrackingMiddleware: Error calling securityProviderRequest - ${e}`,
);
}
} else {
properties.method = method;
}
if (method === MESSAGE_TYPE.PERSONAL_SIGN) {
const data = req?.params?.[0];
const { isSIWEMessage } = detectSIWE({ data });
if (isSIWEMessage) {
properties.ui_customizations = [
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS].SIWE,
];
}
}
trackEvent({
event,
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
@ -192,7 +237,7 @@ export default function createRPCMethodTrackingMiddleware({
}, SECOND * rateLimitSeconds);
}
next((callback) => {
next(async (callback) => {
if (shouldTrackEvent === false || typeof eventType === 'undefined') {
return callback();
}
@ -216,22 +261,65 @@ export default function createRPCMethodTrackingMiddleware({
event = eventType.APPROVED;
}
let msgParams;
if (eventType.REQUESTED === EVENT_NAMES.SIGNATURE_REQUESTED) {
properties.signature_type = method;
const data = req?.params?.[0];
const from = req?.params?.[1];
const paramsExamplePassword = req?.params?.[2];
msgParams = {
...paramsExamplePassword,
from,
data,
origin,
};
const msgData = {
msgParams,
status: 'unapproved',
type: req.method,
};
try {
const securityProviderResponse = await securityProviderRequest(
msgData,
req.method,
);
if (securityProviderResponse?.flagAsDangerous === 1) {
properties.ui_customizations = ['flagged_as_malicious'];
} else if (securityProviderResponse?.flagAsDangerous === 2) {
properties.ui_customizations = ['flagged_as_safety_unknown'];
} else {
properties.ui_customizations = null;
}
if (method === MESSAGE_TYPE.PERSONAL_SIGN) {
const { isSIWEMessage } = detectSIWE({ data });
if (isSIWEMessage) {
properties.ui_customizations === null
? (properties.ui_customizations = [
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS]
.SIWE,
])
: properties.ui_customizations.push(
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS]
.SIWE,
);
}
}
} catch (e) {
console.warn(
`createRPCMethodTrackingMiddleware: Error calling securityProviderRequest - ${e}`,
);
}
} else {
properties.method = method;
}
if (method === MESSAGE_TYPE.PERSONAL_SIGN) {
const data = req?.params?.[0];
const { isSIWEMessage } = detectSIWE({ data });
if (isSIWEMessage) {
properties.ui_customizations = [
METAMETRIC_KEY_OPTIONS[METAMETRIC_KEY.UI_CUSTOMIZATIONS].SIWE,
];
}
}
trackEvent({
event,
category: EVENT.CATEGORIES.INPAGE_PROVIDER,

View File

@ -8,10 +8,19 @@ const trackEvent = jest.fn();
const metricsState = { participateInMetaMetrics: null };
const getMetricsState = () => metricsState;
let flagAsDangerous = 0;
const securityProviderRequest = () => {
return {
flagAsDangerous,
};
};
const handler = createRPCMethodTrackingMiddleware({
trackEvent,
getMetricsState,
rateLimitSeconds: 1,
securityProviderRequest,
});
function getNext(timeout = 500) {
@ -92,7 +101,7 @@ describe('createRPCMethodTrackingMiddleware', () => {
metricsState.participateInMetaMetrics = true;
});
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event`, () => {
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event`, async () => {
const req = {
method: MESSAGE_TYPE.ETH_SIGN,
origin: 'some.dapp',
@ -102,12 +111,14 @@ describe('createRPCMethodTrackingMiddleware', () => {
error: null,
};
const { next } = getNext();
handler(req, res, next);
await handler(req, res, next);
expect(trackEvent).toHaveBeenCalledTimes(1);
expect(trackEvent.mock.calls[0][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_REQUESTED,
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN },
properties: {
signature_type: MESSAGE_TYPE.ETH_SIGN,
},
referrer: { url: 'some.dapp' },
});
});
@ -122,13 +133,15 @@ describe('createRPCMethodTrackingMiddleware', () => {
error: null,
};
const { next, executeMiddlewareStack } = getNext();
handler(req, res, next);
await handler(req, res, next);
await executeMiddlewareStack();
expect(trackEvent).toHaveBeenCalledTimes(2);
expect(trackEvent.mock.calls[1][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_APPROVED,
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 },
properties: {
signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4,
},
referrer: { url: 'some.dapp' },
});
});
@ -143,13 +156,15 @@ describe('createRPCMethodTrackingMiddleware', () => {
error: { code: 4001 },
};
const { next, executeMiddlewareStack } = getNext();
handler(req, res, next);
await handler(req, res, next);
await executeMiddlewareStack();
expect(trackEvent).toHaveBeenCalledTimes(2);
expect(trackEvent.mock.calls[1][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_REJECTED,
properties: { signature_type: MESSAGE_TYPE.PERSONAL_SIGN },
properties: {
signature_type: MESSAGE_TYPE.PERSONAL_SIGN,
},
referrer: { url: 'some.dapp' },
});
});
@ -162,7 +177,7 @@ describe('createRPCMethodTrackingMiddleware', () => {
const res = {};
const { next, executeMiddlewareStack } = getNext();
handler(req, res, next);
await handler(req, res, next);
await executeMiddlewareStack();
expect(trackEvent).toHaveBeenCalledTimes(2);
expect(trackEvent.mock.calls[1][0]).toMatchObject({
@ -227,7 +242,7 @@ describe('createRPCMethodTrackingMiddleware', () => {
};
const { next, executeMiddlewareStack } = getNext();
handler(req, res, next);
await handler(req, res, next);
await executeMiddlewareStack();
expect(trackEvent).toHaveBeenCalledTimes(2);
@ -244,4 +259,93 @@ describe('createRPCMethodTrackingMiddleware', () => {
});
});
});
describe('participateInMetaMetrics is set to true with a request flagged as safe', () => {
beforeEach(() => {
metricsState.participateInMetaMetrics = true;
});
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event which is flagged as safe`, async () => {
const req = {
method: MESSAGE_TYPE.ETH_SIGN,
origin: 'some.dapp',
};
const res = {
error: null,
};
const { next } = getNext();
await handler(req, res, next);
expect(trackEvent).toHaveBeenCalledTimes(1);
expect(trackEvent.mock.calls[0][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_REQUESTED,
properties: {
signature_type: MESSAGE_TYPE.ETH_SIGN,
ui_customizations: null,
},
referrer: { url: 'some.dapp' },
});
});
});
describe('participateInMetaMetrics is set to true with a request flagged as malicious', () => {
beforeEach(() => {
metricsState.participateInMetaMetrics = true;
flagAsDangerous = 1;
});
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event which is flagged as malicious`, async () => {
const req = {
method: MESSAGE_TYPE.ETH_SIGN,
origin: 'some.dapp',
};
const res = {
error: null,
};
const { next } = getNext();
await handler(req, res, next);
expect(trackEvent).toHaveBeenCalledTimes(1);
expect(trackEvent.mock.calls[0][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_REQUESTED,
properties: {
signature_type: MESSAGE_TYPE.ETH_SIGN,
ui_customizations: ['flagged_as_malicious'],
},
referrer: { url: 'some.dapp' },
});
});
});
describe('participateInMetaMetrics is set to true with a request flagged as safety unknown', () => {
beforeEach(() => {
metricsState.participateInMetaMetrics = true;
flagAsDangerous = 2;
});
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event which is flagged as safety unknown`, async () => {
const req = {
method: MESSAGE_TYPE.ETH_SIGN,
origin: 'some.dapp',
};
const res = {
error: null,
};
const { next } = getNext();
await handler(req, res, next);
expect(trackEvent).toHaveBeenCalledTimes(1);
expect(trackEvent.mock.calls[0][0]).toMatchObject({
category: 'inpage_provider',
event: EVENT_NAMES.SIGNATURE_REQUESTED,
properties: {
signature_type: MESSAGE_TYPE.ETH_SIGN,
ui_customizations: ['flagged_as_safety_unknown'],
},
referrer: { url: 'some.dapp' },
});
});
});
});

View File

@ -39,12 +39,12 @@ export async function securityProviderCheck(
rpc_method_name: methodName,
chain_id: chainId,
data: {
from_address: requestData.txParams.from,
to_address: requestData.txParams.to,
gas: requestData.txParams.gas,
gasPrice: requestData.txParams.gasPrice,
value: requestData.txParams.value,
data: requestData.txParams.data,
from_address: requestData?.txParams?.from,
to_address: requestData?.txParams?.to,
gas: requestData?.txParams?.gas,
gasPrice: requestData?.txParams?.gasPrice,
value: requestData?.txParams?.value,
data: requestData?.txParams?.data,
},
currentLocale,
};

View File

@ -3729,6 +3729,7 @@ export default class MetamaskController extends EventEmitter {
getMetricsState: this.metaMetricsController.store.getState.bind(
this.metaMetricsController.store,
),
securityProviderRequest: this.securityProviderRequest.bind(this),
}),
);
@ -4383,11 +4384,11 @@ export default class MetamaskController extends EventEmitter {
const { currentLocale, transactionSecurityCheckEnabled } =
this.preferencesController.store.getState();
const chainId = Number(
hexToDecimal(this.networkController.store.getState().provider.chainId),
);
if (transactionSecurityCheckEnabled) {
const chainId = Number(
hexToDecimal(this.networkController.store.getState().provider.chainId),
);
try {
const securityProviderResponse = await securityProviderCheck(
requestData,

View File

@ -187,6 +187,8 @@
* identify the token_detection_enabled trait
* @property {'install_date_ext'} INSTALL_DATE_EXT - when the user installed the extension
* @property {'desktop_enabled'} [DESKTOP_ENABLED] - optional / does the user have desktop enabled?
* @property {'security_providers'} SECURITY_PROVIDERS - when security provider feature is toggled we
* identify the security_providers trait
*/
/**
@ -210,6 +212,7 @@ export const TRAITS = {
THREE_BOX_ENABLED: 'three_box_enabled',
TOKEN_DETECTION_ENABLED: 'token_detection_enabled',
DESKTOP_ENABLED: 'desktop_enabled',
SECURITY_PROVIDERS: 'security_providers',
};
/**
@ -240,6 +243,7 @@ export const TRAITS = {
* @property {string} [theme] - which theme the user has selected
* @property {boolean} [token_detection_enabled] - does the user have token detection is enabled?
* @property {boolean} [desktop_enabled] - optional / does the user have desktop enabled?
* @property {Array<string>} [security_providers] - whether security provider feature toggle is on or off
*/
// Mixpanel converts the zero address value to a truly anonymous event, which

View File

@ -31,10 +31,10 @@ describe('Eth sign', function () {
await driver.openNewPage('http://127.0.0.1:8080/');
await driver.clickElement('#ethSign');
await driver.delay(1000);
const ethSignButton = await driver.findElement('#ethSign');
const exceptionString =
'ERROR: ETH_SIGN HAS BEEN DISABLED. YOU MUST ENABLE IT IN THE ADVANCED SETTINGS';
assert.equal(await ethSignButton.getText(), exceptionString);
},
);