@avalabs/evm-module 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +25 -24
- package/CHANGELOG.md +8 -0
- package/dist/index.cjs +25 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +22 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/constants.ts +1 -0
- package/src/handlers/eth-send-transaction/eth-send-transaction.test.ts +232 -1
- package/src/handlers/eth-send-transaction/eth-send-transaction.ts +24 -5
- package/src/handlers/eth-sign/eth-sign.test.ts +138 -35
- package/src/handlers/eth-sign/eth-sign.ts +29 -8
- package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.test.ts +2 -2
- package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.ts +4 -6
- package/src/handlers/get-balances/get-balances.test.ts +0 -5
- package/src/handlers/get-balances/get-balances.ts +14 -3
- package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.test.ts +0 -1
- package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.ts +10 -7
- package/src/handlers/get-tokens/get-tokens.test.ts +6 -6
- package/src/module.ts +2 -0
- package/src/types.ts +9 -0
- package/src/utils/parse-erc20-transaction-type.ts +35 -0
- package/src/utils/process-transaction-simulation.test.ts +105 -0
- package/src/utils/process-transaction-simulation.ts +294 -0
- package/src/utils/scan-transaction.ts +63 -0
- package/src/handlers/eth-sign/schemas/parse-request-params.ts +0 -90
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avalabs/evm-module",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"main": "dist/index.cjs",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@avalabs/vm-module-types": "0.0.
|
|
9
|
+
"@avalabs/vm-module-types": "0.0.17",
|
|
10
10
|
"@zodios/core": "10.9.6",
|
|
11
11
|
"@avalabs/coingecko-sdk": "v2.8.0-alpha.193",
|
|
12
12
|
"@avalabs/utils-sdk": "2.8.0-alpha.193",
|
|
13
13
|
"@avalabs/etherscan-sdk": "v2.8.0-alpha.193",
|
|
14
14
|
"@avalabs/glacier-sdk": "v2.8.0-alpha.193",
|
|
15
15
|
"@avalabs/wallets-sdk": "v2.8.0-alpha.193",
|
|
16
|
+
"@blockaid/client": "0.11.0",
|
|
16
17
|
"bn.js": "5.2.1",
|
|
17
18
|
"lodash.startcase": "4.4.0",
|
|
18
19
|
"@metamask/rpc-errors": "6.3.0",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"ethers": "6.8.1",
|
|
32
33
|
"@internal/tsup-config": "0.0.1",
|
|
33
34
|
"eslint-config-custom": "0.0.1",
|
|
34
|
-
"@internal/utils": "0.0.
|
|
35
|
+
"@internal/utils": "0.0.3"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"ethers": "^6.8.1"
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_DECIMALS = 18;
|
|
@@ -3,16 +3,33 @@ import { parseRequestParams } from './schema';
|
|
|
3
3
|
import { estimateGasLimit } from '../../utils/estimate-gas-limit';
|
|
4
4
|
import { getNonce } from '../../utils/get-nonce';
|
|
5
5
|
import { rpcErrors } from '@metamask/rpc-errors';
|
|
6
|
-
import { RpcMethod, type ApprovalController, type Network } from '@avalabs/vm-module-types';
|
|
6
|
+
import { AlertType, RpcMethod, TokenType, type ApprovalController, type Network } from '@avalabs/vm-module-types';
|
|
7
7
|
import { ZodError } from 'zod';
|
|
8
8
|
import { getProvider } from '../../utils/get-provider';
|
|
9
|
+
import Blockaid from '@blockaid/client';
|
|
9
10
|
|
|
10
11
|
const mockGetProvider = getProvider as jest.MockedFunction<typeof getProvider>;
|
|
11
12
|
|
|
13
|
+
const PROXY_API_URL = 'https://proxy-api.avax.network';
|
|
14
|
+
|
|
12
15
|
jest.mock('./schema');
|
|
13
16
|
jest.mock('../../utils/estimate-gas-limit');
|
|
14
17
|
jest.mock('../../utils/get-nonce');
|
|
15
18
|
jest.mock('../../utils/get-provider');
|
|
19
|
+
jest.mock('@blockaid/client', () => {
|
|
20
|
+
return jest.fn().mockImplementation(() => {
|
|
21
|
+
return {
|
|
22
|
+
evm: {
|
|
23
|
+
transaction: {
|
|
24
|
+
scan: jest.fn().mockResolvedValue({ validation: { result_type: 'Benign' } }),
|
|
25
|
+
},
|
|
26
|
+
jsonRpc: {
|
|
27
|
+
scan: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
});
|
|
16
33
|
|
|
17
34
|
const mockOnTransactionConfirmed = jest.fn();
|
|
18
35
|
const mockOnTransactionReverted = jest.fn();
|
|
@@ -64,6 +81,7 @@ const testRequestParams = () => ({
|
|
|
64
81
|
},
|
|
65
82
|
network: testNetwork,
|
|
66
83
|
approvalController: mockApprovalController,
|
|
84
|
+
proxyApiUrl: PROXY_API_URL,
|
|
67
85
|
});
|
|
68
86
|
|
|
69
87
|
const displayData = {
|
|
@@ -80,6 +98,9 @@ const displayData = {
|
|
|
80
98
|
data: '0xdata',
|
|
81
99
|
},
|
|
82
100
|
networkFeeSelector: true,
|
|
101
|
+
alert: undefined,
|
|
102
|
+
tokenApprovals: undefined,
|
|
103
|
+
balanceChange: undefined,
|
|
83
104
|
};
|
|
84
105
|
|
|
85
106
|
const signingData = {
|
|
@@ -221,6 +242,149 @@ describe('eth_sendTransaction handler', () => {
|
|
|
221
242
|
});
|
|
222
243
|
});
|
|
223
244
|
|
|
245
|
+
it('should add alert object with Warning type to displayData when validation result is Warning', async () => {
|
|
246
|
+
testWithValidationResultType('Warning');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should add alert object with Warning type to displayData when validation result is Error', async () => {
|
|
250
|
+
testWithValidationResultType('Error');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should add alert object with Danger type to displayData when validation result is Malicious', async () => {
|
|
254
|
+
testWithValidationResultType('Malicious');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should process transaction and add token approvals and balance changes to displayData', async () => {
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
(Blockaid as any).mockImplementation(() => ({
|
|
260
|
+
evm: {
|
|
261
|
+
transaction: {
|
|
262
|
+
scan: jest.fn().mockResolvedValue({
|
|
263
|
+
validation: { result_type: 'Benign' },
|
|
264
|
+
simulation: {
|
|
265
|
+
status: 'Success',
|
|
266
|
+
account_summary: {
|
|
267
|
+
exposures: [
|
|
268
|
+
{
|
|
269
|
+
asset: {
|
|
270
|
+
type: TokenType.ERC20,
|
|
271
|
+
address: '0xTokenAddress',
|
|
272
|
+
name: 'TokenName',
|
|
273
|
+
symbol: 'TKN',
|
|
274
|
+
decimals: 18,
|
|
275
|
+
logo_url: 'logo_url',
|
|
276
|
+
},
|
|
277
|
+
spenders: {
|
|
278
|
+
'0xSpenderAddress': {
|
|
279
|
+
exposure: [{ raw_value: '1', usd_price: '1' }],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
assets_diffs: [
|
|
285
|
+
{
|
|
286
|
+
asset: {
|
|
287
|
+
name: 'TokenName',
|
|
288
|
+
symbol: 'TKN',
|
|
289
|
+
decimals: 18,
|
|
290
|
+
logo_url: 'logo_url',
|
|
291
|
+
type: TokenType.ERC20,
|
|
292
|
+
address: '0xTokenAddress',
|
|
293
|
+
},
|
|
294
|
+
in: [{ value: '1', usd_price: '1' }],
|
|
295
|
+
out: [{ value: '1', usd_price: '1' }],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
}));
|
|
304
|
+
|
|
305
|
+
mockParseRequestParams.mockReturnValue({
|
|
306
|
+
success: true,
|
|
307
|
+
data: [{ from: '0xfrom', to: '0xto', data: '0xdata', value: '0xvalue', nonce: '12', gas: '0x5208' }],
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const requestParams = testRequestParams();
|
|
311
|
+
|
|
312
|
+
await ethSendTransaction(requestParams);
|
|
313
|
+
|
|
314
|
+
expect(mockGetProvider).toHaveBeenCalledWith({
|
|
315
|
+
chainId: 1,
|
|
316
|
+
chainName: 'chainName',
|
|
317
|
+
rpcUrl: 'rpcUrl',
|
|
318
|
+
multiContractAddress: 'multiContractAddress',
|
|
319
|
+
pollingInterval: 1000,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith({
|
|
323
|
+
request: requestParams.request,
|
|
324
|
+
displayData: {
|
|
325
|
+
...displayData,
|
|
326
|
+
tokenApprovals: {
|
|
327
|
+
isEditable: true,
|
|
328
|
+
approvals: [
|
|
329
|
+
{
|
|
330
|
+
token: {
|
|
331
|
+
type: TokenType.ERC20,
|
|
332
|
+
address: '0xTokenAddress',
|
|
333
|
+
name: 'TokenName',
|
|
334
|
+
symbol: 'TKN',
|
|
335
|
+
decimals: 18,
|
|
336
|
+
logoUri: 'logo_url',
|
|
337
|
+
},
|
|
338
|
+
spenderAddress: '0xSpenderAddress',
|
|
339
|
+
value: '1',
|
|
340
|
+
usdPrice: '1',
|
|
341
|
+
logoUri: 'logo_url',
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
balanceChange: {
|
|
346
|
+
ins: [
|
|
347
|
+
{
|
|
348
|
+
token: {
|
|
349
|
+
type: TokenType.ERC20,
|
|
350
|
+
address: '0xTokenAddress',
|
|
351
|
+
name: 'TokenName',
|
|
352
|
+
symbol: 'TKN',
|
|
353
|
+
decimals: 18,
|
|
354
|
+
logoUri: 'logo_url',
|
|
355
|
+
},
|
|
356
|
+
items: [
|
|
357
|
+
{
|
|
358
|
+
displayValue: '1',
|
|
359
|
+
usdPrice: '1',
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
outs: [
|
|
365
|
+
{
|
|
366
|
+
token: {
|
|
367
|
+
type: TokenType.ERC20,
|
|
368
|
+
address: '0xTokenAddress',
|
|
369
|
+
name: 'TokenName',
|
|
370
|
+
symbol: 'TKN',
|
|
371
|
+
decimals: 18,
|
|
372
|
+
logoUri: 'logo_url',
|
|
373
|
+
},
|
|
374
|
+
items: [
|
|
375
|
+
{
|
|
376
|
+
displayValue: '1',
|
|
377
|
+
usdPrice: '1',
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
signingData,
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
224
388
|
it('should return error if gas limit calculation fails', async () => {
|
|
225
389
|
mockParseRequestParams.mockReturnValue({
|
|
226
390
|
success: true,
|
|
@@ -331,3 +495,70 @@ describe('eth_sendTransaction handler', () => {
|
|
|
331
495
|
});
|
|
332
496
|
});
|
|
333
497
|
});
|
|
498
|
+
|
|
499
|
+
const testWithValidationResultType = async (resultType: 'Warning' | 'Error' | 'Malicious') => {
|
|
500
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
501
|
+
(Blockaid as any).mockImplementation(() => ({
|
|
502
|
+
evm: {
|
|
503
|
+
transaction: {
|
|
504
|
+
scan: jest.fn().mockResolvedValue({
|
|
505
|
+
validation: { result_type: resultType },
|
|
506
|
+
simulation: { status: 'Success', account_summary: { exposures: [], assets_diffs: [] } },
|
|
507
|
+
}),
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
}));
|
|
511
|
+
|
|
512
|
+
mockParseRequestParams.mockReturnValue({
|
|
513
|
+
success: true,
|
|
514
|
+
data: [{ from: '0xfrom', to: '0xto', data: '0xdata', value: '0xvalue', nonce: '12', gas: '0x5208' }],
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const requestParams = testRequestParams();
|
|
518
|
+
|
|
519
|
+
await ethSendTransaction(requestParams);
|
|
520
|
+
|
|
521
|
+
expect(mockGetProvider).toHaveBeenCalledWith({
|
|
522
|
+
chainId: 1,
|
|
523
|
+
chainName: 'chainName',
|
|
524
|
+
rpcUrl: 'rpcUrl',
|
|
525
|
+
multiContractAddress: 'multiContractAddress',
|
|
526
|
+
pollingInterval: 1000,
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
if (resultType === 'Malicious') {
|
|
530
|
+
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith({
|
|
531
|
+
request: requestParams.request,
|
|
532
|
+
displayData: {
|
|
533
|
+
...displayData,
|
|
534
|
+
alert: {
|
|
535
|
+
type: AlertType.DANGER,
|
|
536
|
+
details: {
|
|
537
|
+
title: 'Scam Transaction',
|
|
538
|
+
description: 'This transaction is malicious, do not proceed.',
|
|
539
|
+
actionTitles: {
|
|
540
|
+
reject: 'Reject Transaction',
|
|
541
|
+
proceed: 'Proceed Anyway',
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
signingData,
|
|
547
|
+
});
|
|
548
|
+
} else {
|
|
549
|
+
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith({
|
|
550
|
+
request: requestParams.request,
|
|
551
|
+
displayData: {
|
|
552
|
+
...displayData,
|
|
553
|
+
alert: {
|
|
554
|
+
type: AlertType.WARNING,
|
|
555
|
+
details: {
|
|
556
|
+
title: 'Suspicious Transaction',
|
|
557
|
+
description: 'Use caution, this transaction may be malicious.',
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
signingData,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
};
|
|
@@ -13,15 +13,20 @@ import { getNonce } from '../../utils/get-nonce';
|
|
|
13
13
|
import { rpcErrors } from '@metamask/rpc-errors';
|
|
14
14
|
import { getProvider } from '../../utils/get-provider';
|
|
15
15
|
import type { JsonRpcBatchInternal } from '@avalabs/wallets-sdk';
|
|
16
|
+
import { processTransactionSimulation } from '../../utils/process-transaction-simulation';
|
|
17
|
+
import { parseERC20TransactionType } from '../../utils/parse-erc20-transaction-type';
|
|
18
|
+
import { ERC20TransactionType } from '../../types';
|
|
16
19
|
|
|
17
20
|
export const ethSendTransaction = async ({
|
|
18
21
|
request,
|
|
19
22
|
network,
|
|
20
23
|
approvalController,
|
|
24
|
+
proxyApiUrl,
|
|
21
25
|
}: {
|
|
22
26
|
request: RpcRequest;
|
|
23
27
|
network: Network;
|
|
24
28
|
approvalController: ApprovalController;
|
|
29
|
+
proxyApiUrl: string;
|
|
25
30
|
}) => {
|
|
26
31
|
const { dappInfo, params } = request;
|
|
27
32
|
|
|
@@ -87,14 +92,24 @@ export const ethSendTransaction = async ({
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
const transactionType = parseERC20TransactionType(transaction);
|
|
96
|
+
|
|
97
|
+
const { alert, balanceChange, tokenApprovals } = await processTransactionSimulation({
|
|
98
|
+
request,
|
|
99
|
+
proxyApiUrl,
|
|
100
|
+
chainId: network.chainId,
|
|
101
|
+
params: transaction,
|
|
102
|
+
dAppUrl: request.dappInfo.url,
|
|
103
|
+
});
|
|
92
104
|
|
|
93
105
|
// generate display and signing data
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
let title = 'Approve Transaction';
|
|
107
|
+
if (transactionType === ERC20TransactionType.APPROVE) {
|
|
108
|
+
title = 'Token Spend Approval';
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
const displayData: DisplayData = {
|
|
97
|
-
title
|
|
112
|
+
title,
|
|
98
113
|
network: {
|
|
99
114
|
chainId: network.chainId,
|
|
100
115
|
name: network.chainName,
|
|
@@ -105,8 +120,12 @@ export const ethSendTransaction = async ({
|
|
|
105
120
|
from: transaction.from,
|
|
106
121
|
to: transaction.to,
|
|
107
122
|
data: transaction.data,
|
|
123
|
+
type: transactionType,
|
|
108
124
|
},
|
|
109
125
|
networkFeeSelector: true,
|
|
126
|
+
alert,
|
|
127
|
+
balanceChange,
|
|
128
|
+
tokenApprovals,
|
|
110
129
|
};
|
|
111
130
|
|
|
112
131
|
const signingData: SigningData = {
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { ethSign } from './eth-sign';
|
|
2
|
-
import {
|
|
2
|
+
import { AlertType, RpcMethod } from '@avalabs/vm-module-types';
|
|
3
3
|
import { rpcErrors } from '@metamask/rpc-errors';
|
|
4
|
+
import Blockaid from '@blockaid/client';
|
|
5
|
+
|
|
6
|
+
const PROXY_API_URL = 'https://proxy-api.avax.network';
|
|
7
|
+
|
|
8
|
+
jest.mock('@blockaid/client', () => {
|
|
9
|
+
return jest.fn().mockImplementation(() => {
|
|
10
|
+
return {
|
|
11
|
+
evm: {
|
|
12
|
+
transaction: {
|
|
13
|
+
scan: jest.fn().mockResolvedValue({ validation: { result_type: 'Benign' } }),
|
|
14
|
+
},
|
|
15
|
+
jsonRpc: {
|
|
16
|
+
scan: jest.fn().mockResolvedValue({ validation: { result_type: 'Benign' } }),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
});
|
|
4
22
|
|
|
5
23
|
jest.mock('./schemas/parse-request-params/parse-request-params', () => ({
|
|
6
24
|
parseRequestParams: jest.fn(),
|
|
@@ -31,37 +49,38 @@ const mockBeautifySimpleMessage = require('./utils/beautify-message/beautify-mes
|
|
|
31
49
|
const mockBeautifyComplexMessage = require('./utils/beautify-message/beautify-message').beautifyComplexMessage;
|
|
32
50
|
const mockIsTypedDataV1 = require('./utils/typeguards').isTypedDataV1;
|
|
33
51
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const mockRequest = {
|
|
53
|
+
method: RpcMethod.ETH_SIGN,
|
|
54
|
+
params: ['0x123'],
|
|
55
|
+
dappInfo: {
|
|
56
|
+
name: 'Test DApp',
|
|
57
|
+
url: 'test-url',
|
|
58
|
+
icon: 'test-icon-uri',
|
|
59
|
+
},
|
|
60
|
+
requestId: 'requestId',
|
|
61
|
+
sessionId: 'sessionId',
|
|
62
|
+
chainId: 'eip155:1',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const mockNetwork = {
|
|
66
|
+
chainId: 1,
|
|
67
|
+
chainName: 'Ethereum',
|
|
68
|
+
logoUri: 'test-logo-uri',
|
|
69
|
+
rpcUrl: 'rpcUrl',
|
|
70
|
+
networkToken: {
|
|
71
|
+
name: 'ethereum',
|
|
72
|
+
symbol: 'ETH',
|
|
73
|
+
decimals: 18,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
const mockApprovalController = {
|
|
78
|
+
requestApproval: jest.fn(),
|
|
79
|
+
onTransactionConfirmed: jest.fn(),
|
|
80
|
+
onTransactionReverted: jest.fn(),
|
|
81
|
+
};
|
|
64
82
|
|
|
83
|
+
describe('ethSign', () => {
|
|
65
84
|
beforeEach(() => {
|
|
66
85
|
mockApprovalController.requestApproval.mockResolvedValue({ result: '0x1234' });
|
|
67
86
|
mockBeautifySimpleMessage.mockReturnValue('beautified simple message');
|
|
@@ -75,6 +94,7 @@ describe('ethSign', () => {
|
|
|
75
94
|
request: mockRequest,
|
|
76
95
|
network: mockNetwork,
|
|
77
96
|
approvalController: mockApprovalController,
|
|
97
|
+
proxyApiUrl: PROXY_API_URL,
|
|
78
98
|
});
|
|
79
99
|
|
|
80
100
|
expect(result).toEqual({
|
|
@@ -96,16 +116,19 @@ describe('ethSign', () => {
|
|
|
96
116
|
request: { ...mockRequest, method },
|
|
97
117
|
network: mockNetwork,
|
|
98
118
|
approvalController: mockApprovalController,
|
|
119
|
+
proxyApiUrl: PROXY_API_URL,
|
|
99
120
|
});
|
|
100
121
|
|
|
101
122
|
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith(
|
|
102
123
|
expect.objectContaining({
|
|
103
124
|
displayData: expect.objectContaining({
|
|
104
|
-
|
|
105
|
-
type:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
alert: {
|
|
126
|
+
type: AlertType.INFO,
|
|
127
|
+
details: {
|
|
128
|
+
title: 'Warning: Verify Message Content',
|
|
129
|
+
description: 'This message contains non-standard elements.',
|
|
130
|
+
detailedDescription: 'Invalid typed data',
|
|
131
|
+
},
|
|
109
132
|
},
|
|
110
133
|
}),
|
|
111
134
|
}),
|
|
@@ -153,6 +176,7 @@ describe('ethSign', () => {
|
|
|
153
176
|
request: { ...mockRequest, method },
|
|
154
177
|
network: mockNetwork,
|
|
155
178
|
approvalController: mockApprovalController,
|
|
179
|
+
proxyApiUrl: PROXY_API_URL,
|
|
156
180
|
});
|
|
157
181
|
|
|
158
182
|
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith({
|
|
@@ -172,6 +196,9 @@ describe('ethSign', () => {
|
|
|
172
196
|
logoUri: 'test-logo-uri',
|
|
173
197
|
name: 'Ethereum',
|
|
174
198
|
},
|
|
199
|
+
alert: undefined,
|
|
200
|
+
balanceChange: undefined,
|
|
201
|
+
tokenApprovals: undefined,
|
|
175
202
|
},
|
|
176
203
|
request: { ...mockRequest, method },
|
|
177
204
|
signingData: {
|
|
@@ -184,6 +211,18 @@ describe('ethSign', () => {
|
|
|
184
211
|
},
|
|
185
212
|
);
|
|
186
213
|
|
|
214
|
+
it('should add alert object with Warning type to displayData when validation result is Warning', async () => {
|
|
215
|
+
testWithValidationResultType('Warning');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should add alert object with Warning type to displayData when validation result is Error', async () => {
|
|
219
|
+
testWithValidationResultType('Error');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should add alert object with Danger type to displayData when validation result is Malicious', async () => {
|
|
223
|
+
testWithValidationResultType('Malicious');
|
|
224
|
+
});
|
|
225
|
+
|
|
187
226
|
it('should handle success case for approvalController.requestApproval', async () => {
|
|
188
227
|
mockParseRequestParams.mockReturnValueOnce({
|
|
189
228
|
success: true,
|
|
@@ -194,6 +233,7 @@ describe('ethSign', () => {
|
|
|
194
233
|
request: mockRequest,
|
|
195
234
|
network: mockNetwork,
|
|
196
235
|
approvalController: mockApprovalController,
|
|
236
|
+
proxyApiUrl: PROXY_API_URL,
|
|
197
237
|
});
|
|
198
238
|
|
|
199
239
|
expect(result).toEqual({ result: '0x1234' });
|
|
@@ -210,8 +250,71 @@ describe('ethSign', () => {
|
|
|
210
250
|
request: mockRequest,
|
|
211
251
|
network: mockNetwork,
|
|
212
252
|
approvalController: mockApprovalController,
|
|
253
|
+
proxyApiUrl: PROXY_API_URL,
|
|
213
254
|
});
|
|
214
255
|
|
|
215
256
|
expect(result).toEqual({ error: 'User denied message signature' });
|
|
216
257
|
});
|
|
217
258
|
});
|
|
259
|
+
|
|
260
|
+
const testWithValidationResultType = async (resultType: 'Warning' | 'Error' | 'Malicious') => {
|
|
261
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
262
|
+
(Blockaid as any).mockImplementation(() => ({
|
|
263
|
+
evm: {
|
|
264
|
+
jsonRpc: {
|
|
265
|
+
scan: jest.fn().mockResolvedValue({
|
|
266
|
+
validation: { result_type: resultType },
|
|
267
|
+
simulation: { status: 'Success', account_summary: { exposures: [], assets_diffs: [] } },
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
mockParseRequestParams.mockReturnValueOnce({
|
|
274
|
+
success: true,
|
|
275
|
+
data: { method: RpcMethod.ETH_SIGN, data: 'data', address: '0xabc' },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const result = await ethSign({
|
|
279
|
+
request: mockRequest,
|
|
280
|
+
network: mockNetwork,
|
|
281
|
+
approvalController: mockApprovalController,
|
|
282
|
+
proxyApiUrl: PROXY_API_URL,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(result).toEqual({ result: '0x1234' });
|
|
286
|
+
|
|
287
|
+
if (resultType === 'Malicious') {
|
|
288
|
+
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith(
|
|
289
|
+
expect.objectContaining({
|
|
290
|
+
displayData: expect.objectContaining({
|
|
291
|
+
alert: {
|
|
292
|
+
type: AlertType.DANGER,
|
|
293
|
+
details: {
|
|
294
|
+
title: 'Scam Transaction',
|
|
295
|
+
description: 'This transaction is malicious, do not proceed.',
|
|
296
|
+
actionTitles: {
|
|
297
|
+
reject: 'Reject Transaction',
|
|
298
|
+
proceed: 'Proceed Anyway',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
expect(mockApprovalController.requestApproval).toHaveBeenCalledWith(
|
|
307
|
+
expect.objectContaining({
|
|
308
|
+
displayData: expect.objectContaining({
|
|
309
|
+
alert: {
|
|
310
|
+
type: AlertType.WARNING,
|
|
311
|
+
details: {
|
|
312
|
+
title: 'Suspicious Transaction',
|
|
313
|
+
description: 'Use caution, this transaction may be malicious.',
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
}),
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
type DisplayData,
|
|
6
6
|
type RpcRequest,
|
|
7
7
|
RpcMethod,
|
|
8
|
-
|
|
8
|
+
type Alert,
|
|
9
|
+
AlertType,
|
|
9
10
|
} from '@avalabs/vm-module-types';
|
|
10
11
|
import { rpcErrors } from '@metamask/rpc-errors';
|
|
11
12
|
import { toUtf8String } from 'ethers';
|
|
@@ -13,15 +14,18 @@ import { beautifySimpleMessage, beautifyComplexMessage } from './utils/beautify-
|
|
|
13
14
|
import { parseRequestParams } from './schemas/parse-request-params/parse-request-params';
|
|
14
15
|
import { isTypedDataV1 } from './utils/typeguards';
|
|
15
16
|
import { isTypedDataValid } from './utils/is-typed-data-valid';
|
|
17
|
+
import { processJsonRpcSimulation } from '../../utils/process-transaction-simulation';
|
|
16
18
|
|
|
17
19
|
export const ethSign = async ({
|
|
18
20
|
request,
|
|
19
21
|
network,
|
|
20
22
|
approvalController,
|
|
23
|
+
proxyApiUrl,
|
|
21
24
|
}: {
|
|
22
25
|
request: RpcRequest;
|
|
23
26
|
network: Network;
|
|
24
27
|
approvalController: ApprovalController;
|
|
28
|
+
proxyApiUrl: string;
|
|
25
29
|
}) => {
|
|
26
30
|
const result = parseRequestParams({ method: request.method, params: request.params });
|
|
27
31
|
|
|
@@ -46,14 +50,16 @@ export const ethSign = async ({
|
|
|
46
50
|
let signingData: SigningData | undefined;
|
|
47
51
|
let messageDetails: string | undefined;
|
|
48
52
|
let disclaimer: string | undefined;
|
|
49
|
-
let
|
|
53
|
+
let alert: Alert | undefined;
|
|
50
54
|
|
|
51
55
|
if (typedDataValidationResult && !typedDataValidationResult.isValid) {
|
|
52
|
-
|
|
53
|
-
type:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
alert = {
|
|
57
|
+
type: AlertType.INFO,
|
|
58
|
+
details: {
|
|
59
|
+
title: 'Warning: Verify Message Content',
|
|
60
|
+
description: 'This message contains non-standard elements.',
|
|
61
|
+
detailedDescription: (typedDataValidationResult.error as Error).toString(),
|
|
62
|
+
},
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -106,8 +112,20 @@ export const ethSign = async ({
|
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
const {
|
|
116
|
+
alert: prioritizedAlert,
|
|
117
|
+
balanceChange,
|
|
118
|
+
tokenApprovals,
|
|
119
|
+
} = await processJsonRpcSimulation({
|
|
120
|
+
request,
|
|
121
|
+
proxyApiUrl,
|
|
122
|
+
accountAddress: address,
|
|
123
|
+
chainId: network.chainId,
|
|
124
|
+
data: { method, params: request.params },
|
|
125
|
+
dAppUrl: request.dappInfo.url,
|
|
126
|
+
});
|
|
127
|
+
|
|
109
128
|
const displayData: DisplayData = {
|
|
110
|
-
banner,
|
|
111
129
|
title: 'Sign Message',
|
|
112
130
|
dAppInfo: {
|
|
113
131
|
name: request.dappInfo.name,
|
|
@@ -122,6 +140,9 @@ export const ethSign = async ({
|
|
|
122
140
|
account: address,
|
|
123
141
|
messageDetails,
|
|
124
142
|
disclaimer,
|
|
143
|
+
alert: prioritizedAlert ?? alert,
|
|
144
|
+
balanceChange,
|
|
145
|
+
tokenApprovals,
|
|
125
146
|
};
|
|
126
147
|
|
|
127
148
|
// prompt user for approval
|