mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-25 03:20:23 +01:00
Handle watch asset accept and reject using ApprovalController only (#18829)
This commit is contained in:
parent
b5ef94b9f0
commit
5355000202
@ -1,83 +1,77 @@
|
|||||||
export const suggestedAssets = [
|
import { ApprovalType } from '@metamask/controller-utils';
|
||||||
|
|
||||||
|
const suggestedAssets = [
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||||||
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
symbol: 'ETH',
|
||||||
symbol: 'ETH',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: './images/eth_logo.png',
|
||||||
image: './images/eth_logo.png',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
|
||||||
address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
|
symbol: '0X',
|
||||||
symbol: '0X',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: '0x.svg',
|
||||||
image: '0x.svg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
|
||||||
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
|
symbol: 'AST',
|
||||||
symbol: 'AST',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'ast.png',
|
||||||
image: 'ast.png',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
|
||||||
address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
|
symbol: 'BAT',
|
||||||
symbol: 'BAT',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'BAT_icon.svg',
|
||||||
image: 'BAT_icon.svg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1',
|
||||||
address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1',
|
symbol: 'CVL',
|
||||||
symbol: 'CVL',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'CVL_token.svg',
|
||||||
image: 'CVL_token.svg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
|
||||||
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
|
symbol: 'GLA',
|
||||||
symbol: 'GLA',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'gladius.svg',
|
||||||
image: 'gladius.svg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F',
|
||||||
address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F',
|
symbol: 'GNO',
|
||||||
symbol: 'GNO',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'gnosis.svg',
|
||||||
image: 'gnosis.svg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2',
|
||||||
address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2',
|
symbol: 'OMG',
|
||||||
symbol: 'OMG',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'omg.jpg',
|
||||||
image: 'omg.jpg',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
asset: {
|
address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1',
|
||||||
address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1',
|
symbol: 'WED',
|
||||||
symbol: 'WED',
|
decimals: 18,
|
||||||
decimals: 18,
|
image: 'wed.png',
|
||||||
image: 'wed.png',
|
unlisted: false,
|
||||||
unlisted: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const pendingAssetApprovals = suggestedAssets.map((asset, index) => {
|
||||||
|
return {
|
||||||
|
type: ApprovalType.WatchAsset,
|
||||||
|
requestData: {
|
||||||
|
id: index,
|
||||||
|
asset,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
@ -1153,7 +1153,6 @@ const state = {
|
|||||||
'0xaD6D458402F60fD3Bd25163575031ACDce07538D': './sai.svg',
|
'0xaD6D458402F60fD3Bd25163575031ACDce07538D': './sai.svg',
|
||||||
},
|
},
|
||||||
hiddenTokens: [],
|
hiddenTokens: [],
|
||||||
suggestedAssets: [],
|
|
||||||
useNonceField: false,
|
useNonceField: false,
|
||||||
usePhishDetect: true,
|
usePhishDetect: true,
|
||||||
useTokenDetection: true,
|
useTokenDetection: true,
|
||||||
|
@ -0,0 +1,211 @@
|
|||||||
|
diff --git a/dist/TokensController.js b/dist/TokensController.js
|
||||||
|
index 0e03b88e8a46dce73a5cc87fb432b0b2431b3797..fc8893fa6bad76d65aa34fa1bfb0b233b1259ae6 100644
|
||||||
|
--- a/dist/TokensController.js
|
||||||
|
+++ b/dist/TokensController.js
|
||||||
|
@@ -25,13 +25,6 @@ const base_controller_1 = require("@metamask/base-controller");
|
||||||
|
const controller_utils_1 = require("@metamask/controller-utils");
|
||||||
|
const assetsUtil_1 = require("./assetsUtil");
|
||||||
|
const token_service_1 = require("./token-service");
|
||||||
|
-var SuggestedAssetStatus;
|
||||||
|
-(function (SuggestedAssetStatus) {
|
||||||
|
- SuggestedAssetStatus["accepted"] = "accepted";
|
||||||
|
- SuggestedAssetStatus["failed"] = "failed";
|
||||||
|
- SuggestedAssetStatus["pending"] = "pending";
|
||||||
|
- SuggestedAssetStatus["rejected"] = "rejected";
|
||||||
|
-})(SuggestedAssetStatus || (SuggestedAssetStatus = {}));
|
||||||
|
/**
|
||||||
|
* The name of the {@link TokensController}.
|
||||||
|
*/
|
||||||
|
@@ -93,10 +86,6 @@ class TokensController extends base_controller_1.BaseController {
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- failSuggestedAsset(suggestedAssetMeta, error) {
|
||||||
|
- const failedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.failed, error });
|
||||||
|
- this.hub.emit(`${suggestedAssetMeta.id}:finished`, failedSuggestedAssetMeta);
|
||||||
|
- }
|
||||||
|
/**
|
||||||
|
* Fetch metadata for a token.
|
||||||
|
*
|
||||||
|
@@ -412,9 +401,10 @@ class TokensController extends base_controller_1.BaseController {
|
||||||
|
_generateRandomId() {
|
||||||
|
return (0, uuid_1.v1)();
|
||||||
|
}
|
||||||
|
+ // THIS PATCHED METHOD HAS ALREADY BEEN RELEASED IN VERSION 8.0.0 of @metamask/assets-controllers
|
||||||
|
/**
|
||||||
|
- * Adds a new suggestedAsset to state. Parameters will be validated according to
|
||||||
|
- * asset type being watched. A `<suggestedAssetMeta.id>:pending` hub event will be emitted once added.
|
||||||
|
+ * Adds a new suggestedAsset to the list of watched assets.
|
||||||
|
+ * Parameters will be validated according to the asset type being watched.
|
||||||
|
*
|
||||||
|
* @param asset - The asset to be watched. For now only ERC20 tokens are accepted.
|
||||||
|
* @param type - The asset type.
|
||||||
|
@@ -423,103 +413,22 @@ class TokensController extends base_controller_1.BaseController {
|
||||||
|
*/
|
||||||
|
watchAsset(asset, type, interactingAddress) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
+ if (type !== controller_utils_1.ERC20) {
|
||||||
|
+ throw new Error(`Asset of type ${type} not supported`);
|
||||||
|
+ }
|
||||||
|
const { selectedAddress } = this.config;
|
||||||
|
const suggestedAssetMeta = {
|
||||||
|
asset,
|
||||||
|
id: this._generateRandomId(),
|
||||||
|
- status: SuggestedAssetStatus.pending,
|
||||||
|
time: Date.now(),
|
||||||
|
type,
|
||||||
|
interactingAddress: interactingAddress || selectedAddress,
|
||||||
|
};
|
||||||
|
- try {
|
||||||
|
- switch (type) {
|
||||||
|
- case 'ERC20':
|
||||||
|
- (0, assetsUtil_1.validateTokenToWatch)(asset);
|
||||||
|
- break;
|
||||||
|
- default:
|
||||||
|
- throw new Error(`Asset of type ${type} not supported`);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- catch (error) {
|
||||||
|
- this.failSuggestedAsset(suggestedAssetMeta, error);
|
||||||
|
- return Promise.reject(error);
|
||||||
|
- }
|
||||||
|
- const result = new Promise((resolve, reject) => {
|
||||||
|
- this.hub.once(`${suggestedAssetMeta.id}:finished`, (meta) => {
|
||||||
|
- switch (meta.status) {
|
||||||
|
- case SuggestedAssetStatus.accepted:
|
||||||
|
- return resolve(meta.asset.address);
|
||||||
|
- case SuggestedAssetStatus.rejected:
|
||||||
|
- return reject(new Error('User rejected to watch the asset.'));
|
||||||
|
- case SuggestedAssetStatus.failed:
|
||||||
|
- return reject(new Error(meta.error.message));
|
||||||
|
- /* istanbul ignore next */
|
||||||
|
- default:
|
||||||
|
- return reject(new Error(`Unknown status: ${meta.status}`));
|
||||||
|
- }
|
||||||
|
- });
|
||||||
|
- });
|
||||||
|
- const { suggestedAssets } = this.state;
|
||||||
|
- suggestedAssets.push(suggestedAssetMeta);
|
||||||
|
- this.update({ suggestedAssets: [...suggestedAssets] });
|
||||||
|
- this._requestApproval(suggestedAssetMeta);
|
||||||
|
- return { result, suggestedAssetMeta };
|
||||||
|
- });
|
||||||
|
- }
|
||||||
|
- /**
|
||||||
|
- * Accepts to watch an asset and updates it's status and deletes the suggestedAsset from state,
|
||||||
|
- * adding the asset to corresponding asset state. In this case ERC20 tokens.
|
||||||
|
- * A `<suggestedAssetMeta.id>:finished` hub event is fired after accepted or failure.
|
||||||
|
- *
|
||||||
|
- * @param suggestedAssetID - The ID of the suggestedAsset to accept.
|
||||||
|
- */
|
||||||
|
- acceptWatchAsset(suggestedAssetID) {
|
||||||
|
- return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
- const { selectedAddress } = this.config;
|
||||||
|
- const { suggestedAssets } = this.state;
|
||||||
|
- const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id);
|
||||||
|
- const suggestedAssetMeta = suggestedAssets[index];
|
||||||
|
- try {
|
||||||
|
- switch (suggestedAssetMeta.type) {
|
||||||
|
- case 'ERC20':
|
||||||
|
- const { address, symbol, decimals, image } = suggestedAssetMeta.asset;
|
||||||
|
- yield this.addToken(address, symbol, decimals, image, (suggestedAssetMeta === null || suggestedAssetMeta === void 0 ? void 0 : suggestedAssetMeta.interactingAddress) || selectedAddress);
|
||||||
|
- this._acceptApproval(suggestedAssetID);
|
||||||
|
- const acceptedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.accepted });
|
||||||
|
- this.hub.emit(`${suggestedAssetMeta.id}:finished`, acceptedSuggestedAssetMeta);
|
||||||
|
- break;
|
||||||
|
- default:
|
||||||
|
- throw new Error(`Asset of type ${suggestedAssetMeta.type} not supported`);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- catch (error) {
|
||||||
|
- this.failSuggestedAsset(suggestedAssetMeta, error);
|
||||||
|
- this._rejectApproval(suggestedAssetID);
|
||||||
|
- }
|
||||||
|
- const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID);
|
||||||
|
- this.update({ suggestedAssets: [...newSuggestedAssets] });
|
||||||
|
+ (0, assetsUtil_1.validateTokenToWatch)(asset);
|
||||||
|
+ yield this._requestApproval(suggestedAssetMeta);
|
||||||
|
+ yield this.addToken(asset.address, asset.symbol, asset.decimals, asset.image, suggestedAssetMeta.interactingAddress);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- /**
|
||||||
|
- * Rejects a watchAsset request based on its ID by setting its status to "rejected"
|
||||||
|
- * and emitting a `<suggestedAssetMeta.id>:finished` hub event.
|
||||||
|
- *
|
||||||
|
- * @param suggestedAssetID - The ID of the suggestedAsset to accept.
|
||||||
|
- */
|
||||||
|
- rejectWatchAsset(suggestedAssetID) {
|
||||||
|
- const { suggestedAssets } = this.state;
|
||||||
|
- const index = suggestedAssets.findIndex(({ id }) => suggestedAssetID === id);
|
||||||
|
- const suggestedAssetMeta = suggestedAssets[index];
|
||||||
|
- if (!suggestedAssetMeta) {
|
||||||
|
- return;
|
||||||
|
- }
|
||||||
|
- const rejectedSuggestedAssetMeta = Object.assign(Object.assign({}, suggestedAssetMeta), { status: SuggestedAssetStatus.rejected });
|
||||||
|
- this.hub.emit(`${suggestedAssetMeta.id}:finished`, rejectedSuggestedAssetMeta);
|
||||||
|
- const newSuggestedAssets = suggestedAssets.filter(({ id }) => id !== suggestedAssetID);
|
||||||
|
- this.update({ suggestedAssets: [...newSuggestedAssets] });
|
||||||
|
- this._rejectApproval(suggestedAssetID);
|
||||||
|
- }
|
||||||
|
/**
|
||||||
|
* Takes a new tokens and ignoredTokens array for the current network/account combination
|
||||||
|
* and returns new allTokens and allIgnoredTokens state to update to.
|
||||||
|
@@ -576,43 +485,26 @@ class TokensController extends base_controller_1.BaseController {
|
||||||
|
clearIgnoredTokens() {
|
||||||
|
this.update({ ignoredTokens: [], allIgnoredTokens: {} });
|
||||||
|
}
|
||||||
|
+ // THIS PATCHED METHOD HAS ALREADY BEEN RELEASED IN VERSION 8.0.0 of @metamask/assets-controllers
|
||||||
|
_requestApproval(suggestedAssetMeta) {
|
||||||
|
- this.messagingSystem
|
||||||
|
- .call('ApprovalController:addRequest', {
|
||||||
|
- id: suggestedAssetMeta.id,
|
||||||
|
- origin: controller_utils_1.ORIGIN_METAMASK,
|
||||||
|
- type: controller_utils_1.ApprovalType.WatchAsset,
|
||||||
|
- requestData: {
|
||||||
|
+ return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
+ return this.messagingSystem.call('ApprovalController:addRequest', {
|
||||||
|
id: suggestedAssetMeta.id,
|
||||||
|
- interactingAddress: suggestedAssetMeta.interactingAddress,
|
||||||
|
- asset: {
|
||||||
|
- address: suggestedAssetMeta.asset.address,
|
||||||
|
- decimals: suggestedAssetMeta.asset.decimals,
|
||||||
|
- symbol: suggestedAssetMeta.asset.symbol,
|
||||||
|
- image: suggestedAssetMeta.asset.image || null,
|
||||||
|
+ origin: controller_utils_1.ORIGIN_METAMASK,
|
||||||
|
+ type: controller_utils_1.ApprovalType.WatchAsset,
|
||||||
|
+ requestData: {
|
||||||
|
+ id: suggestedAssetMeta.id,
|
||||||
|
+ interactingAddress: suggestedAssetMeta.interactingAddress,
|
||||||
|
+ asset: {
|
||||||
|
+ address: suggestedAssetMeta.asset.address,
|
||||||
|
+ decimals: suggestedAssetMeta.asset.decimals,
|
||||||
|
+ symbol: suggestedAssetMeta.asset.symbol,
|
||||||
|
+ image: suggestedAssetMeta.asset.image || null,
|
||||||
|
+ },
|
||||||
|
},
|
||||||
|
- },
|
||||||
|
- }, true)
|
||||||
|
- .catch(() => {
|
||||||
|
- // Intentionally ignored as promise not currently used
|
||||||
|
+ }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
- _acceptApproval(approvalRequestId) {
|
||||||
|
- try {
|
||||||
|
- this.messagingSystem.call('ApprovalController:acceptRequest', approvalRequestId);
|
||||||
|
- }
|
||||||
|
- catch (error) {
|
||||||
|
- console.error('Failed to accept token watch approval request', error);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
- _rejectApproval(approvalRequestId) {
|
||||||
|
- try {
|
||||||
|
- this.messagingSystem.call('ApprovalController:rejectRequest', approvalRequestId, new Error('Rejected'));
|
||||||
|
- }
|
||||||
|
- catch (messageCallError) {
|
||||||
|
- console.error('Failed to reject token watch approval request', messageCallError);
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
}
|
||||||
|
exports.TokensController = TokensController;
|
||||||
|
exports.default = TokensController;
|
@ -1,4 +1,3 @@
|
|||||||
import { ethErrors } from 'eth-rpc-errors';
|
|
||||||
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
|
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
|
||||||
|
|
||||||
const watchAsset = {
|
const watchAsset = {
|
||||||
@ -37,14 +36,10 @@ async function watchAssetHandler(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { options: asset, type } = req.params;
|
const { options: asset, type } = req.params;
|
||||||
const handleWatchAssetResult = await handleWatchAssetRequest(asset, type);
|
await handleWatchAssetRequest(asset, type);
|
||||||
await handleWatchAssetResult.result;
|
|
||||||
res.result = true;
|
res.result = true;
|
||||||
return end();
|
return end();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message === 'User rejected to watch the asset.') {
|
|
||||||
return end(ethErrors.provider.userRejectedRequest());
|
|
||||||
}
|
|
||||||
return end(error);
|
return end(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2121,10 +2121,6 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
preferencesController,
|
preferencesController,
|
||||||
),
|
),
|
||||||
addToken: tokensController.addToken.bind(tokensController),
|
addToken: tokensController.addToken.bind(tokensController),
|
||||||
rejectWatchAsset:
|
|
||||||
tokensController.rejectWatchAsset.bind(tokensController),
|
|
||||||
acceptWatchAsset:
|
|
||||||
tokensController.acceptWatchAsset.bind(tokensController),
|
|
||||||
updateTokenType: tokensController.updateTokenType.bind(tokensController),
|
updateTokenType: tokensController.updateTokenType.bind(tokensController),
|
||||||
setAccountLabel: preferencesController.setAccountLabel.bind(
|
setAccountLabel: preferencesController.setAccountLabel.bind(
|
||||||
preferencesController,
|
preferencesController,
|
||||||
|
89
app/scripts/migrations/087.test.js
Normal file
89
app/scripts/migrations/087.test.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { migrate, version } from './087';
|
||||||
|
|
||||||
|
describe('migration #87', () => {
|
||||||
|
it('should update the version metadata', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {
|
||||||
|
version: 86,
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migrate(oldStorage);
|
||||||
|
expect(newStorage.meta).toStrictEqual({
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return state unaltered if there is no tokens controller state', async () => {
|
||||||
|
const oldData = {
|
||||||
|
other: 'data',
|
||||||
|
};
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {
|
||||||
|
version: 86,
|
||||||
|
},
|
||||||
|
data: oldData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migrate(oldStorage);
|
||||||
|
expect(newStorage.data).toStrictEqual(oldData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return state unaltered if there is no tokens controller suggested assets state', async () => {
|
||||||
|
const oldData = {
|
||||||
|
other: 'data',
|
||||||
|
TokensController: {
|
||||||
|
allDetectedTokens: {},
|
||||||
|
allIgnoredTokens: {},
|
||||||
|
allTokens: {},
|
||||||
|
detectedTokens: [],
|
||||||
|
ignoredTokens: [],
|
||||||
|
tokens: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {
|
||||||
|
version: 86,
|
||||||
|
},
|
||||||
|
data: oldData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migrate(oldStorage);
|
||||||
|
expect(newStorage.data).toStrictEqual(oldData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the suggested assets state', async () => {
|
||||||
|
const oldData = {
|
||||||
|
other: 'data',
|
||||||
|
TokensController: {
|
||||||
|
allDetectedTokens: {},
|
||||||
|
allIgnoredTokens: {},
|
||||||
|
allTokens: {},
|
||||||
|
detectedTokens: [],
|
||||||
|
ignoredTokens: [],
|
||||||
|
suggestedAssets: [],
|
||||||
|
tokens: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {
|
||||||
|
version: 86,
|
||||||
|
},
|
||||||
|
data: oldData,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migrate(oldStorage);
|
||||||
|
expect(newStorage.data).toStrictEqual({
|
||||||
|
other: 'data',
|
||||||
|
TokensController: {
|
||||||
|
allDetectedTokens: {},
|
||||||
|
allIgnoredTokens: {},
|
||||||
|
allTokens: {},
|
||||||
|
detectedTokens: [],
|
||||||
|
ignoredTokens: [],
|
||||||
|
tokens: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
33
app/scripts/migrations/087.ts
Normal file
33
app/scripts/migrations/087.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { isObject } from '@metamask/utils';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
export const version = 87;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the now-obsolete tokens controller `suggestedAssets` state.
|
||||||
|
*
|
||||||
|
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
||||||
|
* @param originalVersionedData.meta - State metadata.
|
||||||
|
* @param originalVersionedData.meta.version - The current state version.
|
||||||
|
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
||||||
|
* @returns Updated versioned MetaMask extension state.
|
||||||
|
*/
|
||||||
|
export async function migrate(originalVersionedData: {
|
||||||
|
meta: { version: number };
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
}) {
|
||||||
|
const versionedData = cloneDeep(originalVersionedData);
|
||||||
|
versionedData.meta.version = version;
|
||||||
|
versionedData.data = transformState(versionedData.data);
|
||||||
|
return versionedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformState(state: Record<string, unknown>) {
|
||||||
|
if (!isObject(state.TokensController)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete state.TokensController.suggestedAssets;
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
@ -90,6 +90,7 @@ import * as m083 from './083';
|
|||||||
import * as m084 from './084';
|
import * as m084 from './084';
|
||||||
import * as m085 from './085';
|
import * as m085 from './085';
|
||||||
import * as m086 from './086';
|
import * as m086 from './086';
|
||||||
|
import * as m087 from './087';
|
||||||
|
|
||||||
const migrations = [
|
const migrations = [
|
||||||
m002,
|
m002,
|
||||||
@ -177,6 +178,7 @@ const migrations = [
|
|||||||
m084,
|
m084,
|
||||||
m085,
|
m085,
|
||||||
m086,
|
m086,
|
||||||
|
m087,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default migrations;
|
export default migrations;
|
||||||
|
@ -197,6 +197,7 @@
|
|||||||
"request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch",
|
"request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch",
|
||||||
"request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch",
|
"request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch",
|
||||||
"@metamask/assets-controllers@^6.0.0": "patch:@metamask/assets-controllers@npm%3A6.0.0#./.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch",
|
"@metamask/assets-controllers@^6.0.0": "patch:@metamask/assets-controllers@npm%3A6.0.0#./.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch",
|
||||||
|
"@metamask/assets-controllers@^7.0.0": "patch:@metamask/assets-controllers@npm%3A7.0.0#./.yarn/patches/@metamask-assets-controllers-npm-7.0.0-9dec51787d.patch",
|
||||||
"@metamask/signature-controller@^2.0.0": "patch:@metamask/signature-controller@npm%3A2.0.0#./.yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch"
|
"@metamask/signature-controller@^2.0.0": "patch:@metamask/signature-controller@npm%3A2.0.0#./.yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -290,7 +290,6 @@ function defaultFixture() {
|
|||||||
allTokens: {},
|
allTokens: {},
|
||||||
detectedTokens: [],
|
detectedTokens: [],
|
||||||
ignoredTokens: [],
|
ignoredTokens: [],
|
||||||
suggestedAssets: [],
|
|
||||||
tokens: [],
|
tokens: [],
|
||||||
},
|
},
|
||||||
TransactionController: {
|
TransactionController: {
|
||||||
@ -394,7 +393,6 @@ function onboardingFixture() {
|
|||||||
allTokens: {},
|
allTokens: {},
|
||||||
detectedTokens: [],
|
detectedTokens: [],
|
||||||
ignoredTokens: [],
|
ignoredTokens: [],
|
||||||
suggestedAssets: [],
|
|
||||||
tokens: [],
|
tokens: [],
|
||||||
},
|
},
|
||||||
config: {},
|
config: {},
|
||||||
@ -740,7 +738,6 @@ class FixtureBuilder {
|
|||||||
},
|
},
|
||||||
allIgnoredTokens: {},
|
allIgnoredTokens: {},
|
||||||
allDetectedTokens: {},
|
allDetectedTokens: {},
|
||||||
suggestedAssets: [],
|
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -123,3 +123,122 @@ describe('Add existing token using search', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Add token using wallet_watchAsset', function () {
|
||||||
|
const ganacheOptions = {
|
||||||
|
accounts: [
|
||||||
|
{
|
||||||
|
secretKey:
|
||||||
|
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
|
||||||
|
balance: convertToHexValue(25000000000000000000),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('opens a notification that adds a token when wallet_watchAsset is executed, then approves', async function () {
|
||||||
|
await withFixtures(
|
||||||
|
{
|
||||||
|
dapp: true,
|
||||||
|
fixtures: new FixtureBuilder()
|
||||||
|
.withPermissionControllerConnectedToTestDapp()
|
||||||
|
.build(),
|
||||||
|
ganacheOptions,
|
||||||
|
title: this.test.title,
|
||||||
|
},
|
||||||
|
async ({ driver }) => {
|
||||||
|
await driver.navigate();
|
||||||
|
await driver.fill('#password', 'correct horse battery staple');
|
||||||
|
await driver.press('#password', driver.Key.ENTER);
|
||||||
|
|
||||||
|
await driver.openNewPage('http://127.0.0.1:8080/');
|
||||||
|
|
||||||
|
await driver.executeScript(`
|
||||||
|
window.ethereum.request({
|
||||||
|
method: 'wallet_watchAsset',
|
||||||
|
params: {
|
||||||
|
type: 'ERC20',
|
||||||
|
options: {
|
||||||
|
address: '0x86002be4cdd922de1ccb831582bf99284b99ac12',
|
||||||
|
symbol: 'TST',
|
||||||
|
decimals: 4
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
`);
|
||||||
|
|
||||||
|
const windowHandles = await driver.waitUntilXWindowHandles(3);
|
||||||
|
|
||||||
|
await driver.switchToWindowWithTitle(
|
||||||
|
'MetaMask Notification',
|
||||||
|
windowHandles,
|
||||||
|
);
|
||||||
|
|
||||||
|
await driver.clickElement({
|
||||||
|
tag: 'button',
|
||||||
|
text: 'Add token',
|
||||||
|
});
|
||||||
|
|
||||||
|
await driver.switchToWindowWithTitle('MetaMask', windowHandles);
|
||||||
|
|
||||||
|
await driver.waitForSelector({
|
||||||
|
css: '[data-testid="multichain-token-list-item-value"]',
|
||||||
|
text: '0 TST',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens a notification that adds a token when wallet_watchAsset is executed, then rejects', async function () {
|
||||||
|
await withFixtures(
|
||||||
|
{
|
||||||
|
dapp: true,
|
||||||
|
fixtures: new FixtureBuilder()
|
||||||
|
.withPermissionControllerConnectedToTestDapp()
|
||||||
|
.build(),
|
||||||
|
ganacheOptions,
|
||||||
|
title: this.test.title,
|
||||||
|
},
|
||||||
|
async ({ driver }) => {
|
||||||
|
await driver.navigate();
|
||||||
|
await driver.fill('#password', 'correct horse battery staple');
|
||||||
|
await driver.press('#password', driver.Key.ENTER);
|
||||||
|
|
||||||
|
await driver.openNewPage('http://127.0.0.1:8080/');
|
||||||
|
|
||||||
|
await driver.executeScript(`
|
||||||
|
window.ethereum.request({
|
||||||
|
method: 'wallet_watchAsset',
|
||||||
|
params: {
|
||||||
|
type: 'ERC20',
|
||||||
|
options: {
|
||||||
|
address: '0x86002be4cdd922de1ccb831582bf99284b99ac12',
|
||||||
|
symbol: 'TST',
|
||||||
|
decimals: 4
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
`);
|
||||||
|
|
||||||
|
const windowHandles = await driver.waitUntilXWindowHandles(3);
|
||||||
|
|
||||||
|
await driver.switchToWindowWithTitle(
|
||||||
|
'MetaMask Notification',
|
||||||
|
windowHandles,
|
||||||
|
);
|
||||||
|
|
||||||
|
await driver.clickElement({
|
||||||
|
tag: 'button',
|
||||||
|
text: 'Cancel',
|
||||||
|
});
|
||||||
|
|
||||||
|
await driver.switchToWindowWithTitle('MetaMask', windowHandles);
|
||||||
|
|
||||||
|
const assetListItems = await driver.findElements(
|
||||||
|
'.multichain-token-list-item',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(assetListItems.length, 1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
|
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { ethErrors, serializeError } from 'eth-rpc-errors';
|
||||||
|
import { ApprovalType } from '@metamask/controller-utils';
|
||||||
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
|
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
|
||||||
import Button from '../../components/ui/button';
|
import Button from '../../components/ui/button';
|
||||||
import Identicon from '../../components/ui/identicon';
|
import Identicon from '../../components/ui/identicon';
|
||||||
@ -12,8 +14,11 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
|||||||
import { getTokens } from '../../ducks/metamask/metamask';
|
import { getTokens } from '../../ducks/metamask/metamask';
|
||||||
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
|
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
|
||||||
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
||||||
import { getSuggestedAssets } from '../../selectors';
|
import { getApprovalRequestsByType } from '../../selectors';
|
||||||
import { rejectWatchAsset, acceptWatchAsset } from '../../store/actions';
|
import {
|
||||||
|
resolvePendingApproval,
|
||||||
|
rejectPendingApproval,
|
||||||
|
} from '../../store/actions';
|
||||||
import {
|
import {
|
||||||
MetaMetricsEventCategory,
|
MetaMetricsEventCategory,
|
||||||
MetaMetricsEventName,
|
MetaMetricsEventName,
|
||||||
@ -72,7 +77,11 @@ const ConfirmAddSuggestedToken = () => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||||
const suggestedAssets = useSelector(getSuggestedAssets);
|
const suggestedAssets = useSelector((metamaskState) =>
|
||||||
|
getApprovalRequestsByType(metamaskState, ApprovalType.WatchAsset).map(
|
||||||
|
({ requestData }) => requestData,
|
||||||
|
),
|
||||||
|
);
|
||||||
const tokens = useSelector(getTokens);
|
const tokens = useSelector(getTokens);
|
||||||
|
|
||||||
const trackEvent = useContext(MetaMetricsContext);
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
@ -119,7 +128,7 @@ const ConfirmAddSuggestedToken = () => {
|
|||||||
const handleAddTokensClick = useCallback(async () => {
|
const handleAddTokensClick = useCallback(async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
suggestedAssets.map(async ({ asset, id }) => {
|
suggestedAssets.map(async ({ asset, id }) => {
|
||||||
await dispatch(acceptWatchAsset(id));
|
await dispatch(resolvePendingApproval(id, null));
|
||||||
|
|
||||||
trackEvent({
|
trackEvent({
|
||||||
event: MetaMetricsEventName.TokenAdded,
|
event: MetaMetricsEventName.TokenAdded,
|
||||||
@ -136,10 +145,23 @@ const ConfirmAddSuggestedToken = () => {
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
history.push(mostRecentOverviewPage);
|
history.push(mostRecentOverviewPage);
|
||||||
}, [dispatch, history, trackEvent, mostRecentOverviewPage, suggestedAssets]);
|
}, [dispatch, history, trackEvent, mostRecentOverviewPage, suggestedAssets]);
|
||||||
|
|
||||||
|
const handleCancelClick = useCallback(async () => {
|
||||||
|
await Promise.all(
|
||||||
|
suggestedAssets.map(({ id }) =>
|
||||||
|
dispatch(
|
||||||
|
rejectPendingApproval(
|
||||||
|
id,
|
||||||
|
serializeError(ethErrors.provider.userRejectedRequest()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
history.push(mostRecentOverviewPage);
|
||||||
|
}, [dispatch, history, mostRecentOverviewPage, suggestedAssets]);
|
||||||
|
|
||||||
const goBackIfNoSuggestedAssetsOnFirstRender = () => {
|
const goBackIfNoSuggestedAssetsOnFirstRender = () => {
|
||||||
if (!suggestedAssets.length) {
|
if (!suggestedAssets.length) {
|
||||||
history.push(mostRecentOverviewPage);
|
history.push(mostRecentOverviewPage);
|
||||||
@ -201,12 +223,7 @@ const ConfirmAddSuggestedToken = () => {
|
|||||||
<PageContainerFooter
|
<PageContainerFooter
|
||||||
cancelText={t('cancel')}
|
cancelText={t('cancel')}
|
||||||
submitText={t('addToken')}
|
submitText={t('addToken')}
|
||||||
onCancel={async () => {
|
onCancel={handleCancelClick}
|
||||||
await Promise.all(
|
|
||||||
suggestedAssets.map(({ id }) => dispatch(rejectWatchAsset(id))),
|
|
||||||
);
|
|
||||||
history.push(mostRecentOverviewPage);
|
|
||||||
}}
|
|
||||||
onSubmit={handleAddTokensClick}
|
onSubmit={handleAddTokensClick}
|
||||||
disabled={suggestedAssets.length === 0}
|
disabled={suggestedAssets.length === 0}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { suggestedAssets as mockSuggestedAssets } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
|
import { pendingAssetApprovals as mockPendingAssetApprovals } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
|
||||||
|
|
||||||
import configureStore from '../../store/store';
|
import configureStore from '../../store/store';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import ConfirmAddSuggestedToken from '.';
|
|||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
...mockState.metamask,
|
...mockState.metamask,
|
||||||
suggestedAssets: [...mockSuggestedAssets],
|
pendingApprovals: [...mockPendingAssetApprovals],
|
||||||
tokens: [],
|
tokens: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -29,10 +29,10 @@ export const WithDuplicateAddress = () => <ConfirmAddSuggestedToken />;
|
|||||||
const WithDuplicateAddressStore = configureStore({
|
const WithDuplicateAddressStore = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
...mockState.metamask,
|
...mockState.metamask,
|
||||||
suggestedAssets: [...mockSuggestedAssets],
|
pendingApprovals: [...mockPendingAssetApprovals],
|
||||||
tokens: [
|
tokens: [
|
||||||
{
|
{
|
||||||
...mockSuggestedAssets[0].asset,
|
...mockPendingAssetApprovals[0].requestData.asset,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -47,10 +47,10 @@ export const WithDuplicateSymbolAndDifferentAddress = () => (
|
|||||||
const WithDuplicateSymbolAndDifferentAddressStore = configureStore({
|
const WithDuplicateSymbolAndDifferentAddressStore = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
...mockState.metamask,
|
...mockState.metamask,
|
||||||
suggestedAssets: [...mockSuggestedAssets],
|
pendingApprovals: [...mockPendingAssetApprovals],
|
||||||
tokens: [
|
tokens: [
|
||||||
{
|
{
|
||||||
...mockSuggestedAssets[0].asset,
|
...mockPendingAssetApprovals[0].requestData.asset,
|
||||||
address: '0xNonSuggestedAddress',
|
address: '0xNonSuggestedAddress',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { ApprovalType } from '@metamask/controller-utils';
|
||||||
import { fireEvent, screen } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
import { acceptWatchAsset, rejectWatchAsset } from '../../store/actions';
|
import {
|
||||||
|
resolvePendingApproval,
|
||||||
|
rejectPendingApproval,
|
||||||
|
} from '../../store/actions';
|
||||||
import configureStore from '../../store/store';
|
import configureStore from '../../store/store';
|
||||||
import { renderWithProvider } from '../../../test/jest/rendering';
|
import { renderWithProvider } from '../../../test/jest/rendering';
|
||||||
import ConfirmAddSuggestedToken from '.';
|
import ConfirmAddSuggestedToken from '.';
|
||||||
@ -28,6 +33,15 @@ const MOCK_SUGGESTED_ASSETS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const MOCK_PENDING_ASSET_APPROVALS = MOCK_SUGGESTED_ASSETS.map(
|
||||||
|
(requestData) => {
|
||||||
|
return {
|
||||||
|
type: ApprovalType.WatchAsset,
|
||||||
|
requestData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const MOCK_TOKEN = {
|
const MOCK_TOKEN = {
|
||||||
address: '0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d',
|
address: '0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d',
|
||||||
symbol: 'TEST',
|
symbol: 'TEST',
|
||||||
@ -35,14 +49,14 @@ const MOCK_TOKEN = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('../../store/actions', () => ({
|
jest.mock('../../store/actions', () => ({
|
||||||
acceptWatchAsset: jest.fn().mockReturnValue({ type: 'test' }),
|
resolvePendingApproval: jest.fn().mockReturnValue({ type: 'test' }),
|
||||||
rejectWatchAsset: jest.fn().mockReturnValue({ type: 'test' }),
|
rejectPendingApproval: jest.fn().mockReturnValue({ type: 'test' }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const renderComponent = (tokens = []) => {
|
const renderComponent = (tokens = []) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
suggestedAssets: [...MOCK_SUGGESTED_ASSETS],
|
pendingApprovals: [...MOCK_PENDING_ASSET_APPROVALS],
|
||||||
tokens,
|
tokens,
|
||||||
providerConfig: { chainId: '0x1' },
|
providerConfig: { chainId: '0x1' },
|
||||||
},
|
},
|
||||||
@ -80,23 +94,45 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch acceptWatchAsset when clicking the "Add token" button', () => {
|
it('should dispatch resolvePendingApproval when clicking the "Add token" button', async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
const addTokenBtn = screen.getByRole('button', { name: 'Add token' });
|
const addTokenBtn = screen.getByRole('button', { name: 'Add token' });
|
||||||
|
|
||||||
fireEvent.click(addTokenBtn);
|
await act(async () => {
|
||||||
expect(acceptWatchAsset).toHaveBeenCalled();
|
fireEvent.click(addTokenBtn);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resolvePendingApproval).toHaveBeenCalledTimes(
|
||||||
|
MOCK_SUGGESTED_ASSETS.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
MOCK_SUGGESTED_ASSETS.forEach(({ id }) => {
|
||||||
|
expect(resolvePendingApproval).toHaveBeenCalledWith(id, null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dispatch rejectWatchAsset when clicking the "Cancel" button', () => {
|
it('should dispatch rejectPendingApproval when clicking the "Cancel" button', async () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
const cancelBtn = screen.getByRole('button', { name: 'Cancel' });
|
const cancelBtn = screen.getByRole('button', { name: 'Cancel' });
|
||||||
|
|
||||||
expect(rejectWatchAsset).toHaveBeenCalledTimes(0);
|
await act(async () => {
|
||||||
fireEvent.click(cancelBtn);
|
fireEvent.click(cancelBtn);
|
||||||
expect(rejectWatchAsset).toHaveBeenCalledTimes(
|
});
|
||||||
|
|
||||||
|
expect(rejectPendingApproval).toHaveBeenCalledTimes(
|
||||||
MOCK_SUGGESTED_ASSETS.length,
|
MOCK_SUGGESTED_ASSETS.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
MOCK_SUGGESTED_ASSETS.forEach(({ id }) => {
|
||||||
|
expect(rejectPendingApproval).toHaveBeenCalledWith(
|
||||||
|
id,
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 4001,
|
||||||
|
message: 'User rejected the request.',
|
||||||
|
stack: expect.any(String),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the suggested token address matches an existing token address', () => {
|
describe('when the suggested token address matches an existing token address', () => {
|
||||||
|
@ -502,9 +502,7 @@ export function getCurrentCurrency(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTotalUnapprovedCount(state) {
|
export function getTotalUnapprovedCount(state) {
|
||||||
const { pendingApprovalCount = 0 } = state.metamask;
|
return state.metamask.pendingApprovalCount ?? 0;
|
||||||
|
|
||||||
return pendingApprovalCount + getSuggestedAssetCount(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTotalUnapprovedMessagesCount(state) {
|
export function getTotalUnapprovedMessagesCount(state) {
|
||||||
@ -556,15 +554,6 @@ export function getUnapprovedTemplatedConfirmations(state) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSuggestedAssetCount(state) {
|
|
||||||
const { suggestedAssets = [] } = state.metamask;
|
|
||||||
return suggestedAssets.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSuggestedAssets(state) {
|
|
||||||
return state.metamask.suggestedAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getIsMainnet(state) {
|
export function getIsMainnet(state) {
|
||||||
const chainId = getCurrentChainId(state);
|
const chainId = getCurrentChainId(state);
|
||||||
return chainId === CHAIN_IDS.MAINNET;
|
return chainId === CHAIN_IDS.MAINNET;
|
||||||
|
@ -2298,44 +2298,6 @@ export function addTokens(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rejectWatchAsset(
|
|
||||||
suggestedAssetID: string,
|
|
||||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
|
||||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
|
||||||
dispatch(showLoadingIndication());
|
|
||||||
try {
|
|
||||||
await submitRequestToBackground('rejectWatchAsset', [suggestedAssetID]);
|
|
||||||
await forceUpdateMetamaskState(dispatch);
|
|
||||||
} catch (error) {
|
|
||||||
logErrorWithMessage(error);
|
|
||||||
dispatch(displayWarning(error));
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
dispatch(hideLoadingIndication());
|
|
||||||
}
|
|
||||||
dispatch(closeCurrentNotificationWindow());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function acceptWatchAsset(
|
|
||||||
suggestedAssetID: string,
|
|
||||||
): ThunkAction<void, MetaMaskReduxState, unknown, AnyAction> {
|
|
||||||
return async (dispatch: MetaMaskReduxDispatch) => {
|
|
||||||
dispatch(showLoadingIndication());
|
|
||||||
try {
|
|
||||||
await submitRequestToBackground('acceptWatchAsset', [suggestedAssetID]);
|
|
||||||
await forceUpdateMetamaskState(dispatch);
|
|
||||||
} catch (error) {
|
|
||||||
logErrorWithMessage(error);
|
|
||||||
dispatch(displayWarning(error));
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
dispatch(hideLoadingIndication());
|
|
||||||
}
|
|
||||||
dispatch(closeCurrentNotificationWindow());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearPendingTokens(): Action {
|
export function clearPendingTokens(): Action {
|
||||||
return {
|
return {
|
||||||
type: actionConstants.CLEAR_PENDING_TOKENS,
|
type: actionConstants.CLEAR_PENDING_TOKENS,
|
||||||
|
36
yarn.lock
36
yarn.lock
@ -3873,7 +3873,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@metamask/assets-controllers@npm:^7.0.0":
|
"@metamask/assets-controllers@npm:7.0.0":
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
resolution: "@metamask/assets-controllers@npm:7.0.0"
|
resolution: "@metamask/assets-controllers@npm:7.0.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3907,6 +3907,40 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A7.0.0#./.yarn/patches/@metamask-assets-controllers-npm-7.0.0-9dec51787d.patch::locator=metamask-crx%40workspace%3A.":
|
||||||
|
version: 7.0.0
|
||||||
|
resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A7.0.0#./.yarn/patches/@metamask-assets-controllers-npm-7.0.0-9dec51787d.patch::version=7.0.0&hash=e60732&locator=metamask-crx%40workspace%3A."
|
||||||
|
dependencies:
|
||||||
|
"@ethersproject/bignumber": ^5.7.0
|
||||||
|
"@ethersproject/contracts": ^5.7.0
|
||||||
|
"@ethersproject/providers": ^5.7.0
|
||||||
|
"@metamask/abi-utils": ^1.1.0
|
||||||
|
"@metamask/approval-controller": ^2.1.1
|
||||||
|
"@metamask/base-controller": ^2.0.0
|
||||||
|
"@metamask/contract-metadata": ^2.3.1
|
||||||
|
"@metamask/controller-utils": ^3.4.0
|
||||||
|
"@metamask/metamask-eth-abis": 3.0.0
|
||||||
|
"@metamask/network-controller": ^8.0.0
|
||||||
|
"@metamask/preferences-controller": ^3.0.0
|
||||||
|
"@metamask/utils": ^5.0.1
|
||||||
|
"@types/uuid": ^8.3.0
|
||||||
|
abort-controller: ^3.0.0
|
||||||
|
async-mutex: ^0.2.6
|
||||||
|
babel-runtime: ^6.26.0
|
||||||
|
eth-query: ^2.1.2
|
||||||
|
eth-rpc-errors: ^4.0.2
|
||||||
|
ethereumjs-util: ^7.0.10
|
||||||
|
immer: ^9.0.6
|
||||||
|
multiformats: ^9.5.2
|
||||||
|
single-call-balance-checker-abi: ^1.0.0
|
||||||
|
uuid: ^8.3.2
|
||||||
|
peerDependencies:
|
||||||
|
"@metamask/approval-controller": ^2.1.1
|
||||||
|
"@metamask/network-controller": ^8.0.0
|
||||||
|
checksum: 150461535d47ac8079f726a4b8b6c130043757d87bc715ae480ac10262613e3f06f930882fec0150717856ff9dfc8c1df3e6ff96a474dec0fe850aa70b2c51a8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@metamask/auto-changelog@npm:^2.1.0":
|
"@metamask/auto-changelog@npm:^2.1.0":
|
||||||
version: 2.6.1
|
version: 2.6.1
|
||||||
resolution: "@metamask/auto-changelog@npm:2.6.1"
|
resolution: "@metamask/auto-changelog@npm:2.6.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user