mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Extend wallet_watchAsset
to support ERC721
and ERC1155
tokens (#19454)
* Extend wallet_watchAsset to support ERC721 and ERC1155 tokens
This commit is contained in:
parent
8b3e3c8a58
commit
c16b35c029
@ -1,77 +1,121 @@
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
|
||||
const suggestedAssets = [
|
||||
{
|
||||
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
image: './images/eth_logo.png',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
|
||||
symbol: '0X',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
|
||||
symbol: 'AST',
|
||||
decimals: 18,
|
||||
image: 'ast.png',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
|
||||
symbol: 'BAT',
|
||||
decimals: 18,
|
||||
image: 'BAT_icon.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1',
|
||||
symbol: 'CVL',
|
||||
decimals: 18,
|
||||
image: 'CVL_token.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
|
||||
symbol: 'GLA',
|
||||
decimals: 18,
|
||||
image: 'gladius.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F',
|
||||
symbol: 'GNO',
|
||||
decimals: 18,
|
||||
image: 'gnosis.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2',
|
||||
symbol: 'OMG',
|
||||
decimals: 18,
|
||||
image: 'omg.jpg',
|
||||
unlisted: false,
|
||||
},
|
||||
{
|
||||
address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1',
|
||||
symbol: 'WED',
|
||||
decimals: 18,
|
||||
image: 'wed.png',
|
||||
unlisted: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const pendingAssetApprovals = suggestedAssets.map((asset, index) => {
|
||||
return {
|
||||
export const pendingTokenApprovals = {
|
||||
1: {
|
||||
id: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
id: index,
|
||||
asset,
|
||||
asset: {
|
||||
address: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
image: './images/eth_logo.png',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
2: {
|
||||
id: 2,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52',
|
||||
symbol: '0X',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
3: {
|
||||
id: 3,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
|
||||
symbol: 'AST',
|
||||
decimals: 18,
|
||||
image: 'ast.png',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
4: {
|
||||
id: 4,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
|
||||
symbol: 'BAT',
|
||||
decimals: 18,
|
||||
image: 'BAT_icon.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
5: {
|
||||
id: 5,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1',
|
||||
symbol: 'CVL',
|
||||
decimals: 18,
|
||||
image: 'CVL_token.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
6: {
|
||||
id: 6,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
|
||||
symbol: 'GLA',
|
||||
decimals: 18,
|
||||
image: 'gladius.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
7: {
|
||||
id: 7,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F',
|
||||
symbol: 'GNO',
|
||||
decimals: 18,
|
||||
image: 'gnosis.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
8: {
|
||||
id: 8,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2',
|
||||
symbol: 'OMG',
|
||||
decimals: 18,
|
||||
image: 'omg.jpg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
9: {
|
||||
id: 9,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1',
|
||||
symbol: 'WED',
|
||||
decimals: 18,
|
||||
image: 'wed.png',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
13
app/_locales/en/messages.json
generated
13
app/_locales/en/messages.json
generated
@ -265,6 +265,15 @@
|
||||
"addNewToken": {
|
||||
"message": "Add new token"
|
||||
},
|
||||
"addNft": {
|
||||
"message": "Add NFT"
|
||||
},
|
||||
"addNfts": {
|
||||
"message": "Add NFTs"
|
||||
},
|
||||
"addSuggestedNFTs": {
|
||||
"message": "Add suggested NFTs"
|
||||
},
|
||||
"addSuggestedTokens": {
|
||||
"message": "Add suggested tokens"
|
||||
},
|
||||
@ -5108,6 +5117,10 @@
|
||||
"wantToAddThisNetwork": {
|
||||
"message": "Want to add this network?"
|
||||
},
|
||||
"wantsToAddThisAsset": {
|
||||
"message": "$1 wants to add this asset to your wallet",
|
||||
"description": "$1 is the name of the website that wants to add an asset to your wallet"
|
||||
},
|
||||
"warning": {
|
||||
"message": "Warning"
|
||||
},
|
||||
|
@ -35,8 +35,11 @@ async function watchAssetHandler(
|
||||
{ handleWatchAssetRequest },
|
||||
) {
|
||||
try {
|
||||
const { options: asset, type } = req.params;
|
||||
await handleWatchAssetRequest(asset, type);
|
||||
const {
|
||||
params: { options: asset, type },
|
||||
origin,
|
||||
} = req;
|
||||
await handleWatchAssetRequest(asset, type, origin);
|
||||
res.result = true;
|
||||
return end();
|
||||
} catch (error) {
|
||||
|
@ -80,7 +80,12 @@ import { SignatureController } from '@metamask/signature-controller';
|
||||
import { DesktopController } from '@metamask/desktop/dist/controllers/desktop';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import {
|
||||
ApprovalType,
|
||||
ERC1155,
|
||||
ERC20,
|
||||
ERC721,
|
||||
} from '@metamask/controller-utils';
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||
@ -401,7 +406,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const nftControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'NftController',
|
||||
allowedActions: ['ApprovalController:addRequest'],
|
||||
allowedActions: [`${this.approvalController.name}:addRequest`],
|
||||
});
|
||||
this.nftController = new NftController(
|
||||
{
|
||||
@ -3449,6 +3454,18 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
handleWatchAssetRequest = (asset, type, origin) => {
|
||||
switch (type) {
|
||||
case ERC20:
|
||||
return this.tokensController.watchAsset(asset, type);
|
||||
case ERC721:
|
||||
case ERC1155:
|
||||
return this.nftController.watchNft(asset, type, origin);
|
||||
default:
|
||||
throw new Error(`Asset type ${type} not supported`);
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// PASSWORD MANAGEMENT
|
||||
//=============================================================================
|
||||
@ -3828,9 +3845,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
|
||||
this.appStateController,
|
||||
),
|
||||
handleWatchAssetRequest: this.tokensController.watchAsset.bind(
|
||||
this.tokensController,
|
||||
),
|
||||
handleWatchAssetRequest: this.handleWatchAssetRequest.bind(this),
|
||||
requestUserApproval:
|
||||
this.approvalController.addAndShowApprovalRequest.bind(
|
||||
this.approvalController,
|
||||
|
@ -386,7 +386,7 @@
|
||||
"@metamask/eslint-config-typescript": "^9.0.1",
|
||||
"@metamask/forwarder": "^1.1.0",
|
||||
"@metamask/phishing-warning": "^2.1.0",
|
||||
"@metamask/test-dapp": "^6.0.0",
|
||||
"@metamask/test-dapp": "^7.0.0",
|
||||
"@sentry/cli": "^1.58.0",
|
||||
"@storybook/addon-a11y": "^7.0.11",
|
||||
"@storybook/addon-actions": "^7.0.11",
|
||||
|
@ -561,7 +561,7 @@ class FixtureBuilder {
|
||||
[toHex(1337)]: [
|
||||
{
|
||||
address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.NFTS}`,
|
||||
name: 'TestDappCollectibles',
|
||||
name: 'TestDappNFTs',
|
||||
symbol: 'TDC',
|
||||
},
|
||||
],
|
||||
@ -572,12 +572,12 @@ class FixtureBuilder {
|
||||
[toHex(1337)]: [
|
||||
{
|
||||
address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.NFTS}`,
|
||||
description: 'Test Dapp Collectibles for testing.',
|
||||
description: 'Test Dapp NFTs for testing.',
|
||||
favorite: false,
|
||||
image:
|
||||
'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM1MCIgd2lkdGg9IjM1MCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdGggaWQ9Ik15UGF0aCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJyZWQiIGQ9Ik0xMCw5MCBROTAsOTAgOTAsNDUgUTkwLDEwIDUwLDEwIFExMCwxMCAxMCw0MCBRMTAsNzAgNDUsNzAgUTcwLDcwIDc1LDUwIiAvPjwvZGVmcz48dGV4dD48dGV4dFBhdGggaHJlZj0iI015UGF0aCI+UXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLjwvdGV4dFBhdGg+PC90ZXh0Pjwvc3ZnPg==',
|
||||
isCurrentlyOwned: true,
|
||||
name: 'Test Dapp Collectibles #1',
|
||||
name: 'Test Dapp NFTs #1',
|
||||
standard: 'ERC721',
|
||||
tokenId: '1',
|
||||
},
|
||||
|
@ -15,6 +15,214 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
],
|
||||
};
|
||||
|
||||
it('should prompt users to add their NFTs to their wallet (one by one)', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
dapp: true,
|
||||
fixtures: new FixtureBuilder()
|
||||
.withPermissionControllerConnectedToTestDapp()
|
||||
.build(),
|
||||
ganacheOptions,
|
||||
smartContract,
|
||||
title: this.test.title,
|
||||
failOnConsoleError: false,
|
||||
},
|
||||
async ({ driver, _, contractRegistry }) => {
|
||||
const contract = contractRegistry.getContractAddress(smartContract);
|
||||
await driver.navigate();
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
// Open Dapp and wait for deployed contract
|
||||
await openDapp(driver, contract);
|
||||
await driver.findClickableElement('#deployButton');
|
||||
|
||||
// mint NFT
|
||||
await driver.fill('#mintAmountInput', '5');
|
||||
await driver.clickElement({ text: 'Mint', tag: 'button' });
|
||||
|
||||
// Notification
|
||||
await driver.waitUntilXWindowHandles(3);
|
||||
let windowHandles = await driver.getAllWindowHandles();
|
||||
const [extension] = windowHandles;
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
await driver.waitForSelector({
|
||||
css: '.confirm-page-container-summary__action__name',
|
||||
text: 'Deposit',
|
||||
});
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
await driver.waitUntilXWindowHandles(2);
|
||||
await driver.switchToWindow(extension);
|
||||
await driver.clickElement('[data-testid="home__activity-tab"]');
|
||||
const transactionItem = await driver.waitForSelector({
|
||||
css: '.list-item__title',
|
||||
text: 'Deposit',
|
||||
});
|
||||
assert.equal(await transactionItem.isDisplayed(), true);
|
||||
|
||||
// verify the mint transaction has finished
|
||||
await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles);
|
||||
const nftsMintStatus = await driver.findElement({
|
||||
css: '#nftsStatus',
|
||||
text: 'Mint completed',
|
||||
});
|
||||
assert.equal(await nftsMintStatus.isDisplayed(), true);
|
||||
|
||||
// watch 3 of the nfts
|
||||
await driver.clickElement({ text: 'Watch NFT 1', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Watch NFT 2', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Watch NFT 3', tag: 'button' });
|
||||
|
||||
await driver.waitUntilXWindowHandles(3);
|
||||
windowHandles = await driver.getAllWindowHandles();
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
|
||||
// confirm watchNFT
|
||||
await driver.waitForSelector({
|
||||
css: '.mm-text--heading-lg',
|
||||
text: 'Add suggested NFTs',
|
||||
});
|
||||
await driver.clickElement({ text: 'Add NFTs', tag: 'button' });
|
||||
await driver.switchToWindow(extension);
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (3)' });
|
||||
const nftsListItemsFirstCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
);
|
||||
assert.equal(nftsListItemsFirstCheck.length, 3);
|
||||
|
||||
await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles);
|
||||
await driver.clickElement({ text: 'Watch NFT 4', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Watch NFT 5', tag: 'button' });
|
||||
await driver.clickElement({ text: 'Watch NFT 6', tag: 'button' });
|
||||
|
||||
await driver.waitUntilXWindowHandles(3);
|
||||
windowHandles = await driver.getAllWindowHandles();
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
|
||||
// confirm watchNFT
|
||||
await driver.waitForSelector({
|
||||
css: '.mm-text--heading-lg',
|
||||
text: 'Add suggested NFTs',
|
||||
});
|
||||
await driver.clickElement({ text: 'Add NFTs', tag: 'button' });
|
||||
await driver.switchToWindow(extension);
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (6)' });
|
||||
const nftsListItemsSecondCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
);
|
||||
assert.equal(nftsListItemsSecondCheck.length, 6);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should prompt users to add their NFTs to their wallet (all at once)', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
dapp: true,
|
||||
fixtures: new FixtureBuilder()
|
||||
.withPermissionControllerConnectedToTestDapp()
|
||||
.build(),
|
||||
ganacheOptions,
|
||||
smartContract,
|
||||
title: this.test.title,
|
||||
failOnConsoleError: false,
|
||||
},
|
||||
async ({ driver, _, contractRegistry }) => {
|
||||
const contract = contractRegistry.getContractAddress(smartContract);
|
||||
await driver.navigate();
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
// Open Dapp and wait for deployed contract
|
||||
await openDapp(driver, contract);
|
||||
await driver.findClickableElement('#deployButton');
|
||||
|
||||
// mint NFT
|
||||
await driver.fill('#mintAmountInput', '5');
|
||||
await driver.clickElement({ text: 'Mint', tag: 'button' });
|
||||
|
||||
// Notification
|
||||
await driver.waitUntilXWindowHandles(3);
|
||||
let windowHandles = await driver.getAllWindowHandles();
|
||||
const [extension] = windowHandles;
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
await driver.waitForSelector({
|
||||
css: '.confirm-page-container-summary__action__name',
|
||||
text: 'Deposit',
|
||||
});
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
await driver.waitUntilXWindowHandles(2);
|
||||
await driver.switchToWindow(extension);
|
||||
await driver.clickElement('[data-testid="home__activity-tab"]');
|
||||
const transactionItem = await driver.waitForSelector({
|
||||
css: '.list-item__title',
|
||||
text: 'Deposit',
|
||||
});
|
||||
assert.equal(await transactionItem.isDisplayed(), true);
|
||||
// verify the mint transaction has finished
|
||||
await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles);
|
||||
const nftsMintStatus = await driver.findElement({
|
||||
css: '#nftsStatus',
|
||||
text: 'Mint completed',
|
||||
});
|
||||
assert.equal(await nftsMintStatus.isDisplayed(), true);
|
||||
|
||||
// watch all nfts
|
||||
await driver.clickElement({ text: 'Watch all NFTs', tag: 'button' });
|
||||
|
||||
await driver.waitUntilXWindowHandles(3);
|
||||
windowHandles = await driver.getAllWindowHandles();
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
|
||||
// confirm watchNFT
|
||||
await driver.waitForSelector({
|
||||
css: '.mm-text--heading-lg',
|
||||
text: 'Add suggested NFTs',
|
||||
});
|
||||
|
||||
await driver.findElements('.confirm-add-suggested-nft__nft-list-item');
|
||||
const suggestedNftListItems = await driver.findElements(
|
||||
'.confirm-add-suggested-nft__nft-list-item',
|
||||
);
|
||||
// there are 6 nfts to add because one is minted as part of the fixture
|
||||
assert.equal(suggestedNftListItems.length, 6);
|
||||
|
||||
// remove one nft from the list
|
||||
const removeButtons = await driver.findElements(
|
||||
'.confirm-add-suggested-nft__nft-remove',
|
||||
);
|
||||
await removeButtons[0].click();
|
||||
|
||||
await driver.clickElement({ text: 'Add NFTs', tag: 'button' });
|
||||
await driver.switchToWindow(extension);
|
||||
await driver.clickElement({ text: 'NFTs', tag: 'button' });
|
||||
await driver.findElement({ text: 'TestDappNFTs (5)' });
|
||||
const nftsListItemsSecondCheck = await driver.findElements(
|
||||
'.nft-item__item',
|
||||
);
|
||||
|
||||
assert.equal(nftsListItemsSecondCheck.length, 5);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer a single ERC721 NFT from one account to another', async function () {
|
||||
await withFixtures(
|
||||
{
|
||||
@ -51,7 +259,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
// Confirm transfer
|
||||
await driver.waitForSelector({
|
||||
css: '.mm-text--heading-md',
|
||||
text: 'TestDappCollectibles',
|
||||
text: 'TestDappNFTs',
|
||||
});
|
||||
await driver.clickElement({ text: 'Confirm', tag: 'button' });
|
||||
await driver.waitUntilXWindowHandles(2);
|
||||
@ -62,7 +270,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
);
|
||||
|
||||
// Verify transaction
|
||||
await driver.findElement({ text: 'Send TDC' });
|
||||
await driver.findElement({ text: 'Send TDN' });
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -116,7 +324,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
);
|
||||
assert.equal(
|
||||
await title.getText(),
|
||||
'Allow access to and transfer of your TestDappCollectibles (#1)?',
|
||||
'Allow access to and transfer of your TestDappNFTs (#1)?',
|
||||
);
|
||||
assert.equal(await func.getText(), 'Function: Approve');
|
||||
|
||||
@ -132,7 +340,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
// Verify transaction
|
||||
const completedTx = await driver.waitForSelector({
|
||||
css: '.list-item__title',
|
||||
text: 'Approve TDC spending cap',
|
||||
text: 'Approve TDN spending cap',
|
||||
});
|
||||
assert.equal(await completedTx.isDisplayed(), true);
|
||||
},
|
||||
@ -184,7 +392,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
);
|
||||
assert.equal(
|
||||
await title.getText(),
|
||||
'Allow access to and transfer of all your TestDappCollectibles?',
|
||||
'Allow access to and transfer of all your TestDappNFTs?',
|
||||
);
|
||||
assert.equal(await func.getText(), 'Function: SetApprovalForAll');
|
||||
assert.equal(await params.getText(), 'Parameters: true');
|
||||
@ -203,7 +411,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
// Verify transaction
|
||||
const completedTx = await driver.waitForSelector({
|
||||
css: '.list-item__title',
|
||||
text: 'Approve TDC with no spend limit',
|
||||
text: 'Approve TDN with no spend limit',
|
||||
});
|
||||
assert.equal(await completedTx.isDisplayed(), true);
|
||||
},
|
||||
@ -258,7 +466,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
);
|
||||
assert.equal(
|
||||
await title.getText(),
|
||||
'Revoke permission to access and transfer all of your TestDappCollectibles?',
|
||||
'Revoke permission to access and transfer all of your TestDappNFTs?',
|
||||
);
|
||||
assert.equal(await func.getText(), 'Function: SetApprovalForAll');
|
||||
assert.equal(await params.getText(), 'Parameters: false');
|
||||
@ -277,7 +485,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
// Verify transaction
|
||||
const completedTx = await driver.waitForSelector({
|
||||
css: '.list-item__title',
|
||||
text: 'Approve TDC with no spend limit',
|
||||
text: 'Approve TDN with no spend limit',
|
||||
});
|
||||
assert.equal(await completedTx.isDisplayed(), true);
|
||||
},
|
||||
|
@ -51,7 +51,7 @@ describe('Import NFT', function () {
|
||||
// Check the imported NFT and its image are displayed in the NFT tab
|
||||
const importedNft = await driver.waitForSelector({
|
||||
css: 'h5',
|
||||
text: 'TestDappCollectibles',
|
||||
text: 'TestDappNFTs',
|
||||
});
|
||||
const importedNftImage = await driver.findElement(
|
||||
'.nft-item__item-image',
|
||||
|
@ -71,7 +71,7 @@ describe('Send NFT', function () {
|
||||
|
||||
const sendNftItem = await driver.findElement({
|
||||
css: 'h2',
|
||||
text: 'Send Test Dapp Collectibles',
|
||||
text: 'Send Test Dapp NFTs',
|
||||
});
|
||||
assert.equal(await sendNftItem.isDisplayed(), true);
|
||||
|
||||
|
@ -38,19 +38,19 @@ describe('View NFT details', function () {
|
||||
const detailsPageTitle = await driver.findElement('.asset-breadcrumb');
|
||||
assert.equal(
|
||||
await detailsPageTitle.getText(),
|
||||
'Account 1 / TestDappCollectibles',
|
||||
'Account 1 / TestDappNFTs',
|
||||
);
|
||||
|
||||
// Check the displayed NFT details
|
||||
const nftName = await driver.findElement('.nft-details__info h4');
|
||||
assert.equal(await nftName.getText(), 'Test Dapp Collectibles #1');
|
||||
assert.equal(await nftName.getText(), 'Test Dapp NFTs #1');
|
||||
|
||||
const nftDescription = await driver.findElement(
|
||||
'.nft-details__info h6:nth-of-type(2)',
|
||||
);
|
||||
assert.equal(
|
||||
await nftDescription.getText(),
|
||||
'Test Dapp Collectibles for testing.',
|
||||
'Test Dapp NFTs for testing.',
|
||||
);
|
||||
|
||||
const nftImage = await driver.findElement('.nft-item__item-image');
|
||||
|
@ -45,7 +45,7 @@ class GanacheSeeder {
|
||||
await contract.deployTransaction.wait();
|
||||
|
||||
if (contractName === SMART_CONTRACTS.NFTS) {
|
||||
const transaction = await contract.mintCollectibles(1, {
|
||||
const transaction = await contract.mintNFTs(1, {
|
||||
from: fromAddress,
|
||||
});
|
||||
await transaction.wait();
|
||||
|
@ -3,8 +3,8 @@ const {
|
||||
hstAbi,
|
||||
piggybankBytecode,
|
||||
piggybankAbi,
|
||||
collectiblesAbi,
|
||||
collectiblesBytecode,
|
||||
nftsAbi,
|
||||
nftsBytecode,
|
||||
erc1155Abi,
|
||||
erc1155Bytecode,
|
||||
failingContractAbi,
|
||||
@ -23,8 +23,8 @@ const hstFactory = {
|
||||
};
|
||||
|
||||
const nftsFactory = {
|
||||
bytecode: collectiblesBytecode,
|
||||
abi: collectiblesAbi,
|
||||
bytecode: nftsBytecode,
|
||||
abi: nftsAbi,
|
||||
};
|
||||
|
||||
const erc1155Factory = {
|
||||
|
@ -15,13 +15,18 @@ import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { Text } from '../../component-library';
|
||||
import Box from '../../ui/box/box';
|
||||
|
||||
export default function NftDefaultImage({ name, tokenId, clickable = false }) {
|
||||
export default function NftDefaultImage({
|
||||
name,
|
||||
tokenId,
|
||||
className,
|
||||
clickable = false,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
return (
|
||||
<Box
|
||||
tabIndex={0}
|
||||
data-testid="nft-default-image"
|
||||
className={classnames('nft-default', {
|
||||
className={classnames(className, 'nft-default', {
|
||||
'nft-default--clickable': clickable,
|
||||
})}
|
||||
display={Display.Flex}
|
||||
@ -57,4 +62,8 @@ NftDefaultImage.propTypes = {
|
||||
* Controls the css class for the cursor hover
|
||||
*/
|
||||
clickable: PropTypes.bool,
|
||||
/**
|
||||
* An additional className to apply to the NFT default image
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ const IMPORT_TOKEN_ROUTE = '/import-token';
|
||||
const CONFIRM_IMPORT_TOKEN_ROUTE = '/confirm-import-token';
|
||||
const CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE = '/confirm-add-suggested-token';
|
||||
const NEW_ACCOUNT_ROUTE = '/new-account';
|
||||
const CONFIRM_ADD_SUGGESTED_NFT_ROUTE = '/confirm-add-suggested-nft';
|
||||
const CONNECT_HARDWARE_ROUTE = '/new-account/connect';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
const CUSTODY_ACCOUNT_ROUTE = '/new-account/custody';
|
||||
@ -131,6 +132,7 @@ const PATH_NAME_MAP = {
|
||||
[CONFIRM_IMPORT_TOKEN_ROUTE]: 'Confirm Import Token Page',
|
||||
[CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE]: 'Confirm Add Suggested Token Page',
|
||||
[NEW_ACCOUNT_ROUTE]: 'New Account Page',
|
||||
[CONFIRM_ADD_SUGGESTED_NFT_ROUTE]: 'Confirm Add Suggested NFT Page',
|
||||
[CONNECT_HARDWARE_ROUTE]: 'Connect Hardware Wallet Page',
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
[INSTITUTIONAL_FEATURES_DONE_ROUTE]: 'Institutional Features Done Page',
|
||||
@ -197,6 +199,7 @@ export {
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_NFT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
SEND_ROUTE,
|
||||
TOKEN_DETAILS,
|
||||
|
@ -492,11 +492,11 @@ export const sanitizeMessage = (msg, primaryType, types) => {
|
||||
};
|
||||
|
||||
export function getAssetImageURL(image, ipfsGateway) {
|
||||
if (!image || !ipfsGateway || typeof image !== 'string') {
|
||||
if (!image || typeof image !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (image.startsWith('ipfs://')) {
|
||||
if (ipfsGateway && image.startsWith('ipfs://')) {
|
||||
return getFormattedIpfsUrl(ipfsGateway, image, true);
|
||||
}
|
||||
return image;
|
||||
|
343
ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js
Normal file
343
ui/pages/confirm-add-suggested-nft/confirm-add-suggested-nft.js
Normal file
@ -0,0 +1,343 @@
|
||||
import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { ethErrors, serializeError } from 'eth-rpc-errors';
|
||||
import { getTokenTrackerLink } from '@metamask/etherscan-link';
|
||||
import classnames from 'classnames';
|
||||
import { PageContainerFooter } from '../../components/ui/page-container';
|
||||
import { I18nContext } from '../../contexts/i18n';
|
||||
import { MetaMetricsContext } from '../../contexts/metametrics';
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import {
|
||||
resolvePendingApproval,
|
||||
rejectPendingApproval,
|
||||
} from '../../store/actions';
|
||||
import {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
MetaMetricsTokenEventSource,
|
||||
} from '../../../shared/constants/metametrics';
|
||||
import { AssetType } from '../../../shared/constants/transaction';
|
||||
import {
|
||||
BUTTON_SIZES,
|
||||
ButtonIcon,
|
||||
ButtonIconSize,
|
||||
ButtonLink,
|
||||
IconName,
|
||||
Text,
|
||||
Box,
|
||||
} from '../../components/component-library';
|
||||
import {
|
||||
getCurrentChainId,
|
||||
getRpcPrefsForCurrentProvider,
|
||||
getSuggestedNfts,
|
||||
getIpfsGateway,
|
||||
} from '../../selectors';
|
||||
import NftDefaultImage from '../../components/app/nft-default-image/nft-default-image';
|
||||
import { getAssetImageURL, shortenAddress } from '../../helpers/utils/util';
|
||||
import {
|
||||
AlignItems,
|
||||
BorderRadius,
|
||||
Display,
|
||||
FlexDirection,
|
||||
FlexWrap,
|
||||
IconColor,
|
||||
JustifyContent,
|
||||
TextAlign,
|
||||
TextVariant,
|
||||
BlockSize,
|
||||
} from '../../helpers/constants/design-system';
|
||||
|
||||
const ConfirmAddSuggestedNFT = () => {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||
const suggestedNfts = useSelector(getSuggestedNfts);
|
||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const ipfsGateway = useSelector(getIpfsGateway);
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const handleAddNftsClick = useCallback(async () => {
|
||||
await Promise.all(
|
||||
suggestedNfts.map(async ({ requestData: { asset }, id }) => {
|
||||
await dispatch(resolvePendingApproval(id, null));
|
||||
|
||||
trackEvent({
|
||||
event: MetaMetricsEventName.NftAdded,
|
||||
category: MetaMetricsEventCategory.Wallet,
|
||||
sensitiveProperties: {
|
||||
token_symbol: asset.symbol,
|
||||
token_id: asset.tokenId,
|
||||
token_contract_address: asset.address,
|
||||
source_connection_method: MetaMetricsTokenEventSource.Dapp,
|
||||
token_standard: asset.standard,
|
||||
asset_type: AssetType.NFT,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
history.push(mostRecentOverviewPage);
|
||||
}, [dispatch, history, trackEvent, mostRecentOverviewPage, suggestedNfts]);
|
||||
|
||||
const handleCancelNftClick = useCallback(async () => {
|
||||
await Promise.all(
|
||||
suggestedNfts.map(async ({ id }) => {
|
||||
return dispatch(
|
||||
rejectPendingApproval(
|
||||
id,
|
||||
serializeError(ethErrors.provider.userRejectedRequest()),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
history.push(mostRecentOverviewPage);
|
||||
}, [dispatch, history, mostRecentOverviewPage, suggestedNfts]);
|
||||
|
||||
useEffect(() => {
|
||||
const goBackIfNoSuggestedNftsOnFirstRender = () => {
|
||||
if (!suggestedNfts.length) {
|
||||
history.push(mostRecentOverviewPage);
|
||||
}
|
||||
};
|
||||
goBackIfNoSuggestedNftsOnFirstRender();
|
||||
}, [history, mostRecentOverviewPage, suggestedNfts]);
|
||||
|
||||
let origin;
|
||||
if (suggestedNfts.length) {
|
||||
try {
|
||||
origin = new URL(suggestedNfts[0].origin)?.host;
|
||||
} catch {
|
||||
origin = 'dapp';
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
height={BlockSize.Full}
|
||||
width={BlockSize.Full}
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Column}
|
||||
>
|
||||
<Box paddingBottom={2} className="confirm-add-suggested-nft__header">
|
||||
<Text
|
||||
variant={TextVariant.headingLg}
|
||||
textAlign={TextAlign.Center}
|
||||
margin={2}
|
||||
>
|
||||
{t('addSuggestedNFTs')}
|
||||
</Text>
|
||||
<Text variant={TextVariant.bodyMd} textAlign={TextAlign.Center}>
|
||||
{t('wantsToAddThisAsset', [
|
||||
origin === 'dapp' ? (
|
||||
<Text key={origin} variant={TextVariant.bodyMd} fontWeight="bold">
|
||||
{origin}
|
||||
</Text>
|
||||
) : (
|
||||
<ButtonLink
|
||||
key={origin}
|
||||
size={BUTTON_SIZES.INHERIT}
|
||||
href={origin}
|
||||
target="_blank"
|
||||
>
|
||||
{origin}
|
||||
</ButtonLink>
|
||||
),
|
||||
])}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box className="confirm-add-suggested-nft__content">
|
||||
<Box
|
||||
className="confirm-add-suggested-nft__card"
|
||||
padding={2}
|
||||
borderRadius={BorderRadius.MD}
|
||||
>
|
||||
<Box
|
||||
className={classnames({
|
||||
'confirm-add-suggested-nft__nft-list': suggestedNfts.length > 1,
|
||||
})}
|
||||
>
|
||||
{suggestedNfts.map(
|
||||
({
|
||||
id,
|
||||
requestData: {
|
||||
asset: { address, tokenId, symbol, image, name },
|
||||
},
|
||||
}) => {
|
||||
const nftImageURL = getAssetImageURL(image, ipfsGateway);
|
||||
const blockExplorerLink = getTokenTrackerLink(
|
||||
address,
|
||||
chainId,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
);
|
||||
|
||||
if (suggestedNfts.length === 1) {
|
||||
return (
|
||||
<Box
|
||||
className="confirm-add-suggested-nft__nft-single"
|
||||
borderRadius={BorderRadius.MD}
|
||||
margin={0}
|
||||
padding={0}
|
||||
>
|
||||
{nftImageURL ? (
|
||||
<img
|
||||
className="confirm-add-suggested-nft__nft-single-image"
|
||||
src={nftImageURL}
|
||||
alt={name || tokenId}
|
||||
/>
|
||||
) : (
|
||||
<NftDefaultImage
|
||||
className="confirm-add-suggested-nft__nft-single-image-default"
|
||||
tokenId={tokenId}
|
||||
name={name || symbol || shortenAddress(address)}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
padding={1}
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Row}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
alignItems={AlignItems.Center}
|
||||
>
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Column}
|
||||
justifyContent={JustifyContent.spaceEvenly}
|
||||
flexWrap={FlexWrap.NoWrap}
|
||||
width={BlockSize.Full}
|
||||
className="confirm-add-suggested-nft__nft-single-sub-details"
|
||||
>
|
||||
{rpcPrefs.blockExplorerUrl ? (
|
||||
<ButtonLink
|
||||
className="confirm-add-suggested-nft__nft-name"
|
||||
href={blockExplorerLink}
|
||||
title={address}
|
||||
target="_blank"
|
||||
size={BUTTON_SIZES.INHERIT}
|
||||
>
|
||||
{name || symbol || shortenAddress(address)}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
<Text
|
||||
variant={TextVariant.bodyMd}
|
||||
className="confirm-add-suggested-nft__nft-name"
|
||||
title={address}
|
||||
>
|
||||
{name || symbol || shortenAddress(address)}
|
||||
</Text>
|
||||
)}
|
||||
<Text
|
||||
variant={TextVariant.bodyMd}
|
||||
className="confirm-add-suggested-nft__nft-tokenId"
|
||||
>
|
||||
#{tokenId}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Row}
|
||||
flexWrap={FlexWrap.NoWrap}
|
||||
alignItems={AlignItems.Center}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
marginBottom={4}
|
||||
className="confirm-add-suggested-nft__nft-list-item"
|
||||
key={`${address}-${tokenId}`}
|
||||
>
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Row}
|
||||
flexWrap={FlexWrap.NoWrap}
|
||||
alignItems={AlignItems.Center}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
{nftImageURL ? (
|
||||
<img
|
||||
className="confirm-add-suggested-nft__nft-image"
|
||||
src={nftImageURL}
|
||||
alt={name || tokenId}
|
||||
/>
|
||||
) : (
|
||||
<NftDefaultImage className="confirm-add-suggested-nft__nft-image-default" />
|
||||
)}
|
||||
<Box
|
||||
display={Display.Flex}
|
||||
flexDirection={FlexDirection.Column}
|
||||
justifyContent={JustifyContent.spaceEvenly}
|
||||
flexWrap={FlexWrap.NoWrap}
|
||||
width={BlockSize.Full}
|
||||
className="confirm-add-suggested-nft__nft-sub-details"
|
||||
>
|
||||
{rpcPrefs.blockExplorerUrl ? (
|
||||
<ButtonLink
|
||||
className="confirm-add-suggested-nft__nft-name"
|
||||
href={blockExplorerLink}
|
||||
title={address}
|
||||
target="_blank"
|
||||
size={BUTTON_SIZES.INHERIT}
|
||||
>
|
||||
{name || symbol || shortenAddress(address)}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
className="confirm-add-suggested-nft__nft-name"
|
||||
title={address}
|
||||
>
|
||||
{name || symbol || shortenAddress(address)}
|
||||
</Text>
|
||||
)}
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
className="confirm-add-suggested-nft__nft-tokenId"
|
||||
>
|
||||
#{tokenId}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonIcon
|
||||
className="confirm-add-suggested-nft__nft-remove"
|
||||
data-testid={`confirm-add-suggested-nft__nft-remove-${id}`}
|
||||
iconName={IconName.Close}
|
||||
size={ButtonIconSize.Sm}
|
||||
color={IconColor.iconMuted}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dispatch(
|
||||
rejectPendingApproval(
|
||||
id,
|
||||
serializeError(
|
||||
ethErrors.provider.userRejectedRequest(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<PageContainerFooter
|
||||
cancelText={t('cancel')}
|
||||
submitText={suggestedNfts.length === 1 ? t('addNft') : t('addNfts')}
|
||||
onCancel={handleCancelNftClick}
|
||||
onSubmit={handleAddNftsClick}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmAddSuggestedNFT;
|
@ -0,0 +1,71 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
|
||||
import configureStore from '../../store/store';
|
||||
|
||||
import mockState from '../../../.storybook/test-data';
|
||||
|
||||
import ConfirmAddSuggestedNFT from '.';
|
||||
|
||||
const pendingNftApprovals = {
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'https://www.opensea.io',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xb7F7F6C52F2e2fdb1963Eab30438024864c313F6',
|
||||
name: 'Wrapped CryptoPunks',
|
||||
tokenId: '1848',
|
||||
standard: 'ERC721',
|
||||
image: 'https://images.wrappedpunks.com/images/punks/1848.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
origin: 'https://www.nft-collector.io',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
name: 'Legends of the Dance Floor',
|
||||
tokenId: '1',
|
||||
standard: 'ERC721',
|
||||
image: 'https://www.miladymaker.net/milady/736.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
pendingApprovals: {
|
||||
1: Object.values(pendingNftApprovals)[0],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
title: 'Pages/ConfirmAddSuggestedNFT',
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
};
|
||||
|
||||
export const DefaultStory = () => <ConfirmAddSuggestedNFT />;
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
export const WithMultipleSuggestedNFTs = () => <ConfirmAddSuggestedNFT />;
|
||||
const WithDuplicateAddressStore = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
pendingApprovals: pendingNftApprovals,
|
||||
},
|
||||
});
|
||||
WithMultipleSuggestedNFTs.decorators = [
|
||||
(story) => <Provider store={WithDuplicateAddressStore}>{story()}</Provider>,
|
||||
];
|
@ -0,0 +1,191 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import {
|
||||
resolvePendingApproval,
|
||||
rejectPendingApproval,
|
||||
} from '../../store/actions';
|
||||
import configureStore from '../../store/store';
|
||||
import { renderWithProvider } from '../../../test/jest/rendering';
|
||||
import ConfirmAddSuggestedNFT from '.';
|
||||
|
||||
const PENDING_NFT_APPROVALS = {
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'https://www.opensea.io',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
name: 'CryptoKitty',
|
||||
tokenId: '15',
|
||||
standard: 'ERC721',
|
||||
image: 'https://www.cryptokitties.com/images/kitty-eth.svg',
|
||||
},
|
||||
},
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
origin: 'https://www.nft-collector.io',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
name: 'Legends of the Dance Floor',
|
||||
tokenId: '1',
|
||||
standard: 'ERC721',
|
||||
image:
|
||||
'https://www.nft-collector.io/images/legends-of-the-dance-floor.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const PENDING_TOKEN_APPROVALS = {
|
||||
3: {
|
||||
id: '3',
|
||||
origin: 'https://www.uniswap.io',
|
||||
time: 2,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
|
||||
symbol: 'UNI',
|
||||
decimals: '18',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../../store/actions', () => ({
|
||||
resolvePendingApproval: jest.fn().mockReturnValue({ type: 'test' }),
|
||||
rejectPendingApproval: jest.fn().mockReturnValue({ type: 'test' }),
|
||||
}));
|
||||
|
||||
const renderComponent = (pendingNfts = {}) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
pendingApprovals: pendingNfts,
|
||||
providerConfig: { chainId: '0x1' },
|
||||
},
|
||||
history: {
|
||||
mostRecentOverviewPage: '/',
|
||||
},
|
||||
});
|
||||
return renderWithProvider(<ConfirmAddSuggestedNFT />, store);
|
||||
};
|
||||
|
||||
describe('ConfirmAddSuggestedNFT Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render one suggested NFT', () => {
|
||||
renderComponent({
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'https://www.opensea.io',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
name: 'CryptoKitty',
|
||||
tokenId: '15',
|
||||
standard: 'ERC721',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByText('Add suggested NFTs')).toBeInTheDocument();
|
||||
expect(screen.getByText('www.opensea.io')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('wants to add this asset to your wallet'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('CryptoKitty')).toBeInTheDocument();
|
||||
expect(screen.getByText('#15')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Add NFT' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render a list of suggested NFTs', () => {
|
||||
renderComponent({ ...PENDING_NFT_APPROVALS, ...PENDING_TOKEN_APPROVALS });
|
||||
|
||||
for (const {
|
||||
requestData: { asset },
|
||||
} of Object.values(PENDING_NFT_APPROVALS)) {
|
||||
expect(screen.getByText(asset.name)).toBeInTheDocument();
|
||||
expect(screen.getByText(`#${asset.tokenId}`)).toBeInTheDocument();
|
||||
}
|
||||
expect(screen.getAllByRole('img')).toHaveLength(
|
||||
Object.values(PENDING_NFT_APPROVALS).length,
|
||||
);
|
||||
});
|
||||
|
||||
it('should dispatch resolvePendingApproval when clicking the "Add NFTs" button', async () => {
|
||||
renderComponent(PENDING_NFT_APPROVALS);
|
||||
const addNftButton = screen.getByRole('button', { name: 'Add NFTs' });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(addNftButton);
|
||||
});
|
||||
|
||||
expect(resolvePendingApproval).toHaveBeenCalledTimes(
|
||||
Object.values(PENDING_NFT_APPROVALS).length,
|
||||
);
|
||||
|
||||
Object.values(PENDING_NFT_APPROVALS).forEach(({ id }) => {
|
||||
expect(resolvePendingApproval).toHaveBeenCalledWith(id, null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch rejectPendingApproval when clicking the "Cancel" button', async () => {
|
||||
renderComponent(PENDING_NFT_APPROVALS);
|
||||
const cancelBtn = screen.getByRole('button', { name: 'Cancel' });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(cancelBtn);
|
||||
});
|
||||
|
||||
expect(rejectPendingApproval).toHaveBeenCalledTimes(
|
||||
Object.values(PENDING_NFT_APPROVALS).length,
|
||||
);
|
||||
|
||||
Object.values(PENDING_NFT_APPROVALS).forEach(({ id }) => {
|
||||
expect(rejectPendingApproval).toHaveBeenCalledWith(
|
||||
id,
|
||||
expect.objectContaining({
|
||||
code: 4001,
|
||||
message: 'User rejected the request.',
|
||||
stack: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow users to remove individual NFTs from the list of NFTs to add', async () => {
|
||||
renderComponent(PENDING_NFT_APPROVALS);
|
||||
|
||||
const idToRemove = Object.values(PENDING_NFT_APPROVALS)[0].id;
|
||||
const removeBtn = screen.getByTestId(
|
||||
`confirm-add-suggested-nft__nft-remove-${idToRemove}`,
|
||||
);
|
||||
await act(async () => {
|
||||
fireEvent.click(removeBtn);
|
||||
});
|
||||
|
||||
expect(rejectPendingApproval).toHaveBeenCalledTimes(1);
|
||||
expect(rejectPendingApproval).toHaveBeenCalledWith(
|
||||
idToRemove,
|
||||
expect.objectContaining({
|
||||
code: 4001,
|
||||
message: 'User rejected the request.',
|
||||
stack: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
1
ui/pages/confirm-add-suggested-nft/index.js
Normal file
1
ui/pages/confirm-add-suggested-nft/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './confirm-add-suggested-nft';
|
81
ui/pages/confirm-add-suggested-nft/index.scss
Normal file
81
ui/pages/confirm-add-suggested-nft/index.scss
Normal file
@ -0,0 +1,81 @@
|
||||
.confirm-add-suggested-nft {
|
||||
&__card {
|
||||
margin: 16px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&__header {
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
}
|
||||
|
||||
&__content {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__nft-list {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__nft-list-item {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__nft-image {
|
||||
margin-right: 12px;
|
||||
width: 48px;
|
||||
border-radius: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__nft-image-default {
|
||||
margin-right: 12px;
|
||||
flex: 0 0 auto;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
&__nft-sub-details {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__nft-name {
|
||||
@include H6;
|
||||
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
&__nft-tokenid {
|
||||
@include H7;
|
||||
|
||||
color: #606060;
|
||||
}
|
||||
|
||||
&__nft-remove-tooltip {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&__nft-single-image {
|
||||
border-radius: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__nft-single-image-default {
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
&__nft-single-sub-details {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import React, { useCallback, useContext, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
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 Button from '../../components/ui/button';
|
||||
import Identicon from '../../components/ui/identicon';
|
||||
@ -14,7 +13,6 @@ import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import { getTokens } from '../../ducks/metamask/metamask';
|
||||
import ZENDESK_URLS from '../../helpers/constants/zendesk-url';
|
||||
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
||||
import { getApprovalRequestsByType } from '../../selectors';
|
||||
import {
|
||||
resolvePendingApproval,
|
||||
rejectPendingApproval,
|
||||
@ -28,22 +26,23 @@ import {
|
||||
AssetType,
|
||||
TokenStandard,
|
||||
} from '../../../shared/constants/transaction';
|
||||
import { getSuggestedTokens } from '../../selectors';
|
||||
|
||||
function getTokenName(name, symbol) {
|
||||
return name === undefined ? symbol : `${name} (${symbol})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} suggestedAssets - an array of assets suggested to add to the user's wallet
|
||||
* @param {Array} suggestedTokens - an array of assets suggested to add to the user's wallet
|
||||
* via the RPC method `wallet_watchAsset`
|
||||
* @param {Array} tokens - the list of tokens currently tracked in state
|
||||
* @returns {boolean} Returns true when the list of suggestedAssets contains an entry with
|
||||
* @returns {boolean} Returns true when the list of suggestedTokens contains an entry with
|
||||
* an address that matches an existing token.
|
||||
*/
|
||||
function hasDuplicateAddress(suggestedAssets, tokens) {
|
||||
const duplicate = suggestedAssets.find(({ asset }) => {
|
||||
function hasDuplicateAddress(suggestedTokens, tokens) {
|
||||
const duplicate = suggestedTokens.find(({ requestData: { asset } }) => {
|
||||
const dupe = tokens.find(({ address }) => {
|
||||
return isEqualCaseInsensitive(address, asset.address);
|
||||
return isEqualCaseInsensitive(address, asset?.address);
|
||||
});
|
||||
return Boolean(dupe);
|
||||
});
|
||||
@ -51,19 +50,19 @@ function hasDuplicateAddress(suggestedAssets, tokens) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} suggestedAssets - a list of assets suggested to add to the user's wallet
|
||||
* @param {Array} suggestedTokens - a list of assets suggested to add to the user's wallet
|
||||
* via RPC method `wallet_watchAsset`
|
||||
* @param {Array} tokens - the list of tokens currently tracked in state
|
||||
* @returns {boolean} Returns true when the list of suggestedAssets contains an entry with both
|
||||
* @returns {boolean} Returns true when the list of suggestedTokens contains an entry with both
|
||||
* 1. a symbol that matches an existing token
|
||||
* 2. an address that does not match an existing token
|
||||
*/
|
||||
function hasDuplicateSymbolAndDiffAddress(suggestedAssets, tokens) {
|
||||
const duplicate = suggestedAssets.find(({ asset }) => {
|
||||
function hasDuplicateSymbolAndDiffAddress(suggestedTokens, tokens) {
|
||||
const duplicate = suggestedTokens.find(({ requestData: { asset } }) => {
|
||||
const dupe = tokens.find((token) => {
|
||||
return (
|
||||
isEqualCaseInsensitive(token.symbol, asset.symbol) &&
|
||||
!isEqualCaseInsensitive(token.address, asset.address)
|
||||
isEqualCaseInsensitive(token.symbol, asset?.symbol) &&
|
||||
!isEqualCaseInsensitive(token.address, asset?.address)
|
||||
);
|
||||
});
|
||||
return Boolean(dupe);
|
||||
@ -77,18 +76,13 @@ const ConfirmAddSuggestedToken = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
|
||||
const suggestedAssets = useSelector((metamaskState) =>
|
||||
getApprovalRequestsByType(metamaskState, ApprovalType.WatchAsset).map(
|
||||
({ requestData }) => requestData,
|
||||
),
|
||||
);
|
||||
const suggestedTokens = useSelector(getSuggestedTokens);
|
||||
const tokens = useSelector(getTokens);
|
||||
|
||||
const trackEvent = useContext(MetaMetricsContext);
|
||||
|
||||
const knownTokenActionableMessage = useMemo(() => {
|
||||
return (
|
||||
hasDuplicateAddress(suggestedAssets, tokens) && (
|
||||
hasDuplicateAddress(suggestedTokens, tokens) && (
|
||||
<ActionableMessage
|
||||
message={t('knownTokenWarning', [
|
||||
<Button
|
||||
@ -109,11 +103,11 @@ const ConfirmAddSuggestedToken = () => {
|
||||
/>
|
||||
)
|
||||
);
|
||||
}, [suggestedAssets, tokens, t]);
|
||||
}, [suggestedTokens, tokens, t]);
|
||||
|
||||
const reusedTokenNameActionableMessage = useMemo(() => {
|
||||
return (
|
||||
hasDuplicateSymbolAndDiffAddress(suggestedAssets, tokens) && (
|
||||
hasDuplicateSymbolAndDiffAddress(suggestedTokens, tokens) && (
|
||||
<ActionableMessage
|
||||
message={t('reusedTokenNameWarning')}
|
||||
type="warning"
|
||||
@ -123,11 +117,11 @@ const ConfirmAddSuggestedToken = () => {
|
||||
/>
|
||||
)
|
||||
);
|
||||
}, [suggestedAssets, tokens, t]);
|
||||
}, [suggestedTokens, tokens, t]);
|
||||
|
||||
const handleAddTokensClick = useCallback(async () => {
|
||||
await Promise.all(
|
||||
suggestedAssets.map(async ({ asset, id }) => {
|
||||
suggestedTokens.map(async ({ requestData: { asset }, id }) => {
|
||||
await dispatch(resolvePendingApproval(id, null));
|
||||
|
||||
trackEvent({
|
||||
@ -146,11 +140,11 @@ const ConfirmAddSuggestedToken = () => {
|
||||
}),
|
||||
);
|
||||
history.push(mostRecentOverviewPage);
|
||||
}, [dispatch, history, trackEvent, mostRecentOverviewPage, suggestedAssets]);
|
||||
}, [dispatch, history, trackEvent, mostRecentOverviewPage, suggestedTokens]);
|
||||
|
||||
const handleCancelClick = useCallback(async () => {
|
||||
const handleCancelTokenClick = useCallback(async () => {
|
||||
await Promise.all(
|
||||
suggestedAssets.map(({ id }) =>
|
||||
suggestedTokens.map(({ id }) =>
|
||||
dispatch(
|
||||
rejectPendingApproval(
|
||||
id,
|
||||
@ -160,16 +154,16 @@ const ConfirmAddSuggestedToken = () => {
|
||||
),
|
||||
);
|
||||
history.push(mostRecentOverviewPage);
|
||||
}, [dispatch, history, mostRecentOverviewPage, suggestedAssets]);
|
||||
}, [dispatch, history, mostRecentOverviewPage, suggestedTokens]);
|
||||
|
||||
const goBackIfNoSuggestedAssetsOnFirstRender = () => {
|
||||
if (!suggestedAssets.length) {
|
||||
const goBackIfNoSuggestedTokensOnFirstRender = () => {
|
||||
if (!suggestedTokens.length) {
|
||||
history.push(mostRecentOverviewPage);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
goBackIfNoSuggestedAssetsOnFirstRender();
|
||||
goBackIfNoSuggestedTokensOnFirstRender();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@ -194,7 +188,7 @@ const ConfirmAddSuggestedToken = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-add-suggested-token__token-list">
|
||||
{suggestedAssets.map(({ asset }) => {
|
||||
{suggestedTokens.map(({ requestData: { asset } }) => {
|
||||
return (
|
||||
<div
|
||||
className="confirm-add-suggested-token__token-list-item"
|
||||
@ -223,9 +217,9 @@ const ConfirmAddSuggestedToken = () => {
|
||||
<PageContainerFooter
|
||||
cancelText={t('cancel')}
|
||||
submitText={t('addToken')}
|
||||
onCancel={handleCancelClick}
|
||||
onCancel={handleCancelTokenClick}
|
||||
onSubmit={handleAddTokensClick}
|
||||
disabled={suggestedAssets.length === 0}
|
||||
disabled={suggestedTokens.length === 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { pendingAssetApprovals as mockPendingAssetApprovals } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
|
||||
import { pendingTokenApprovals as mockPendingTokenApprovals } from '../../../.storybook/initial-states/approval-screens/add-suggested-token';
|
||||
|
||||
import configureStore from '../../store/store';
|
||||
|
||||
@ -12,7 +12,7 @@ import ConfirmAddSuggestedToken from '.';
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
pendingApprovals: [...mockPendingAssetApprovals],
|
||||
pendingApprovals: mockPendingTokenApprovals,
|
||||
tokens: [],
|
||||
},
|
||||
});
|
||||
@ -29,10 +29,11 @@ export const WithDuplicateAddress = () => <ConfirmAddSuggestedToken />;
|
||||
const WithDuplicateAddressStore = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
pendingApprovals: [...mockPendingAssetApprovals],
|
||||
pendingApprovals: mockPendingTokenApprovals,
|
||||
|
||||
tokens: [
|
||||
{
|
||||
...mockPendingAssetApprovals[0].requestData.asset,
|
||||
...Object.values(mockPendingTokenApprovals)[0].asset,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -47,10 +48,11 @@ export const WithDuplicateSymbolAndDifferentAddress = () => (
|
||||
const WithDuplicateSymbolAndDifferentAddressStore = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
pendingApprovals: [...mockPendingAssetApprovals],
|
||||
pendingApprovals: mockPendingTokenApprovals,
|
||||
|
||||
tokens: [
|
||||
{
|
||||
...mockPendingAssetApprovals[0].requestData.asset,
|
||||
...Object.values(mockPendingTokenApprovals)[0].asset,
|
||||
address: '0xNonSuggestedAddress',
|
||||
},
|
||||
],
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 { ApprovalType } from '@metamask/controller-utils';
|
||||
import {
|
||||
resolvePendingApproval,
|
||||
rejectPendingApproval,
|
||||
@ -10,37 +10,40 @@ import configureStore from '../../store/store';
|
||||
import { renderWithProvider } from '../../../test/jest/rendering';
|
||||
import ConfirmAddSuggestedToken from '.';
|
||||
|
||||
const MOCK_SUGGESTED_ASSETS = [
|
||||
{
|
||||
id: 1,
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
symbol: 'NEW',
|
||||
decimals: 18,
|
||||
image: 'metamark.svg',
|
||||
unlisted: false,
|
||||
const PENDING_APPROVALS = {
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'https://test-dapp.com',
|
||||
time: Date.now(),
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
symbol: 'NEW',
|
||||
decimals: 18,
|
||||
image: 'metamark.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
requestState: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
symbol: '0XYX',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
unlisted: false,
|
||||
2: {
|
||||
id: '2',
|
||||
origin: 'https://test-dapp.com',
|
||||
time: Date.now(),
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
symbol: '0XYX',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
unlisted: false,
|
||||
},
|
||||
},
|
||||
requestState: null,
|
||||
},
|
||||
];
|
||||
|
||||
const MOCK_PENDING_ASSET_APPROVALS = MOCK_SUGGESTED_ASSETS.map(
|
||||
(requestData) => {
|
||||
return {
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData,
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const MOCK_TOKEN = {
|
||||
address: '0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d',
|
||||
@ -56,7 +59,7 @@ jest.mock('../../store/actions', () => ({
|
||||
const renderComponent = (tokens = []) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
pendingApprovals: [...MOCK_PENDING_ASSET_APPROVALS],
|
||||
pendingApprovals: PENDING_APPROVALS,
|
||||
tokens,
|
||||
providerConfig: { chainId: '0x1' },
|
||||
},
|
||||
@ -86,11 +89,13 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
||||
it('should render the list of suggested tokens', () => {
|
||||
renderComponent();
|
||||
|
||||
for (const { asset } of MOCK_SUGGESTED_ASSETS) {
|
||||
for (const {
|
||||
requestData: { asset },
|
||||
} of Object.values(PENDING_APPROVALS)) {
|
||||
expect(screen.getByText(asset.symbol)).toBeInTheDocument();
|
||||
}
|
||||
expect(screen.getAllByRole('img')).toHaveLength(
|
||||
MOCK_SUGGESTED_ASSETS.length,
|
||||
Object.values(PENDING_APPROVALS).length,
|
||||
);
|
||||
});
|
||||
|
||||
@ -103,10 +108,10 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
||||
});
|
||||
|
||||
expect(resolvePendingApproval).toHaveBeenCalledTimes(
|
||||
MOCK_SUGGESTED_ASSETS.length,
|
||||
Object.values(PENDING_APPROVALS).length,
|
||||
);
|
||||
|
||||
MOCK_SUGGESTED_ASSETS.forEach(({ id }) => {
|
||||
Object.values(PENDING_APPROVALS).forEach(({ id }) => {
|
||||
expect(resolvePendingApproval).toHaveBeenCalledWith(id, null);
|
||||
});
|
||||
});
|
||||
@ -120,10 +125,10 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
||||
});
|
||||
|
||||
expect(rejectPendingApproval).toHaveBeenCalledTimes(
|
||||
MOCK_SUGGESTED_ASSETS.length,
|
||||
Object.values(PENDING_APPROVALS).length,
|
||||
);
|
||||
|
||||
MOCK_SUGGESTED_ASSETS.forEach(({ id }) => {
|
||||
Object.values(PENDING_APPROVALS).forEach(({ id }) => {
|
||||
expect(rejectPendingApproval).toHaveBeenCalledWith(
|
||||
id,
|
||||
expect.objectContaining({
|
||||
@ -140,7 +145,8 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
||||
const mockTokens = [
|
||||
{
|
||||
...MOCK_TOKEN,
|
||||
address: MOCK_SUGGESTED_ASSETS[0].asset.address,
|
||||
address:
|
||||
Object.values(PENDING_APPROVALS)[0].requestData.asset.address,
|
||||
},
|
||||
];
|
||||
renderComponent(mockTokens);
|
||||
@ -163,7 +169,7 @@ describe('ConfirmAddSuggestedToken Component', () => {
|
||||
const mockTokens = [
|
||||
{
|
||||
...MOCK_TOKEN,
|
||||
symbol: MOCK_SUGGESTED_ASSETS[0].asset.symbol,
|
||||
symbol: Object.values(PENDING_APPROVALS)[0].requestData.asset.symbol,
|
||||
},
|
||||
];
|
||||
renderComponent(mockTokens);
|
||||
|
@ -15,7 +15,7 @@ const renderComponent = (props) => {
|
||||
const props = {
|
||||
siteImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||
origin: 'https://metamask.github.io/test-dapp/',
|
||||
tokenSymbol: 'TestDappCollectibles (#1)',
|
||||
tokenSymbol: 'TestDappNFTs (#1)',
|
||||
assetStandard: TokenStandard.ERC721,
|
||||
tokenImage: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
|
||||
showCustomizeGasModal: jest.fn(),
|
||||
@ -49,7 +49,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
queryByText('https://metamask.github.io/test-dapp/'),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
|
||||
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
|
||||
' Allow access to and transfer of your TestDappNFTs (#1)? ',
|
||||
);
|
||||
expect(
|
||||
queryByText(
|
||||
@ -112,7 +112,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
queryByText('https://metamask.github.io/test-dapp/'),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
|
||||
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
|
||||
' Allow access to and transfer of your TestDappNFTs (#1)? ',
|
||||
);
|
||||
expect(
|
||||
queryByText(
|
||||
@ -174,7 +174,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
queryByText('https://metamask.github.io/test-dapp/'),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
|
||||
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
|
||||
' Allow access to and transfer of your TestDappNFTs (#1)? ',
|
||||
);
|
||||
expect(
|
||||
queryByText(
|
||||
@ -232,7 +232,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
queryByText('https://metamask.github.io/test-dapp/'),
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
|
||||
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
|
||||
' Allow access to and transfer of your TestDappNFTs (#1)? ',
|
||||
);
|
||||
expect(
|
||||
queryByText(
|
||||
|
@ -45,6 +45,7 @@ import {
|
||||
RESTORE_VAULT_ROUTE,
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_NFT_ROUTE,
|
||||
CONNECT_ROUTE,
|
||||
CONNECTED_ROUTE,
|
||||
CONNECTED_ACCOUNTS_ROUTE,
|
||||
@ -105,8 +106,9 @@ export default class Home extends PureComponent {
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
forgottenPassword: PropTypes.bool,
|
||||
hasWatchAssetPendingApprovals: PropTypes.bool,
|
||||
hasTransactionPendingApprovals: PropTypes.bool.isRequired,
|
||||
hasWatchTokenPendingApprovals: PropTypes.bool,
|
||||
hasWatchNftPendingApprovals: PropTypes.bool,
|
||||
shouldShowSeedPhraseReminder: PropTypes.bool.isRequired,
|
||||
isPopup: PropTypes.bool,
|
||||
isNotification: PropTypes.bool.isRequired,
|
||||
@ -194,7 +196,8 @@ export default class Home extends PureComponent {
|
||||
haveSwapsQuotes,
|
||||
isNotification,
|
||||
showAwaitingSwapScreen,
|
||||
hasWatchAssetPendingApprovals,
|
||||
hasWatchTokenPendingApprovals,
|
||||
hasWatchNftPendingApprovals,
|
||||
swapsFetchParams,
|
||||
hasTransactionPendingApprovals,
|
||||
} = this.props;
|
||||
@ -205,7 +208,8 @@ export default class Home extends PureComponent {
|
||||
} else if (
|
||||
firstPermissionsRequestId ||
|
||||
hasTransactionPendingApprovals ||
|
||||
hasWatchAssetPendingApprovals ||
|
||||
hasWatchTokenPendingApprovals ||
|
||||
hasWatchNftPendingApprovals ||
|
||||
(!isNotification &&
|
||||
(showAwaitingSwapScreen || haveSwapsQuotes || swapsFetchParams))
|
||||
) {
|
||||
@ -267,8 +271,9 @@ export default class Home extends PureComponent {
|
||||
firstPermissionsRequestId,
|
||||
history,
|
||||
isNotification,
|
||||
hasWatchAssetPendingApprovals,
|
||||
hasTransactionPendingApprovals,
|
||||
hasWatchTokenPendingApprovals,
|
||||
hasWatchNftPendingApprovals,
|
||||
haveSwapsQuotes,
|
||||
showAwaitingSwapScreen,
|
||||
swapsFetchParams,
|
||||
@ -289,8 +294,10 @@ export default class Home extends PureComponent {
|
||||
history.push(`${CONNECT_ROUTE}/${firstPermissionsRequestId}`);
|
||||
} else if (hasTransactionPendingApprovals) {
|
||||
history.push(CONFIRM_TRANSACTION_ROUTE);
|
||||
} else if (hasWatchAssetPendingApprovals) {
|
||||
} else if (hasWatchTokenPendingApprovals) {
|
||||
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE);
|
||||
} else if (hasWatchNftPendingApprovals) {
|
||||
history.push(CONFIRM_ADD_SUGGESTED_NFT_ROUTE);
|
||||
} else if (pendingConfirmations.length > 0) {
|
||||
history.push(CONFIRMATION_V_NEXT_ROUTE);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
import {
|
||||
getMmiPortfolioEnabled,
|
||||
@ -35,7 +34,8 @@ import {
|
||||
getNewTokensImported,
|
||||
getShouldShowSeedPhraseReminder,
|
||||
getRemoveNftMessage,
|
||||
hasPendingApprovals,
|
||||
getSuggestedTokens,
|
||||
getSuggestedNfts,
|
||||
} from '../../selectors';
|
||||
|
||||
import {
|
||||
@ -121,14 +121,14 @@ const mapStateToProps = (state) => {
|
||||
hasUnsignedQRHardwareTransaction(state) ||
|
||||
hasUnsignedQRHardwareMessage(state);
|
||||
|
||||
const hasWatchAssetPendingApprovals = hasPendingApprovals(
|
||||
state,
|
||||
ApprovalType.WatchAsset,
|
||||
);
|
||||
const hasWatchTokenPendingApprovals = getSuggestedTokens(state).length > 0;
|
||||
|
||||
const hasWatchNftPendingApprovals = getSuggestedNfts(state).length > 0;
|
||||
|
||||
return {
|
||||
forgottenPassword,
|
||||
hasWatchAssetPendingApprovals,
|
||||
hasWatchTokenPendingApprovals,
|
||||
hasWatchNftPendingApprovals,
|
||||
swapsEnabled,
|
||||
hasTransactionPendingApprovals: hasTransactionPendingApprovals(state),
|
||||
shouldShowSeedPhraseReminder: getShouldShowSeedPhraseReminder(state),
|
||||
|
@ -4,6 +4,7 @@
|
||||
@import 'asset/asset';
|
||||
@import 'confirm-import-token/index';
|
||||
@import 'confirm-add-suggested-token/index';
|
||||
@import 'confirm-add-suggested-nft/index';
|
||||
@import 'confirm-approve/index';
|
||||
@import 'confirm-decrypt-message/confirm-decrypt-message';
|
||||
@import 'confirm-encryption-public-key/confirm-encryption-public-key';
|
||||
|
@ -23,6 +23,7 @@ import AddNftPage from '../add-nft';
|
||||
import ConfirmImportTokenPage from '../confirm-import-token';
|
||||
import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token';
|
||||
import CreateAccountPage from '../create-account/create-account.component';
|
||||
import ConfirmAddSuggestedNftPage from '../confirm-add-suggested-nft';
|
||||
import Loading from '../../components/ui/loading-screen';
|
||||
import LoadingNetwork from '../../components/app/loading-network-screen';
|
||||
import { Modal } from '../../components/app/modals';
|
||||
@ -59,6 +60,7 @@ import {
|
||||
IMPORT_TOKEN_ROUTE,
|
||||
ASSET_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
||||
CONFIRM_ADD_SUGGESTED_NFT_ROUTE,
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
CONNECT_ROUTE,
|
||||
DEFAULT_ROUTE,
|
||||
@ -284,6 +286,11 @@ export default class Routes extends Component {
|
||||
component={ConfirmAddSuggestedTokenPage}
|
||||
exact
|
||||
/>
|
||||
<Authenticated
|
||||
path={CONFIRM_ADD_SUGGESTED_NFT_ROUTE}
|
||||
component={ConfirmAddSuggestedNftPage}
|
||||
exact
|
||||
/>
|
||||
<Authenticated
|
||||
path={CONFIRMATION_V_NEXT_ROUTE}
|
||||
component={ConfirmationPage}
|
||||
|
@ -21,7 +21,7 @@ const props = {
|
||||
tokenURI:
|
||||
'data:application/json;base64,eyJuYW1lIjogIlRlc3QgRGFwcCBDb2xsZWN0aWJsZXMgIzIiLCAiZGVzY3JpcHRpb24iOiAiVGVzdCBEYXBwIENvbGxlY3RpYmxlcyBmb3IgdGVzdGluZy4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCb1pXbG5hSFE5SWpNMU1DSWdkMmxrZEdnOUlqTTFNQ0lnZG1sbGQwSnZlRDBpTUNBd0lERXdNQ0F4TURBaUlIaHRiRzV6UFNKb2RIUndPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1EQXdMM04yWnlJK1BHUmxabk0rUEhCaGRHZ2dhV1E5SWsxNVVHRjBhQ0lnWm1sc2JEMGlibTl1WlNJZ2MzUnliMnRsUFNKeVpXUWlJR1E5SWsweE1DdzVNQ0JST1RBc09UQWdPVEFzTkRVZ1VUa3dMREV3SURVd0xERXdJRkV4TUN3eE1DQXhNQ3cwTUNCUk1UQXNOekFnTkRVc056QWdVVGN3TERjd0lEYzFMRFV3SWlBdlBqd3ZaR1ZtY3o0OGRHVjRkRDQ4ZEdWNGRGQmhkR2dnYUhKbFpqMGlJMDE1VUdGMGFDSStVWFZwWTJzZ1luSnZkMjRnWm05NElHcDFiWEJ6SUc5MlpYSWdkR2hsSUd4aGVua2daRzluTGp3dmRHVjRkRkJoZEdnK1BDOTBaWGgwUGp3dmMzWm5QZz09IiwgImF0dHJpYnV0ZXMiOiBbeyJ0cmFpdF90eXBlIjogIlRva2VuIElkIiwgInZhbHVlIjogIjIifV19',
|
||||
symbol: 'TDC',
|
||||
name: 'TestDappCollectibles',
|
||||
name: 'TestDappNFTs',
|
||||
image:
|
||||
'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM1MCIgd2lkdGg9IjM1MCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdGggaWQ9Ik15UGF0aCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJyZWQiIGQ9Ik0xMCw5MCBROTAsOTAgOTAsNDUgUTkwLDEwIDUwLDEwIFExMCwxMCAxMCw0MCBRMTAsNzAgNDUsNzAgUTcwLDcwIDc1LDUwIiAvPjwvZGVmcz48dGV4dD48dGV4dFBhdGggaHJlZj0iI015UGF0aCI+UXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLjwvdGV4dFBhdGg+PC90ZXh0Pjwvc3ZnPg==',
|
||||
},
|
||||
|
@ -11,25 +11,38 @@ type ApprovalsMetaMaskState = {
|
||||
};
|
||||
};
|
||||
|
||||
export function hasPendingApprovals(
|
||||
state: ApprovalsMetaMaskState,
|
||||
approvalType: ApprovalType,
|
||||
predicate?: (
|
||||
approval: ApprovalControllerState['pendingApprovals'][string],
|
||||
) => boolean,
|
||||
) {
|
||||
const pendingApprovalRequests = Object.values(
|
||||
state.metamask.pendingApprovals,
|
||||
).filter(({ type }) => type === approvalType);
|
||||
|
||||
if (predicate) {
|
||||
return pendingApprovalRequests.some(predicate);
|
||||
}
|
||||
|
||||
return pendingApprovalRequests.length > 0;
|
||||
}
|
||||
|
||||
export const getApprovalRequestsByType = (
|
||||
state: ApprovalsMetaMaskState,
|
||||
approvalType: ApprovalType,
|
||||
predicate?: (
|
||||
approval: ApprovalControllerState['pendingApprovals'][string],
|
||||
) => boolean,
|
||||
) => {
|
||||
const pendingApprovalRequests = Object.values(
|
||||
state.metamask.pendingApprovals,
|
||||
).filter(({ type }) => type === approvalType);
|
||||
|
||||
if (predicate) {
|
||||
return pendingApprovalRequests.filter(predicate);
|
||||
}
|
||||
|
||||
return pendingApprovalRequests;
|
||||
};
|
||||
|
||||
export function hasPendingApprovals(
|
||||
state: ApprovalsMetaMaskState,
|
||||
approvalType: ApprovalType,
|
||||
) {
|
||||
const pendingApprovalRequests = getApprovalRequestsByType(
|
||||
state,
|
||||
approvalType,
|
||||
);
|
||||
|
||||
return pendingApprovalRequests.length > 0;
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ export function getPermissionsRequests(state) {
|
||||
return getApprovalRequestsByType(
|
||||
state,
|
||||
ApprovalType.WalletRequestPermissions,
|
||||
).map(({ requestData }) => requestData);
|
||||
)?.map(({ requestData }) => requestData);
|
||||
}
|
||||
|
||||
export function getFirstPermissionRequest(state) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import { SubjectType } from '@metamask/subject-metadata-controller';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
import { ApprovalType, ERC1155, ERC721 } from '@metamask/controller-utils';
|
||||
import {
|
||||
createSelector,
|
||||
createSelectorCreator,
|
||||
@ -544,7 +545,7 @@ export function getUnapprovedTxCount(state) {
|
||||
}
|
||||
|
||||
export function getUnapprovedConfirmations(state) {
|
||||
const { pendingApprovals } = state.metamask;
|
||||
const { pendingApprovals = {} } = state.metamask;
|
||||
return Object.values(pendingApprovals);
|
||||
}
|
||||
|
||||
@ -555,6 +556,28 @@ export function getUnapprovedTemplatedConfirmations(state) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getSuggestedTokens(state) {
|
||||
return (
|
||||
getUnapprovedConfirmations(state)?.filter(({ type, requestData }) => {
|
||||
return (
|
||||
type === ApprovalType.WatchAsset &&
|
||||
requestData?.asset?.tokenId === undefined
|
||||
);
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
|
||||
export function getSuggestedNfts(state) {
|
||||
return (
|
||||
getUnapprovedConfirmations(state)?.filter(({ requestData, type }) => {
|
||||
return (
|
||||
type === ApprovalType.WatchAsset &&
|
||||
[ERC721, ERC1155].includes(requestData?.asset?.standard)
|
||||
);
|
||||
}) || []
|
||||
);
|
||||
}
|
||||
|
||||
export function getIsMainnet(state) {
|
||||
const chainId = getCurrentChainId(state);
|
||||
return chainId === CHAIN_IDS.MAINNET;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import mockState from '../../test/data/mock-state.json';
|
||||
import { KeyringType } from '../../shared/constants/keyring';
|
||||
import {
|
||||
@ -20,6 +21,184 @@ describe('Selectors', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getSuggestedTokens', () => {
|
||||
it('returns an empty array if pendingApprovals is undefined', () => {
|
||||
expect(selectors.getSuggestedTokens({ metamask: {} })).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('returns suggestedTokens from filtered pending approvals', () => {
|
||||
const pendingApprovals = {
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
symbol: 'NEW',
|
||||
decimals: 18,
|
||||
image: 'metamark.svg',
|
||||
},
|
||||
},
|
||||
requestState: null,
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
symbol: '0XYX',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
},
|
||||
},
|
||||
},
|
||||
3: {
|
||||
id: '3',
|
||||
origin: 'origin',
|
||||
time: 1,
|
||||
type: ApprovalType.Transaction,
|
||||
requestData: {
|
||||
// something that is not an asset
|
||||
},
|
||||
},
|
||||
4: {
|
||||
id: '4',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x1234abcd',
|
||||
symbol: '0XYX',
|
||||
tokenId: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
selectors.getSuggestedTokens({ metamask: { pendingApprovals } }),
|
||||
).toStrictEqual([
|
||||
{
|
||||
id: '1',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
symbol: 'NEW',
|
||||
decimals: 18,
|
||||
image: 'metamark.svg',
|
||||
},
|
||||
},
|
||||
requestState: null,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
symbol: '0XYX',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getSuggestedNfts', () => {
|
||||
it('returns an empty array if pendingApprovals is undefined', () => {
|
||||
expect(selectors.getSuggestedNfts({ metamask: {} })).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('returns suggestedNfts from filtered pending approvals', () => {
|
||||
const pendingApprovals = {
|
||||
1: {
|
||||
id: '1',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x8b175474e89094c44da98b954eedeac495271d0a',
|
||||
symbol: 'NEW',
|
||||
decimals: 18,
|
||||
image: 'metamark.svg',
|
||||
},
|
||||
},
|
||||
requestState: null,
|
||||
},
|
||||
2: {
|
||||
id: '2',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0xC8c77482e45F1F44dE1745F52C74426C631bDD51',
|
||||
symbol: '0XYX',
|
||||
decimals: 18,
|
||||
image: '0x.svg',
|
||||
},
|
||||
},
|
||||
},
|
||||
3: {
|
||||
id: '3',
|
||||
origin: 'origin',
|
||||
time: 1,
|
||||
type: ApprovalType.Transaction,
|
||||
requestData: {
|
||||
// something that is not an asset
|
||||
},
|
||||
},
|
||||
4: {
|
||||
id: '4',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x1234abcd',
|
||||
symbol: '0XYX',
|
||||
tokenId: '123',
|
||||
standard: 'ERC721',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
selectors.getSuggestedNfts({ metamask: { pendingApprovals } }),
|
||||
).toStrictEqual([
|
||||
{
|
||||
id: '4',
|
||||
origin: 'dapp',
|
||||
time: 1,
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {
|
||||
asset: {
|
||||
address: '0x1234abcd',
|
||||
symbol: '0XYX',
|
||||
tokenId: '123',
|
||||
standard: 'ERC721',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getNewNetworkAdded', () => {
|
||||
it('returns undefined if newNetworkAddedName is undefined', () => {
|
||||
expect(selectors.getNewNetworkAdded({ appState: {} })).toBeUndefined();
|
||||
|
161
yarn.lock
161
yarn.lock
@ -4931,10 +4931,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/test-dapp@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@metamask/test-dapp@npm:6.0.0"
|
||||
checksum: eee793c8816d1205667002bd0ef60d248f3a35027937d66a51fd6a87e6eb7be70efd63052412d4dbabdd4329a5e729ab8293655dbdd8d3329bd732b1b1be45d1
|
||||
"@metamask/test-dapp@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "@metamask/test-dapp@npm:7.0.0"
|
||||
checksum: 4a03ed86a97c94eef8131c0951fb7bf7eb0c988c407ae8e5ed6a897da30d1c48adf3e1e51e45220df6e5d51c22736a06c732ac95ba5741223573cd660706876b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24367,7 +24367,7 @@ __metadata:
|
||||
"@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1"
|
||||
"@metamask/subject-metadata-controller": ^2.0.0
|
||||
"@metamask/swappable-obj-proxy": ^2.1.0
|
||||
"@metamask/test-dapp": ^6.0.0
|
||||
"@metamask/test-dapp": ^7.0.0
|
||||
"@metamask/utils": ^5.0.0
|
||||
"@ngraveio/bc-ur": ^1.1.6
|
||||
"@popperjs/core": ^2.4.0
|
||||
@ -24669,20 +24669,20 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-gfm-autolink-literal@npm:^1.0.0":
|
||||
version: 1.0.4
|
||||
resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.4"
|
||||
version: 1.0.5
|
||||
resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-sanitize-uri: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: ea66602cc8375bffb414a662f54d7868ed8ba38a7fe9fca6b2c5f6d9ac632f6ed29e88a58dbd45a580c5c629e50c13e9b864382b796d549a69c5f69ba1df51f9
|
||||
checksum: ec2f6bc4a3eb238c1b8be9744454ffbc2957e3d8a248697af5a26bb21479862300c0e40e0a92baf17c299ddf70d4bc4470d4eee112cd92322f87d81e45c2e83d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-gfm-footnote@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "micromark-extension-gfm-footnote@npm:1.1.0"
|
||||
version: 1.1.2
|
||||
resolution: "micromark-extension-gfm-footnote@npm:1.1.2"
|
||||
dependencies:
|
||||
micromark-core-commonmark: ^1.0.0
|
||||
micromark-factory-space: ^1.0.0
|
||||
@ -24692,13 +24692,13 @@ __metadata:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 7a5408625ef2cca5cc18e6591c2522a8a409f466a6fbc0ed938950aafe5fc9bf1eada65e1a4dd4e36ec3e7b24920de1f4b3e2c365d8f5cd2d6ccb1f8c2377c49
|
||||
checksum: c151a629ee1cd92363c018a50f926a002c944ac481ca72b3720b9529e9c20f1cbef98b0fefdcd2d594af37d0d9743673409cac488af0d2b194210fd16375dcb7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-gfm-strikethrough@npm:^1.0.0":
|
||||
version: 1.0.5
|
||||
resolution: "micromark-extension-gfm-strikethrough@npm:1.0.5"
|
||||
version: 1.0.7
|
||||
resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7"
|
||||
dependencies:
|
||||
micromark-util-chunked: ^1.0.0
|
||||
micromark-util-classify-character: ^1.0.0
|
||||
@ -24706,20 +24706,20 @@ __metadata:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 548c0f257753d735c741533411957f04253da53db31e1f398dc5dc1de9f398c45586baad5223dce8f3b55f9433c255e6eb695fc3104256b8c332dd8737136882
|
||||
checksum: 169e310a4408feade0df80180f60d48c5cc5b7070e5e75e0bbd914e9100273508162c4bb20b72d53081dc37f1ff5834b3afa137862576f763878552c03389811
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-gfm-table@npm:^1.0.0":
|
||||
version: 1.0.6
|
||||
resolution: "micromark-extension-gfm-table@npm:1.0.6"
|
||||
version: 1.0.7
|
||||
resolution: "micromark-extension-gfm-table@npm:1.0.7"
|
||||
dependencies:
|
||||
micromark-factory-space: ^1.0.0
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 92a5c15314bc87c9630a0cb1bd0b0ba4493e13e1bc5d02d55fdd843b56bf6b229ced2c73e331dd98d90d721e0929f5cf16737d3dd1864d61e6d0b7748695349a
|
||||
checksum: 4853731285224e409d7e2c94c6ec849165093bff819e701221701aa7b7b34c17702c44f2f831e96b49dc27bb07e445b02b025561b68e62f5c3254415197e7af6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24733,15 +24733,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-gfm-task-list-item@npm:^1.0.0":
|
||||
version: 1.0.4
|
||||
resolution: "micromark-extension-gfm-task-list-item@npm:1.0.4"
|
||||
version: 1.0.5
|
||||
resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5"
|
||||
dependencies:
|
||||
micromark-factory-space: ^1.0.0
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 2575bb47b320f2479d3cc2492ba7cf79d6baa9cd0200c0ed120fd0e318e64e8ebab4a93a056a3781cb5107193f3b36ebd2d86a5928308bef45fc121291f97eb5
|
||||
checksum: 929f05343d272cffb8008899289f4cffe986ef98fc622ebbd1aa4ff11470e6b32ed3e1f18cd294adb69cabb961a400650078f6c12b322cc515b82b5068b31960
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24762,196 +24762,195 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"micromark-factory-destination@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-factory-destination@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-factory-destination@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 8e733ae9c1c2342f14ff290bf09946e20f6f540117d80342377a765cac48df2ea5e748f33c8b07501ad7a43414b1a6597c8510ede2052b6bf1251fab89748e20
|
||||
checksum: 9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-factory-label@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "micromark-factory-label@npm:1.0.2"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-factory-label@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 957e9366bdc8dbc1437c0706ff96972fa985ab4b1274abcae12f6094f527cbf5c69e7f2304c23c7f4b96e311ff7911d226563b8b43dcfcd4091e8c985fb97ce6
|
||||
checksum: fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-factory-space@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-factory-space@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-factory-space@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 70d3aafde4e68ef4e509a3b644e9a29e4aada00801279e346577b008cbca06d78051bcd62aa7ea7425856ed73f09abd2b36607803055f726f52607ee7cb706b0
|
||||
checksum: b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-factory-title@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "micromark-factory-title@npm:1.0.2"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-factory-title@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-factory-space: ^1.0.0
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: 9a9cf66babde0bad1e25d6c1087082bfde6dfc319a36cab67c89651cc1a53d0e21cdec83262b5a4c33bff49f0e3c8dc2a7bd464e991d40dbea166a8f9b37e5b2
|
||||
checksum: 4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-factory-whitespace@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-factory-whitespace@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-factory-whitespace@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-factory-space: ^1.0.0
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 0888386e6ea2dd665a5182c570d9b3d0a172d3f11694ca5a2a84e552149c9f1429f5b975ec26e1f0fa4388c55a656c9f359ce5e0603aff6175ba3e255076f20b
|
||||
checksum: ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-character@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-character@npm:1.1.0"
|
||||
version: 1.2.0
|
||||
resolution: "micromark-util-character@npm:1.2.0"
|
||||
dependencies:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 504a4e3321f69bddf3fec9f0c1058239fc23336bda5be31d532b150491eda47965a251b37f8a7a9db0c65933b3aaa49cf88044fb1028be3af7c5ee6212bf8d5f
|
||||
checksum: 089e79162a19b4a28731736246579ab7e9482ac93cd681c2bfca9983dcff659212ef158a66a5957e9d4b1dba957d1b87b565d85418a5b009f0294f1f07f2aaac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-chunked@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-chunked@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-chunked@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
checksum: c1efd56e8c4217bcf1c6f1a9fb9912b4a2a5503b00d031da902be922fb3fee60409ac53f11739991291357b2784fb0647ddfc74c94753a068646c0cb0fd71421
|
||||
checksum: c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-classify-character@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-classify-character@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-classify-character@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 180446e6a1dec653f625ded028f244784e1db8d10ad05c5d70f08af9de393b4a03dc6cf6fa5ed8ccc9c24bbece7837abf3bf66681c0b4adf159364b7d5236dfd
|
||||
checksum: 8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-combine-extensions@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-combine-extensions@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-combine-extensions@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-chunked: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 5304a820ef75340e1be69d6ad167055b6ba9a3bafe8171e5945a935752f462415a9dd61eb3490220c055a8a11167209a45bfa73f278338b7d3d61fa1464d3f35
|
||||
checksum: ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-decode-numeric-character-reference@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-decode-numeric-character-reference@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
checksum: f3ae2bb582a80f1e9d3face026f585c0c472335c064bd850bde152376f0394cb2831746749b6be6e0160f7d73626f67d10716026c04c87f402c0dd45a1a28633
|
||||
checksum: 4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-decode-string@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "micromark-util-decode-string@npm:1.0.2"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-decode-string@npm:1.1.0"
|
||||
dependencies:
|
||||
decode-named-character-reference: ^1.0.0
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-decode-numeric-character-reference: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
checksum: 2dbb41c9691cc71505d39706405139fb7d6699429d577a524c7c248ac0cfd09d3dd212ad8e91c143a00b2896f26f81136edc67c5bda32d20446f0834d261b17a
|
||||
checksum: f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-encode@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "micromark-util-encode@npm:1.0.1"
|
||||
checksum: 9290583abfdc79ea3e7eb92c012c47a0e14327888f8aaa6f57ff79b3058d8e7743716b9d91abca3646f15ab3d78fdad9779fdb4ccf13349cd53309dfc845253a
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-encode@npm:1.1.0"
|
||||
checksum: 4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-html-tag-name@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-html-tag-name@npm:1.1.0"
|
||||
checksum: a9b783cec89ec813648d59799464c1950fe281ae797b2a965f98ad0167d7fa1a247718eff023b4c015f47211a172f9446b8e6b98aad50e3cd44a3337317dad2c
|
||||
version: 1.2.0
|
||||
resolution: "micromark-util-html-tag-name@npm:1.2.0"
|
||||
checksum: ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-normalize-identifier@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-normalize-identifier@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-normalize-identifier@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
checksum: d7c09d5e8318fb72f194af72664bd84a48a2928e3550b2b21c8fbc0ec22524f2a72e0f6663d2b95dc189a6957d3d7759b60716e888909710767cd557be821f8b
|
||||
checksum: 8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-resolve-all@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "micromark-util-resolve-all@npm:1.0.0"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-resolve-all@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-types: ^1.0.0
|
||||
checksum: 409667f2bd126ef8acce009270d2aecaaa5584c5807672bc657b09e50aa91bd2e552cf41e5be1e6469244a83349cbb71daf6059b746b1c44e3f35446fef63e50
|
||||
checksum: 1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-sanitize-uri@npm:^1.0.0":
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-sanitize-uri@npm:1.1.0"
|
||||
version: 1.2.0
|
||||
resolution: "micromark-util-sanitize-uri@npm:1.2.0"
|
||||
dependencies:
|
||||
micromark-util-character: ^1.0.0
|
||||
micromark-util-encode: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
checksum: fe6093faa0adeb8fad606184d927ce37f207dcc2ec7256438e7f273c8829686245dd6161b597913ef25a3c4fb61863d3612a40cb04cf15f83ba1b4087099996b
|
||||
checksum: 6663f365c4fe3961d622a580f4a61e34867450697f6806f027f21cf63c92989494895fcebe2345d52e249fe58a35be56e223a9776d084c9287818b40c779acc1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-subtokenize@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "micromark-util-subtokenize@npm:1.0.2"
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-subtokenize@npm:1.1.0"
|
||||
dependencies:
|
||||
micromark-util-chunked: ^1.0.0
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.0
|
||||
uvu: ^0.5.0
|
||||
checksum: c32ee58a7e1384ab1161a9ee02fbb04ad7b6e96d0b8c93dba9803c329a53d07f22ab394c7a96b2e30d6b8fbe3585b85817dba07277b1317111fc234e166bd2d1
|
||||
checksum: 4a9d780c4d62910e196ea4fd886dc4079d8e424e5d625c0820016da0ed399a281daff39c50f9288045cc4bcd90ab47647e5396aba500f0853105d70dc8b1fc45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-symbol@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "micromark-util-symbol@npm:1.0.1"
|
||||
checksum: c6a3023b3a7432c15864b5e33a1bcb5042ac7aa097f2f452e587bef45433d42d39e0a5cce12fbea91e0671098ba0c3f62a2b30ce1cde66ecbb5e8336acf4391d
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-symbol@npm:1.1.0"
|
||||
checksum: 02414a753b79f67ff3276b517eeac87913aea6c028f3e668a19ea0fc09d98aea9f93d6222a76ca783d20299af9e4b8e7c797fe516b766185dcc6e93290f11f88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1":
|
||||
version: 1.0.2
|
||||
resolution: "micromark-util-types@npm:1.0.2"
|
||||
checksum: 08dc901b7c06ee3dfeb54befca05cbdab9525c1cf1c1080967c3878c9e72cb9856c7e8ff6112816e18ead36ce6f99d55aaa91560768f2f6417b415dcba1244df
|
||||
version: 1.1.0
|
||||
resolution: "micromark-util-types@npm:1.1.0"
|
||||
checksum: b0ef2b4b9589f15aec2666690477a6a185536927ceb7aa55a0f46475852e012d75a1ab945187e5c7841969a842892164b15d58ff8316b8e0d6cc920cabd5ede7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "micromark@npm:3.1.0"
|
||||
version: 3.2.0
|
||||
resolution: "micromark@npm:3.2.0"
|
||||
dependencies:
|
||||
"@types/debug": ^4.0.0
|
||||
debug: ^4.0.0
|
||||
@ -24970,7 +24969,7 @@ __metadata:
|
||||
micromark-util-symbol: ^1.0.0
|
||||
micromark-util-types: ^1.0.1
|
||||
uvu: ^0.5.0
|
||||
checksum: 5fe5bc3bf92e2ddd37b5f0034080fc3a4d4b3c1130dd5e435bb96ec75e9453091272852e71a4d74906a8fcf992d6f79d794607657c534bda49941e9950a92e28
|
||||
checksum: 56c15851ad3eb8301aede65603473443e50c92a54849cac1dadd57e4ec33ab03a0a77f3df03de47133e6e8f695dae83b759b514586193269e98c0bf319ecd5e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user