diff --git a/.circleci/config.yml b/.circleci/config.yml index d053b456a..823da67ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -655,7 +655,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome --retries 2 + yarn test:e2e:chrome --retries 2 --debug fi no_output_timeout: 20m - run: @@ -692,7 +692,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome --retries 2 --mv3 || echo "Temporarily suppressing MV3 e2e test failures" + yarn test:e2e:chrome --retries 2 --debug --mv3 || echo "Temporarily suppressing MV3 e2e test failures" fi no_output_timeout: 20m - store_artifacts: @@ -720,7 +720,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:firefox:snaps --retries 2 + yarn test:e2e:firefox:snaps --retries 2 --debug fi no_output_timeout: 20m - run: @@ -757,7 +757,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:chrome:snaps --retries 2 + yarn test:e2e:chrome:snaps --retries 2 --debug fi no_output_timeout: 20m - run: @@ -794,7 +794,7 @@ jobs: command: | if .circleci/scripts/test-run-e2e.sh then - yarn test:e2e:firefox --retries 2 + yarn test:e2e:firefox --retries 2 --debug fi no_output_timeout: 20m - run: diff --git a/.eslintrc.js b/.eslintrc.js index 89578fa19..1236df167 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -237,6 +237,7 @@ module.exports = { ], excludedFiles: [ 'app/scripts/controllers/app-state.test.js', + 'app/scripts/controllers/mmi-controller.test.js', 'app/scripts/controllers/network/**/*.test.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', @@ -265,6 +266,7 @@ module.exports = { files: [ '**/__snapshots__/*.snap', 'app/scripts/controllers/app-state.test.js', + 'app/scripts/controllers/mmi-controller.test.js', 'app/scripts/controllers/network/**/*.test.js', 'app/scripts/controllers/network/**/*.test.ts', 'app/scripts/controllers/network/provider-api-tests/*.ts', diff --git a/.iyarc b/.iyarc index cea1e59eb..0bb3e981f 100644 --- a/.iyarc +++ b/.iyarc @@ -3,4 +3,6 @@ GHSA-257v-vj4p-3w2h # request library is subject to SSRF. # addressed by temporary patch in .yarn/patches/request-npm-2.88.2-f4a57c72c4.patch -GHSA-p8p7-x288-28g6 +# NOTE: Disabled for now since this library seems to have been removed from the +# dependency tree — we can re-enable this line later if it appears again +#GHSA-p8p7-x288-28g6 diff --git a/.metamaskrc.dist b/.metamaskrc.dist index d00289cdb..0e0bc421e 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -9,7 +9,6 @@ INFURA_PROJECT_ID=00000000000 ;SWAPS_USE_DEV_APIS= ;PORTFOLIO_URL= ;TRANSACTION_SECURITY_PROVIDER= -;MULTICHAIN= ; Set this to test changes to the phishing warning page. ;PHISHING_WARNING_PAGE_URL= diff --git a/.mocharc.js b/.mocharc.js index a34a05fc8..a8843b0e1 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -8,6 +8,7 @@ module.exports = { './app/scripts/controllers/app-state.test.js', './app/scripts/controllers/network/**/*.test.js', './app/scripts/controllers/permissions/**/*.test.js', + './app/scripts/controllers/mmi-controller.test.js', './app/scripts/constants/error-utils.test.js', ], recursive: true, diff --git a/.storybook/initial-states/approval-screens/add-suggested-token.js b/.storybook/initial-states/approval-screens/add-suggested-token.js index ee62778cc..4197140e6 100644 --- a/.storybook/initial-states/approval-screens/add-suggested-token.js +++ b/.storybook/initial-states/approval-screens/add-suggested-token.js @@ -1,83 +1,121 @@ -export const suggestedAssets = [ - { - asset: { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - symbol: 'ETH', - decimals: 18, - image: './images/eth_logo.png', - unlisted: false, +import { ApprovalType } from '@metamask/controller-utils'; + +export const pendingTokenApprovals = { + 1: { + id: 1, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + symbol: 'ETH', + decimals: 18, + image: './images/eth_logo.png', + unlisted: false, + }, }, }, - { - asset: { - address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', - symbol: '0X', - decimals: 18, - image: '0x.svg', - unlisted: false, + 2: { + id: 2, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', + symbol: '0X', + decimals: 18, + image: '0x.svg', + unlisted: false, + }, }, }, - { - asset: { - address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', - symbol: 'AST', - decimals: 18, - image: 'ast.png', - unlisted: false, + 3: { + id: 3, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + symbol: 'AST', + decimals: 18, + image: 'ast.png', + unlisted: false, + }, }, }, - { - asset: { - address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', - symbol: 'BAT', - decimals: 18, - image: 'BAT_icon.svg', - unlisted: false, + 4: { + id: 4, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + symbol: 'BAT', + decimals: 18, + image: 'BAT_icon.svg', + unlisted: false, + }, }, }, - { - asset: { - address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1', - symbol: 'CVL', - decimals: 18, - image: 'CVL_token.svg', - unlisted: false, + 5: { + id: 5, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1', + symbol: 'CVL', + decimals: 18, + image: 'CVL_token.svg', + unlisted: false, + }, }, }, - { - asset: { - address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', - symbol: 'GLA', - decimals: 18, - image: 'gladius.svg', - unlisted: false, + 6: { + id: 6, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', + symbol: 'GLA', + decimals: 18, + image: 'gladius.svg', + unlisted: false, + }, }, }, - { - asset: { - address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F', - symbol: 'GNO', - decimals: 18, - image: 'gnosis.svg', - unlisted: false, + 7: { + id: 7, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x467Bccd9d29f223BcE8043b84E8C8B282827790F', + symbol: 'GNO', + decimals: 18, + image: 'gnosis.svg', + unlisted: false, + }, }, }, - { - asset: { - address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2', - symbol: 'OMG', - decimals: 18, - image: 'omg.jpg', - unlisted: false, + 8: { + id: 8, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0xff20817765cb7f73d4bde2e66e067e58d11095c2', + symbol: 'OMG', + decimals: 18, + image: 'omg.jpg', + unlisted: false, + }, }, }, - { - asset: { - address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1', - symbol: 'WED', - decimals: 18, - image: 'wed.png', - unlisted: false, + 9: { + id: 9, + type: ApprovalType.WatchAsset, + requestData: { + asset: { + address: '0x8e870d67f660d95d5be530380d0ec0bd388289e1', + symbol: 'WED', + decimals: 18, + image: 'wed.png', + unlisted: false, + }, }, }, -]; +}; \ No newline at end of file diff --git a/.storybook/test-data.js b/.storybook/test-data.js index 9150466ac..f538ee255 100644 --- a/.storybook/test-data.js +++ b/.storybook/test-data.js @@ -16,81 +16,6 @@ const state = { protocol: 'https:', url: 'https://metamask.github.io/test-dapp/', }, - networkList: [ - { - blockExplorerUrl: 'https://etherscan.io', - chainId: '0x1', - iconColor: 'var(--mainnet)', - isATestNetwork: false, - labelKey: 'mainnet', - providerType: 'mainnet', - rpcUrl: 'https://mainnet.infura.io/v3/', - ticker: 'ETH', - viewOnly: true, - }, - { - blockExplorerUrl: 'https://goerli.etherscan.io', - chainId: '0x5', - iconColor: 'var(--color-network-goerli-default)', - isATestNetwork: true, - labelKey: 'goerli', - providerType: 'goerli', - rpcUrl: 'https://goerli.infura.io/v3/', - ticker: 'ETH', - viewOnly: true, - }, - { - blockExplorerUrl: 'https://sepolia.etherscan.io', - chainId: '0xaa36a7', - iconColor: 'var(--color-network-sepolia-default)', - isATestNetwork: true, - labelKey: 'sepolia', - providerType: 'sepolia', - rpcUrl: 'https://sepolia.infura.io/v3/', - ticker: 'ETH', - viewOnly: true, - }, - { - blockExplorerUrl: '', - chainId: '0x539', - iconColor: 'var(--color-network-localhost-default)', - isATestNetwork: true, - label: 'Localhost 8545', - providerType: 'rpc', - rpcUrl: 'http://localhost:8545', - ticker: 'ETH', - }, - { - blockExplorerUrl: 'https://bscscan.com', - chainId: '0x38', - iconColor: 'var(--color-network-localhost-default)', - isATestNetwork: false, - label: 'Binance Smart Chain', - providerType: 'rpc', - rpcUrl: 'https://bsc-dataseed.binance.org/', - ticker: 'BNB', - }, - { - blockExplorerUrl: 'https://cchain.explorer.avax.network/', - chainId: '0xa86a', - iconColor: 'var(--color-network-localhost-default)', - isATestNetwork: false, - label: 'Avalanche', - providerType: 'rpc', - rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', - ticker: 'AVAX', - }, - { - blockExplorerUrl: 'https://polygonscan.com', - chainId: '0x89', - iconColor: 'var(--color-network-localhost-default)', - isATestNetwork: false, - label: 'Polygon', - providerType: 'rpc', - rpcUrl: 'https://polygon-rpc.com', - ticker: 'MATIC', - }, - ], metamask: { tokenList: { '0x514910771af9ca656af840dff83e8264ecf986ca': { @@ -1228,7 +1153,6 @@ const state = { '0xaD6D458402F60fD3Bd25163575031ACDce07538D': './sai.svg', }, hiddenTokens: [], - suggestedAssets: [], useNonceField: false, usePhishDetect: true, useTokenDetection: true, @@ -1425,23 +1349,6 @@ const state = { }, }, }, - swapsState: { - quotes: {}, - fetchParams: null, - tokens: null, - tradeTxId: null, - approveTxId: null, - quotesLastFetched: null, - customMaxGas: '', - customGasPrice: null, - selectedAggId: null, - customApproveTxData: '', - errorKey: '', - topAggId: null, - routeState: '', - swapsFeatureIsLive: false, - swapsQuoteRefreshTime: 60000, - }, ensResolutionsByAddress: {}, pendingApprovals: {}, pendingApprovalCount: 0, @@ -1692,4 +1599,80 @@ const state = { }, }; +export const networkList = [ + { + blockExplorerUrl: 'https://etherscan.io', + chainId: '0x1', + iconColor: 'var(--mainnet)', + isATestNetwork: false, + labelKey: 'mainnet', + providerType: 'mainnet', + rpcUrl: 'https://mainnet.infura.io/v3/', + ticker: 'ETH', + viewOnly: true, + }, + { + blockExplorerUrl: 'https://goerli.etherscan.io', + chainId: '0x5', + iconColor: 'var(--color-network-goerli-default)', + isATestNetwork: true, + labelKey: 'goerli', + providerType: 'goerli', + rpcUrl: 'https://goerli.infura.io/v3/', + ticker: 'ETH', + viewOnly: true, + }, + { + blockExplorerUrl: 'https://sepolia.etherscan.io', + chainId: '0xaa36a7', + iconColor: 'var(--color-network-sepolia-default)', + isATestNetwork: true, + labelKey: 'sepolia', + providerType: 'sepolia', + rpcUrl: 'https://sepolia.infura.io/v3/', + ticker: 'ETH', + viewOnly: true, + }, + { + blockExplorerUrl: '', + chainId: '0x539', + iconColor: 'var(--color-network-localhost-default)', + isATestNetwork: true, + label: 'Localhost 8545', + providerType: 'rpc', + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + }, + { + blockExplorerUrl: 'https://bscscan.com', + chainId: '0x38', + iconColor: 'var(--color-network-localhost-default)', + isATestNetwork: false, + label: 'Binance Smart Chain', + providerType: 'rpc', + rpcUrl: 'https://bsc-dataseed.binance.org/', + ticker: 'BNB', + }, + { + blockExplorerUrl: 'https://cchain.explorer.avax.network/', + chainId: '0xa86a', + iconColor: 'var(--color-network-localhost-default)', + isATestNetwork: false, + label: 'Avalanche', + providerType: 'rpc', + rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + ticker: 'AVAX', + }, + { + blockExplorerUrl: 'https://polygonscan.com', + chainId: '0x89', + iconColor: 'var(--color-network-localhost-default)', + isATestNetwork: false, + label: 'Polygon', + providerType: 'rpc', + rpcUrl: 'https://polygon-rpc.com', + ticker: 'MATIC', + }, +] + export default state; diff --git a/.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch b/.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch deleted file mode 100644 index ec23c633e..000000000 --- a/.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/dist/AssetsContractController.js b/dist/AssetsContractController.js -index 332c87d7cc5687dec4d80ec3c51dcc4bda632c32..1d88cd313efb00c9c9b57f6824ed74bbca6349e3 100644 ---- a/dist/AssetsContractController.js -+++ b/dist/AssetsContractController.js -@@ -33,6 +33,7 @@ exports.SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID = { - [assetsUtil_1.SupportedTokenDetectionNetworks.bsc]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', - [assetsUtil_1.SupportedTokenDetectionNetworks.polygon]: '0x2352c63A83f9Fd126af8676146721Fa00924d7e4', - [assetsUtil_1.SupportedTokenDetectionNetworks.avax]: '0xD023D153a0DFa485130ECFdE2FAA7e612EF94818', -+ [assetsUtil_1.SupportedTokenDetectionNetworks.aurora]: '0x1286415D333855237f89Df27D388127181448538', - }; - exports.MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; - /** -diff --git a/dist/assetsUtil.d.ts b/dist/assetsUtil.d.ts -index 1b69903a3f8e19f5a451c94791ea16fb18e94d93..d45fe80652fea24d19b2b30dd9af7e6219939153 100644 ---- a/dist/assetsUtil.d.ts -+++ b/dist/assetsUtil.d.ts -@@ -45,7 +45,8 @@ export declare enum SupportedTokenDetectionNetworks { - mainnet = "1", - bsc = "56", - polygon = "137", -- avax = "43114" -+ avax = "43114", -+ aurora = "1313161554" - } - /** - * Check if token detection is enabled for certain networks. -diff --git a/dist/assetsUtil.js b/dist/assetsUtil.js -index 4b54e82504cdfae1d937860b0b89b14860385b8e..232228688454d32df818065119dff104f5b0c16c 100644 ---- a/dist/assetsUtil.js -+++ b/dist/assetsUtil.js -@@ -120,6 +120,7 @@ var SupportedTokenDetectionNetworks; - SupportedTokenDetectionNetworks["bsc"] = "56"; - SupportedTokenDetectionNetworks["polygon"] = "137"; - SupportedTokenDetectionNetworks["avax"] = "43114"; -+ SupportedTokenDetectionNetworks["aurora"] = "1313161554"; - })(SupportedTokenDetectionNetworks = exports.SupportedTokenDetectionNetworks || (exports.SupportedTokenDetectionNetworks = {})); - /** - * Check if token detection is enabled for certain networks. diff --git a/.yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch b/.yarn/patches/@metamask-signature-controller-npm-3.0.0-8771b6885e.patch similarity index 64% rename from .yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch rename to .yarn/patches/@metamask-signature-controller-npm-3.0.0-8771b6885e.patch index 999541da8..4a2682995 100644 --- a/.yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch +++ b/.yarn/patches/@metamask-signature-controller-npm-3.0.0-8771b6885e.patch @@ -1,14 +1,17 @@ diff --git a/dist/SignatureController.js b/dist/SignatureController.js -index 59dc5974218e8d073b65969154f35ada3fd9e8ab..5b16d09dcacf49caa15d11c9db7c34569eb36412 100644 +index b39d274f4547ab4e8b647293199ec21c4a9e38ca..288e55c97c3e4a234874dd8b8986ba77576b0dc4 100644 --- a/dist/SignatureController.js +++ b/dist/SignatureController.js -@@ -302,9 +302,9 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE +@@ -308,12 +308,12 @@ _SignatureController_keyringController = new WeakMap(), _SignatureController_isE const messageId = msgParams.metamaskId; try { const cleanMessageParams = yield messageManager.approveMessage(msgParams); + __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_acceptApproval).call(this, messageId); const signature = yield getSignature(cleanMessageParams); - messageManager.setMessageStatusSigned(messageId, signature); + this.hub.emit(`${methodName}:signed`, { signature, messageId }); + if (!cleanMessageParams.deferSetAsSigned) { + messageManager.setMessageStatusSigned(messageId, signature); + } - __classPrivateFieldGet(this, _SignatureController_instances, "m", _SignatureController_acceptApproval).call(this, messageId); return __classPrivateFieldGet(this, _SignatureController_getAllState, "f").call(this); } diff --git a/.yarn/patches/squirrelly-npm-8.0.8-1d17420d8d.patch b/.yarn/patches/squirrelly-npm-9.0.0-3cf710c7bb.patch similarity index 85% rename from .yarn/patches/squirrelly-npm-8.0.8-1d17420d8d.patch rename to .yarn/patches/squirrelly-npm-9.0.0-3cf710c7bb.patch index f316d6b4c..06da8c146 100644 --- a/.yarn/patches/squirrelly-npm-8.0.8-1d17420d8d.patch +++ b/.yarn/patches/squirrelly-npm-9.0.0-3cf710c7bb.patch @@ -1,5 +1,5 @@ diff --git a/dist/squirrelly.cjs.js b/dist/squirrelly.cjs.js -index 4680ee747900853b9af01b480c749880dedb9824..95dce7623bc115508063a78c7de9ed7b6e4d3d82 100644 +index b87580be5da8a9a5af4cf60c8c5acf910c8f9ed2..e037b8a986d331f7075bbd448c80104de8a3453c 100644 --- a/dist/squirrelly.cjs.js +++ b/dist/squirrelly.cjs.js @@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true }); diff --git a/.yarn/plugins/@yarnpkg/plugin-engines.cjs b/.yarn/plugins/@yarnpkg/plugin-engines.cjs new file mode 100644 index 000000000..f88ad2b43 --- /dev/null +++ b/.yarn/plugins/@yarnpkg/plugin-engines.cjs @@ -0,0 +1,9 @@ +/* eslint-disable */ +//prettier-ignore +module.exports = { +name: "@yarnpkg/plugin-engines", +factory: function (require) { +var plugin=(()=>{var P=Object.create,f=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var b=n=>f(n,"__esModule",{value:!0});var i=n=>{if(typeof require!="undefined")return require(n);throw new Error('Dynamic require of "'+n+'" is not supported')};var T=(n,e)=>{for(var r in e)f(n,r,{get:e[r],enumerable:!0})},V=(n,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of N(e))!Y.call(n,t)&&t!=="default"&&f(n,t,{get:()=>e[t],enumerable:!(r=R(e,t))||r.enumerable});return n},s=n=>V(b(f(n!=null?P(j(n)):{},"default",n&&n.__esModule&&"default"in n?{get:()=>n.default,enumerable:!0}:{value:n,enumerable:!0})),n);var U={};T(U,{default:()=>q});var o=s(i("@yarnpkg/core")),c;(function(r){r.Yarn="Yarn",r.Console="Console"})(c||(c={}));var h=class{constructor(e){this.throwWrongEngineError=(e,r)=>{let t=this.formatErrorMessage(e,r);this.throwError(t)};this.throwError=e=>{switch(this.errorReporter){case c.Yarn:this.reportYarnError(e);break;case c.Console:default:this.reportConsoleError(e);break}};this.reportYarnError=e=>{throw new o.ReportError(o.MessageName.UNNAMED,e)};this.reportConsoleError=e=>{console.error(e),process.exit(1)};this.formatErrorMessage=(e,r)=>{let{configuration:t}=this.project,p=o.formatUtils.applyStyle(t,o.formatUtils.pretty(t,this.engine,"green"),2),g=o.formatUtils.pretty(t,e,"cyan"),d=o.formatUtils.pretty(t,r,"cyan"),w=`The current ${p} version ${g} does not satisfy the required version ${d}.`;return o.formatUtils.pretty(t,w,"red")};this.project=e.project,this.errorReporter=e.errorReporter}};var m=s(i("fs")),y=s(i("path")),l=s(i("semver")),k=s(i("@yarnpkg/fslib")),a=s(i("@yarnpkg/core"));var v=class extends h{constructor(){super(...arguments);this.resolveNvmRequiredVersion=()=>{let{configuration:e,cwd:r}=this.project,t=(0,y.resolve)(k.npath.fromPortablePath(r),".nvmrc"),p=a.formatUtils.applyStyle(e,a.formatUtils.pretty(e,this.engine,"green"),2);if(!(0,m.existsSync)(t)){this.throwError(a.formatUtils.pretty(e,`Unable to verify the ${p} version. The .nvmrc file does not exist.`,"red"));return}let g=(0,m.readFileSync)(t,"utf-8").trim();if((0,l.validRange)(g))return g;let d=a.formatUtils.pretty(e,".nvmrc","yellow");this.throwError(a.formatUtils.pretty(e,`Unable to verify the ${p} version. The ${d} file contains an invalid semver range.`,"red"))}}get engine(){return"Node"}verifyEngine(e){let r=e.node;r!=null&&(r===".nvmrc"&&(r=this.resolveNvmRequiredVersion()),(0,l.satisfies)(process.version,r,{includePrerelease:!0})||this.throwWrongEngineError(process.version.replace(/^v/i,""),r.replace(/^v/i,"")))}};var x=s(i("semver")),E=s(i("@yarnpkg/core"));var u=class extends h{get engine(){return"Yarn"}verifyEngine(e){let r=e.yarn;r!=null&&((0,x.satisfies)(E.YarnVersion,r,{includePrerelease:!0})||this.throwWrongEngineError(E.YarnVersion,r))}};var C=n=>e=>{if(process.env.PLUGIN_YARN_ENGINES_DISABLE!=null)return;let{engines:r={}}=e.getWorkspaceByCwd(e.cwd).manifest.raw,t={project:e,errorReporter:n};[new v(t),new u(t)].forEach(g=>g.verifyEngine(r))},S={hooks:{validateProject:C(c.Yarn),setupScriptEnvironment:C(c.Console)}},q=S;return U;})(); +return plugin; +} +}; diff --git a/.yarnrc.yml b/.yarnrc.yml index e0161db88..5d351f8e2 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -13,5 +13,7 @@ plugins: spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js" - path: .yarn/plugins/@yarnpkg/plugin-version.cjs spec: "@yarnpkg/plugin-version" + - path: .yarn/plugins/@yarnpkg/plugin-engines.cjs + spec: "https://raw.githubusercontent.com/devoto13/yarn-plugin-engines/main/bundles/%40yarnpkg/plugin-engines.js" yarnPath: .yarn/releases/yarn-3.2.4.cjs diff --git a/README.md b/README.md index bf2671159..579b81c6c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ To learn how to contribute to the MetaMask project itself, visit our [Internal D - Install [Yarn v3](https://yarnpkg.com/getting-started/install) - ONLY follow the steps in the "Install Corepack" and "Updating the global Yarn version" sections - DO NOT take any of the steps in the "Initializing your project", "Updating to the latest versions" or "Installing the latest build fresh from master" sections. These steps could result in your repo being reset or installing the wrong yarn version, which can break your build. -- Copy the `.metamaskrc.dist` file to `.metamaskrc` +- Duplicate `.metamaskrc.dist` within the root and rename it to `.metamaskrc` - Replace the `INFURA_PROJECT_ID` value with your own personal [Infura Project ID](https://infura.io/docs). - If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/), see [Developing on MetaMask - Segment](./development/README.md#segment). - If debugging unhandled exceptions, you'll need to add a value for `SENTRY_DSN` [Sentry Dsn](https://docs.sentry.io/product/sentry-basics/dsn-explainer/), see [Developing on MetaMask - Sentry](./development/README.md#sentry). diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index c7bb730e5..17317ce1b 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -87,9 +87,6 @@ "back": { "message": "ተመለስ" }, - "backToAll": { - "message": "ወደ ሁሉም ተመለስ" - }, "backupApprovalInfo": { "message": "መሳሪያዎ ቢጠፋ፣ የይለፍ ቃልዎን ቢረሱ፣ MetaMask እንደገና መጫን ቢያስፈልግዎ ወይም በሌላ መሳሪያ ወደ ቋትዎ ለመድረስ ቢፈልጉ ይህ ሚስጥራዊ ኮድ ቋትዎን መልሶ ለማግኘት ያስፈልጋል።" }, @@ -148,16 +145,13 @@ "connect": { "message": "ይገናኙ" }, - "connectHardwareWallet": { - "message": "ከሃርድዌር ቋት ጋር ይገናኙ" - }, "connectingTo": { "message": "ከ $1ጋር መገናኘት" }, "connectingToGoerli": { "message": "ከ Goerli የሙከራ አውታረ መረብ ጋር መገናኘት" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "ከ Linea Goerli የሙከራ አውታረ መረብ ጋር መገናኘት" }, "connectingToMainnet": { @@ -187,9 +181,6 @@ "create": { "message": "ፍጠር" }, - "createAccount": { - "message": "መለያ ፍጠር" - }, "createPassword": { "message": "የይለፍ ቃል ፍጠር" }, @@ -403,7 +394,7 @@ "likeToImportTokens": { "message": "እነዚህን ተለዋጭ ስሞች ለማከል ይፈልጋሉ?" }, - "lineatestnet": { + "lineaGoerli": { "message": "የ Linea Goerli የሙከራ አውታረ መረብ" }, "links": { @@ -566,9 +557,6 @@ "readdToken": { "message": "በመለያ አማራጮችዎ ምናሌ ውስጥ ወደ “ተለዋጭ ስም አክል” በመግባት ለወደፊቱ ይህን ተለዋጭ ስም መልሰው ማከል ይችላሉ።" }, - "recents": { - "message": "የቅርብ ጊዜያት" - }, "recipientAddressPlaceholder": { "message": "ፍለጋ፣ ለሕዝብ ክፍት የሆነ አድራሻ (0x), ወይም ENS" }, @@ -792,16 +780,9 @@ "transfer": { "message": "ያስተላልፉ" }, - "transferBetweenAccounts": { - "message": "በመለያዎች መካከል አስተላልፍ" - }, "transferFrom": { "message": "የማዛወሪያ ቅጽ" }, - "troubleTokenBalances": { - "message": "የተለዋጭ ስም ቀሪ ሂሳብዎን ስንጭን ችግር አጋጥሞናል። ማየት ይችላሉ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "እንደገና ሞክር" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 392d7fdfa..f267c3675 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -97,9 +97,6 @@ "back": { "message": "الرجوع إلى الوراء" }, - "backToAll": { - "message": "العودة إلى الجميع" - }, "backupApprovalInfo": { "message": "هذا الرمز السري مطلوب لاسترداد محفظتك في حالة فقد جهازك أو نسيان كلمة مرورك أو إعادة تثبيت MetaMask أو الرغبة في الوصول إلى محفظتك عبر جهاز آخر." }, @@ -158,16 +155,13 @@ "connect": { "message": "اتصال" }, - "connectHardwareWallet": { - "message": "ربط محفظة الأجهزة" - }, "connectingTo": { "message": "جارِ الاتصال بـ $1" }, "connectingToGoerli": { "message": "الاتصال بشبكة اختبار Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "الاتصال بشبكة اختبار Linea Goerli" }, "connectingToMainnet": { @@ -197,9 +191,6 @@ "create": { "message": "إنشاء" }, - "createAccount": { - "message": "إنشاء حساب" - }, "createPassword": { "message": "إنشاء كلمة مرور" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "هل ترغب في إضافة هذه الرموز؟" }, - "lineatestnet": { + "lineaGoerli": { "message": "شبكة اختبار Linea Goerli" }, "links": { @@ -578,9 +569,6 @@ "readdToken": { "message": "يمكنك إضافة هذه العملة الرمزية مرة أخرى في المستقبل من خلال الانتقال إلى \"إضافة عملة رمزية\" في قائمة خيارات الحسابات الخاصة بك." }, - "recents": { - "message": "الحديث" - }, "recipientAddressPlaceholder": { "message": "البحث، العنوان العام (0x)، أو ENS" }, @@ -804,16 +792,9 @@ "transfer": { "message": "تحويل" }, - "transferBetweenAccounts": { - "message": "التحويل بين حساباتي" - }, "transferFrom": { "message": "التحويل من" }, - "troubleTokenBalances": { - "message": "واجهتنا مشكلة في تحميل أرصدتك من العملات الرمزية. يمكنك الاطلاع عليها", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "إعادة المحاولة" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index e4ce863f5..d361b9a1e 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Назад" }, - "backToAll": { - "message": "Назад към всички" - }, "backupApprovalInfo": { "message": "Този секретен код е необходим за възстановяване на портфейла ви, в случай че загубите устройството си, забравите паролата си, трябва да инсталирате отново MetaMask или искате да получите достъп до портфейла си на друго устройство." }, @@ -154,16 +151,13 @@ "connect": { "message": "Свързване" }, - "connectHardwareWallet": { - "message": "Свържете хардуерен портфейл" - }, "connectingTo": { "message": "Свързване с $1" }, "connectingToGoerli": { "message": "Свързване с тестова мрежа на Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Свързване с тестова мрежа на Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Създаване" }, - "createAccount": { - "message": "Създай акаунт" - }, "createPassword": { "message": "Създаване на парола" }, @@ -411,7 +402,7 @@ "likeToImportTokens": { "message": "Искате ли да добавите тези жетони?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Тестова мрежа на Linea Goerli" }, "links": { @@ -577,9 +568,6 @@ "readdToken": { "message": "Можете да добавите този жетон в бъдеще, като отидете на „Добавяне на жетон“ в менюто с опции на акаунти." }, - "recents": { - "message": "Скорошни" - }, "recipientAddressPlaceholder": { "message": "Търсене, публичен адрес (0x) или ENS" }, @@ -803,16 +791,9 @@ "transfer": { "message": "Трансфер" }, - "transferBetweenAccounts": { - "message": "Прехвърляне между моите акаунти" - }, "transferFrom": { "message": "Трансфер от" }, - "troubleTokenBalances": { - "message": "Имахме проблеми със зареждането на вашите символи. Можете да ги видите", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Нов опит" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index 6ba3e132d..63976b3fe 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -93,9 +93,6 @@ "back": { "message": "ফিরুন" }, - "backToAll": { - "message": "সমস্ততে ফিরে আসুন" - }, "backupApprovalInfo": { "message": "আপনি আপনার ডিভাইস হারিয়ে ফেললে, পাসওয়ার্ড ভুলে গেলে, MetaMask রিইনস্টল করতে হলে বা অন্য ডিভাইসে আপনার ওয়ালেট অ্যাক্সেস করার ক্ষেত্রে আপনার ওয়ালেট পুনরুদ্ধার করতে এই গোপন কোডটি প্রয়োজন।" }, @@ -154,9 +151,6 @@ "connect": { "message": "সংযুক্ত করুন" }, - "connectHardwareWallet": { - "message": "হার্ডওয়্যার ওয়ালেট সংযুক্ত করুন" - }, "connectingTo": { "message": " $1 এর সাথে সংযোগ করছে" }, @@ -190,9 +184,6 @@ "create": { "message": "তৈরি করুন" }, - "createAccount": { - "message": "অ্যাকাউন্ট তৈরি করুন" - }, "createPassword": { "message": "পাসওয়ার্ড তৈরি করুন" }, @@ -575,9 +566,6 @@ "readdToken": { "message": "আপনি আপনার অ্যাকাউন্টস বিকল্পের মেনুতে \"টোকেনগুলি যোগ করুন\" এ গিয়ে ভবিষ্যতে আবার এই টোকেনটি যোগ করতে পারবেন। " }, - "recents": { - "message": "সাম্প্রতিকগুলি" - }, "recipientAddressPlaceholder": { "message": "অনুসন্ধান, সার্বজনীন ঠিকানা (0x), বা ENS" }, @@ -801,16 +789,9 @@ "transfer": { "message": "ট্রান্সফার করুন" }, - "transferBetweenAccounts": { - "message": "আমার অ্যাকাউন্টগুলির মধ্যে ট্রান্সফার করুন" - }, "transferFrom": { "message": "থেকে ট্রান্সফার করুন" }, - "troubleTokenBalances": { - "message": "আপনার টোকেন ব্যালেন্সগুলি লোড করতে আমাদের সমস্যা হয়েছিল। আপনি সেগুলি দেখতে পারেন", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "আবার করুন" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index ab1da7841..b13885495 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Enrere" }, - "backToAll": { - "message": "Torna a Tot" - }, "backupApprovalInfo": { "message": "Aquest codi secret es requereix per recuperar el teu moneder en cas que perdis el dispositiu, oblidis la teva contrasenya, hagis de reinstal·lar MetaMask, o vulguis accedir al teu moneder des d'un altre dispositiu." }, @@ -151,16 +148,13 @@ "connect": { "message": "Connecta" }, - "connectHardwareWallet": { - "message": "Connectar Moneder Hardaware" - }, "connectingTo": { "message": "Connectant a $1 " }, "connectingToGoerli": { "message": "Connectant a Xarxa de Prova Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Connectant a Xarxa de Prova Linea Goerli" }, "connectingToMainnet": { @@ -190,9 +184,6 @@ "create": { "message": "Crea" }, - "createAccount": { - "message": "Crea " - }, "createPassword": { "message": "Crear Contrasenya" }, @@ -402,7 +393,7 @@ "likeToImportTokens": { "message": "T'agradaria afegir aquestes fitxes?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Xarxa de test Linea Goerli" }, "links": { @@ -779,16 +770,9 @@ "transfer": { "message": "Transferència" }, - "transferBetweenAccounts": { - "message": "Transferir entre els meus comptes" - }, "transferFrom": { "message": "Transferir Des de" }, - "troubleTokenBalances": { - "message": "Hem tingut problemes en carregar el teu saldo de fitxes. Els pots veure", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Torna-ho a provar" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 3b82640c9..2b6cb9a3f 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -81,9 +81,6 @@ "create": { "message": "Vytvořit" }, - "createAccount": { - "message": "Vytvořit účet" - }, "customToken": { "message": "Vlastní token" }, @@ -364,10 +361,6 @@ "transactionError": { "message": "Chyba transakce. Vyhozena výjimka v kódu kontraktu." }, - "troubleTokenBalances": { - "message": "Měli jsme problém s načtením vašich tokenových zůstatků. Můžete je vidět ", - "description": "Followed by a link (here) to view token balances" - }, "typePassword": { "message": "Zadejte své heslo" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 733b31dab..36c0b7528 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Tilbage" }, - "backToAll": { - "message": "Tilbage til Alle" - }, "backupApprovalInfo": { "message": "Den hemmelige kode er påkrævet for at gendanne din pung, hvis du mister din enhed, glemmer dit kodeord, er nødt til at geninstallere MetaMask eller ønsker adgang til din pung fra en anden enhed." }, @@ -154,16 +151,13 @@ "connect": { "message": "Få forbindelse" }, - "connectHardwareWallet": { - "message": "Forbind Hardwarepung" - }, "connectingTo": { "message": "Forbinder til $1" }, "connectingToGoerli": { "message": "Opretter forbindelse til Goerli Testnetværk" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Opretter forbindelse til Linea Goerli Testnetværk" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Opret" }, - "createAccount": { - "message": "Opret konto" - }, "createPassword": { "message": "Opret adgangskode" }, @@ -408,7 +399,7 @@ "likeToImportTokens": { "message": "Ønsker du at tilføje disse tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea-testnetværk" }, "loadMore": { @@ -562,9 +553,6 @@ "readdToken": { "message": "Du kan tilføje denne token i fremtiden, ved at gå til \"Tilføj token\" under dine valgmenuen for dine konti." }, - "recents": { - "message": "Seneste" - }, "recipientAddressPlaceholder": { "message": "Søg, offentlig adresse (0x) eller ENS" }, @@ -776,16 +764,9 @@ "transfer": { "message": "Overfør" }, - "transferBetweenAccounts": { - "message": "Overfør mellem konti" - }, "transferFrom": { "message": "Overfør fra" }, - "troubleTokenBalances": { - "message": "Vi havde problemer med at indlæse dine tokenbalancer. Du kan se dem", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Prøv igen" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index e2188a54b..db6d09d94 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Kunden-Token hinzufügen" }, - "addCustomTokenByContractAddress": { - "message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Vertragsadressen finden Sie auf $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Dadurch kann dieses Netzwerk innerhalb MetaMask verwendet werden." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Token hinzufügen" }, + "addTokenByContractAddress": { + "message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Vertragsadressen finden Sie auf $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Adresse" }, @@ -359,12 +359,6 @@ "message": "Erlauben Sie den Zugriff auf und die Übertragung von all Ihren $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Genehmigen und installieren" - }, - "approveAndUpdate": { - "message": "Genehmigen & aktualisieren" - }, "approveButtonText": { "message": "Genehmigen" }, @@ -429,9 +423,6 @@ "back": { "message": "Zurück" }, - "backToAll": { - "message": "Zurück zur Übersicht" - }, "backup": { "message": "Datensicherung" }, @@ -557,13 +548,6 @@ "message": "Für eine Transaktion im Wert von $1 muss die Gasgebühr um mindestens 10 % erhöht werden, damit sie vom Netz erkannt wird.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Tausch für ~$1 abbrechen", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Tausch kostenlos abbrechen" - }, "cancelled": { "message": "Abgebrochen" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Konto verbinden oder Neues erstellen" }, - "connectHardwareWallet": { - "message": "Hardware-Wallet verknüpfen" - }, "connectManually": { "message": "Manuelle Verbindung zur aktuellen Site" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Verbindungsaufbau zum Goerli-Testnetzwerk" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Verbindungsaufbau zum Linea-Testnetzwerk" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Erstellen" }, - "createAccount": { - "message": "Konto erstellen" - }, "createNewWallet": { "message": "Eine neue Wallet erstellen" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Verwenden Sie die OpenSea's API, um NFT-Daten abzurufen. Die NFT-Auto-Erkennung basiert auf der OpenSea's API und wird nicht verfügbar sein, wenn diese deaktiviert ist." }, - "enableSmartTransactions": { - "message": "Intelligente Transaktionen ermöglichen" - }, "enableToken": { "message": "$1 aktivieren", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Jeder kann ein Token mit beliebigem Namen erstellen, einschließlich gefälschter Versionen bestehender Token. Hinzufügen und Handeln auf eigene Gefahr!" }, - "importTokens": { - "message": "Token importieren" - }, "importTokensCamelCase": { "message": "Token importieren" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Möchtest du diese Token hinzufügen?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea-Testnetzwerk" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Hier anfragen" }, - "missingToken": { - "message": "Sie sehen Ihren Token nicht?" - }, "moreComingSoon": { "message": "Mehr in Kürze ..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Nicht genügend Gas" }, - "notifications": { - "message": "Benachrichtigungen" - }, "notifications10ActionText": { "message": "Einstellungen ansehen", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2309,7 +2275,7 @@ "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." }, "notifications3Description": { - "message": "Bleiben Sie auf dem Laufenden bezüglich der bewährten Sicherheitsverfahren von MetaMask und erhalten Sie die neuesten Sicherheitstipps des offiziellen Metamask-Supports.", + "message": "Bleiben Sie auf dem Laufenden bezüglich der bewährten Sicherheitsverfahren von MetaMask und erhalten Sie die neuesten Sicherheitstipps des offiziellen MetaMask-Supports.", "description": "Description of a notification in the 'See What's New' popup. Describes the information they can get on security from the linked support page." }, "notifications3Title": { @@ -2817,9 +2783,6 @@ "receive": { "message": "Erhalten" }, - "recents": { - "message": "Letzte" - }, "recipientAddressPlaceholder": { "message": "Suchen, öffentliche Adresse (0x) oder ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Wählen Sie dies aus, um das Hexdatenfeld auf dem Senden-Bildschirm anzuzeigen" }, - "showHide": { - "message": "Ein-/Ausblenden" - }, "showIncomingTransactions": { "message": "Eingehende Transaktionen anzeigen" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Mir ist klar, dass ich meine Konten und alle dazugehörigen Vermögenswerte verlieren kann, solange ich keine Sicherungskopie meiner Geheimen Wiederherstellungsphrase erstelle." }, - "smartTransaction": { - "message": "Intelligente Transaktionen" - }, "snapContent": { "message": "Diese Inhalte stammen von $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Snap installieren" }, "snapInstallWarningCheck": { - "message": "Um zu bestätigen, dass Sie das verstanden haben, markieren Sie das Kästchen." + "message": "Um zu bestätigen, dass Sie das verstanden haben, markieren Sie das Kästchen.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Um zu bestätigen, dass Sie alles verstanden haben, markieren Sie alle Kästchen." + "message": "Um zu bestätigen, dass Sie alles verstanden haben, markieren Sie alle Kästchen.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Sie gewähren dem Snap „$1“ wichtige $2-Zugriffsrechte. Dies kann nicht rückgängig gemacht werden und gibt „$1“ Kontrolle über Ihre $2-Konten und Vermögenswerte. Stellen Sie sicher, dass Sie „$1“ vertrauen, bevor Sie fortfahren.", @@ -3416,9 +3375,6 @@ "status": { "message": "Status" }, - "statusConnected": { - "message": "Verbinden" - }, "statusNotConnected": { "message": "Nicht verbunden" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Stark" }, - "stxAreHere": { - "message": "Intelligente Transaktionen sind da!" - }, "stxBenefit1": { "message": "Transaktionskosten minimieren" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Versuchen Sie Ihren Swap erneut. Wir werden hier sein, um Sie beim nächsten Mal vor ähnlichen Risiken zu schützen." }, - "stxDescription": { - "message": "MetaMask-Swap ist jetzt viel intelligenter geworden! Wenn Sie intelligente Transaktionen aktivieren, kann MetaMask Ihren Swap programmgesteuert optimieren, um Ihnen zu helfen:" - }, - "stxErrorNotEnoughFunds": { - "message": "Nicht genügend Mittel für eine intelligente Transaktion." - }, - "stxErrorUnavailable": { - "message": "Intelligente Transaktionen sind vorübergehend nicht verfügbar." - }, "stxFailure": { "message": "Swap fehlgeschlagen" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Ihr Swap wird öffentlich eingereicht ..." }, - "stxSubDescription": { - "message": "* Intelligente Transaktionen versuchen mehrmals, Ihre Transaktion privat zu übermitteln. Wenn alle Versuche fehlschlagen, wird die Transaktion öffentlich übertragen, um sicherzustellen, dass Ihr Swap erfolgreich durchgeführt wird." - }, "stxSuccess": { "message": "Swap abgeschlossen!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "eth_sign-Anfragen ein- oder ausschalten" }, - "toggleTestNetworks": { - "message": "$1 Test-Netzwerke", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Überweisung" }, - "transferBetweenAccounts": { - "message": "Zwischen meinen Konten transferieren" - }, "transferFrom": { "message": "Transferieren von" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "Beim Starten von MetaMask ist ein Problem aufgetreten. Dies könnte ein vorübergehendes Problem sein. Versuchen Sie daher, die Erweiterung neu zu starten." }, - "troubleTokenBalances": { - "message": "Wir haben Schwierigkeiten dein Tokenguthaben zu laden. Du kannst es hier anzeigen lassen", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Durch Erteilung der Erlaubnis erlauben Sie den folgenden $1 Zugriff auf Ihr Guthaben" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 6e7732aa3..9021a3098 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Προσθήκη Προσαρμοσμένου Token" }, - "addCustomTokenByContractAddress": { - "message": "Αδυναμία εύρεσης token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε διακριτικό επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων Token μπορούν να βρεθούν στο $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Αυτό θα επιτρέψει σε αυτό το δίκτυο να χρησιμοποιηθεί στο MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Προσθήκη Token" }, + "addTokenByContractAddress": { + "message": "Αδυναμία εύρεσης token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε διακριτικό επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων Token μπορούν να βρεθούν στο $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Διεύθυνση" }, @@ -359,12 +359,6 @@ "message": "Δίνετε άδεια για να αποκτήσετε πρόσβαση σε όλα σας τα $1;", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Έγκριση και Εγκατάσταση" - }, - "approveAndUpdate": { - "message": "Έγκριση και ενημέρωση" - }, "approveButtonText": { "message": "Έγκριση" }, @@ -429,9 +423,6 @@ "back": { "message": "Πίσω" }, - "backToAll": { - "message": "Πίσω σε Όλα" - }, "backup": { "message": "Αντίγραφο ασφαλείας" }, @@ -557,13 +548,6 @@ "message": "Για να $1 τη συναλλαγή, τα τέλη συναλλαγής πρέπει να αυξηθούν κατά τουλάχιστον 10% ώστε να αναγνωριστούν από το δίκτυο.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Ακυρώστε τη συναλλαγή για ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Ακυρώστε τη συναλλαγή δωρεάν" - }, "cancelled": { "message": "Ακυρώθηκε" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Σύνδεση λογαριασμού ή δημιουργία νέου" }, - "connectHardwareWallet": { - "message": "Σύνδεση Πορτοφολιού Υλικού" - }, "connectManually": { "message": "Χειροκίνητη σύνδεση στον τρέχοντα ιστότοπο" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Σύνδεση στο Δίκτυο Δοκιμής Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Σύνδεση στο δίκτυο δοκιμών Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Δημιουργία" }, - "createAccount": { - "message": "Δημιουργία Λογαριασμού" - }, "createNewWallet": { "message": "Δημιουργήστε ένα νέο πορτοφόλι" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Χρησιμοποιήστε το API OpenSea για λήψη δεδομένων NFT. Η αυτόματη ανίχνευση NFT βασίζεται στο API του OpenSea, και δεν θα είναι διαθέσιμη όταν αυτό είναι απενεργοποιημένο." }, - "enableSmartTransactions": { - "message": "Ενεργοποίηση Έξυπνων Συναλλαγών" - }, "enableToken": { "message": "ενεργοποίηση $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Ο καθένας μπορεί να δημιουργήσει ένα token με οποιοδήποτε όνομα, συμπεριλαμβανομένων ψεύτικων εκδόσεων των υφιστάμενων νομισμάτων. Προσθέστε και ανταλλάξτε με δική σας ευθύνη!" }, - "importTokens": { - "message": "εισαγωγή token" - }, "importTokensCamelCase": { "message": "Εισαγωγή Token" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Θέλετε να προσθέσετε αυτά τα token;" }, - "lineatestnet": { + "lineaGoerli": { "message": "Δίκτυο δοκιμών Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Υποβάλετε αίτημα εδώ" }, - "missingToken": { - "message": "Δεν βλέπετε το token σας;" - }, "moreComingSoon": { "message": "Περισσότερα έρχονται σύντομα..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Δεν Υπάρχει Αρκετό τέλος συναλλαγής" }, - "notifications": { - "message": "Ειδοποιήσεις" - }, "notifications10ActionText": { "message": "Μετάβαση στις Ρυθμίσεις", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Λήψη" }, - "recents": { - "message": "Πρόσφατα" - }, "recipientAddressPlaceholder": { "message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS" }, @@ -3211,9 +3174,6 @@ "showHexDataDescription": { "message": "Επιλέξτε αυτό για να εμφανίσετε το πεδίο hex δεδομένων στην οθόνη αποστολής" }, - "showHide": { - "message": "Εμφάνιση/απόκρυψη" - }, "showIncomingTransactions": { "message": "Εμφάνιση Εισερχομένων Συναλλαγών" }, @@ -3266,9 +3226,6 @@ "skipAccountSecurityDetails": { "message": "Καταλαβαίνω ότι μέχρι να δημιουργήσω αντίγραφα ασφαλείας για τη Μυστική Φράση Ανάκτησής μου, μπορεί να χάσω τους λογαριασμούς μου και όλα τα περιουσιακά στοιχεία τους." }, - "smartTransaction": { - "message": "Έξυπνη Συναλλαγή" - }, "snapContent": { "message": "Αυτό το περιεχόμενο προέρχεται από το $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3281,10 +3238,12 @@ "message": "Εγκατάσταση Snap" }, "snapInstallWarningCheck": { - "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα πλαίσια ελέγχου." + "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα πλαίσια ελέγχου.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα κουτάκια." + "message": "Για να επιβεβαιώσετε ότι καταλαβαίνετε, επιλέξτε όλα τα κουτάκια.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Εκχωρείτε στο $2 βασική πρόσβαση στο snap \"$1\". Αυτό είναι αμετάκλητο και παρέχει στο \"$1\" τον έλεγχο των λογαριασμών και των περιουσιακών σας στοιχείων $2. Βεβαιωθείτε ότι εμπιστεύεστε το \"$1\" προτού συνεχίσετε.", @@ -3413,9 +3372,6 @@ "status": { "message": "Κατάσταση" }, - "statusConnected": { - "message": "Συνδεδεμένο" - }, "statusNotConnected": { "message": "Δεν έχει συνδεθεί" }, @@ -3453,9 +3409,6 @@ "strong": { "message": "Ισχυρός" }, - "stxAreHere": { - "message": "Οι Έξυπνες Συναλλαγές είναι εδώ!" - }, "stxBenefit1": { "message": "Ελαχιστοποίηση του κόστους συναλλαγής" }, @@ -3477,15 +3430,6 @@ "stxCancelledSubDescription": { "message": "Προσπαθήστε ξανά να κάνετε ανταλλαγή. Θα είμαστε εδώ για να σας προστατεύσουμε από παρόμοιους κινδύνους και την επόμενη φορά." }, - "stxDescription": { - "message": "Οι Ανταλλαγές MetaMask μόλις έγιναν πολύ πιο έξυπνες! Η ενεργοποίηση των Έξυπνων Συναλλαγών θα επιτρέψει στο MetaMask να βελτιώσει προγραμματικά τις Ανταλλαγές σας ώστε να απολαμβάνετε:" - }, - "stxErrorNotEnoughFunds": { - "message": "Δεν υπάρχουν αρκετοί πόροι για αυτή την έξυπνη συναλλαγή." - }, - "stxErrorUnavailable": { - "message": "Οι Έξυπνες Συναλλαγές είναι προσωρινά μη διαθέσιμες." - }, "stxFailure": { "message": "Η Ανταλλαγή απέτυχε" }, @@ -3499,9 +3443,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Δημόσια υποβολή της Ανταλλαγής σας..." }, - "stxSubDescription": { - "message": "* Οι Έξυπνες Συναλλαγές θα προσπαθήσουν να υποβάλουν τη συναλλαγή σας ιδιωτικά, πολλές φορές. Εάν όλες οι προσπάθειες αποτύχουν, η συναλλαγή θα μεταδοθεί δημόσια για να διασφαλιστεί η επιτυχής πραγματοποίηση της ανταλλαγής σας." - }, "stxSuccess": { "message": "Η ανταλλαγή ολοκληρώθηκε!" }, @@ -3920,10 +3861,6 @@ "toggleEthSignField": { "message": "Εναλλαγή αιτημάτων eth_sign" }, - "toggleTestNetworks": { - "message": "$1 δοκιμαστικά δίκτυα", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Διακριτικό" }, @@ -4090,9 +4027,6 @@ "transfer": { "message": "Μεταφορά" }, - "transferBetweenAccounts": { - "message": "Μεταφορά μεταξύ λογαριασμών μου" - }, "transferFrom": { "message": "Μεταφορά Από" }, @@ -4103,10 +4037,6 @@ "troubleStarting": { "message": "Το MetaMask αντιμετώπισε πρόβλημα κατά την εκκίνηση. Αυτό το σφάλμα μπορεί να είναι τυχαίο, γι' αυτό προσπαθήστε να επανεκκινήσετε την επέκταση." }, - "troubleTokenBalances": { - "message": "Είχαμε πρόβλημα να φορτώσουμε τα υπόλοιπα του διακριτικού σας. Μπορείτε να τα δείτε ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Χορηγώντας άδεια, επιτρέπετε στα ακόλουθα $1 να έχουν πρόσβαση στα χρήματά σας" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 91bad4b6d..cc1803255 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -204,10 +204,6 @@ "addCustomToken": { "message": "Add custom token" }, - "addCustomTokenByContractAddress": { - "message": "Can’t find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "This will allow this network to be used within MetaMask." }, @@ -269,12 +265,25 @@ "addNewToken": { "message": "Add new token" }, + "addNft": { + "message": "Add NFT" + }, + "addNfts": { + "message": "Add NFTs" + }, + "addSuggestedNFTs": { + "message": "Add suggested NFTs" + }, "addSuggestedTokens": { "message": "Add suggested tokens" }, "addToken": { "message": "Add token" }, + "addTokenByContractAddress": { + "message": "Can’t find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "addingCustomNetwork": { "message": "Adding Network" }, @@ -306,7 +315,7 @@ "message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction." }, "agreeTermsOfUse": { - "message": "I agree to Metamask's $1", + "message": "I agree to MetaMask's $1", "description": "$1 is the `terms` link" }, "airgapVault": { @@ -398,12 +407,6 @@ "message": "Allow access to and transfer all of your NFTs from $1?", "description": "$1 a link to contract on the block explorer when we're not able to retrieve a erc721 or erc1155 name" }, - "approveAndInstall": { - "message": "Approve & install" - }, - "approveAndUpdate": { - "message": "Approve & update" - }, "approveButtonText": { "message": "Approve" }, @@ -444,6 +447,10 @@ "attemptSendingAssets": { "message": "If you attempt to send assets directly from one network to another, this may result in permanent asset loss. Make sure to use a bridge." }, + "attemptToCancelSwap": { + "message": "Attempt to cancel swap for ~$1", + "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap" + }, "attemptingConnect": { "message": "Attempting to connect to blockchain." }, @@ -475,9 +482,6 @@ "back": { "message": "Back" }, - "backToAll": { - "message": "Back to all" - }, "backup": { "message": "Backup" }, @@ -609,13 +613,6 @@ "message": "To $1 a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Cancel swap for ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Cancel swap for free" - }, "cancelled": { "message": "Cancelled" }, @@ -654,6 +651,9 @@ "clearActivityDescription": { "message": "This resets the account's nonce and erases data from the activity tab in your wallet. Only the current account and network will be affected. Your balances and incoming transactions won't change." }, + "click": { + "message": "Click" + }, "clickToConnectLedgerViaWebHID": { "message": "Click here to connect your Ledger via WebHID", "description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid" @@ -742,12 +742,13 @@ "connectCustodialAccountTitle": { "message": "Custodial Accounts" }, - "connectHardwareWallet": { - "message": "Connect hardware wallet" - }, "connectManually": { "message": "Manually connect to current site" }, + "connectSnap": { + "message": "Connect $1", + "description": "$1 is the snap for which a connection is being requested." + }, "connectTo": { "message": "Connect to $1", "description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask" @@ -801,7 +802,7 @@ "connectingToGoerli": { "message": "Connecting to Goerli test network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Connecting to Linea Goerli test network" }, "connectingToMainnet": { @@ -813,6 +814,16 @@ "connectionError": { "message": "Connection error" }, + "connectionFailed": { + "message": "Connection failed" + }, + "connectionFailedDescription": { + "message": "Fetching of $1 failed, check your network and try again.", + "description": "$1 is the name of the snap being fetched." + }, + "connectionRequest": { + "message": "Connection request" + }, "contactUs": { "message": "Contact us" }, @@ -892,9 +903,6 @@ "create": { "message": "Create" }, - "createAccount": { - "message": "Create account" - }, "createNewWallet": { "message": "Create a new wallet" }, @@ -1033,6 +1041,12 @@ "message": "$1 has suggested this price.", "description": "$1 is url for the dapp that has suggested gas settings" }, + "dappSuggestedHigh": { + "message": "Site suggested" + }, + "dappSuggestedHighShortLabel": { + "message": "Site (high)" + }, "dappSuggestedShortLabel": { "message": "Site" }, @@ -1370,8 +1384,8 @@ "enableOpenSeaAPIDescription": { "message": "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off." }, - "enableSmartTransactions": { - "message": "Enable smart transactions" + "enableSmartSwaps": { + "message": "Enable smart swaps" }, "enableSnap": { "message": "Enable" @@ -1380,6 +1394,9 @@ "message": "enable $1", "description": "$1 is a token symbol, e.g. ETH" }, + "enabled": { + "message": "Enabled" + }, "encryptionPublicKeyNotice": { "message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.", "description": "$1 is the web3 site name" @@ -1427,6 +1444,9 @@ "enterPasswordContinue": { "message": "Enter password to continue" }, + "enterTokenNameOrAddress": { + "message": "Enter token name or paste address" + }, "enterYourPassword": { "message": "Enter your password" }, @@ -1791,7 +1811,9 @@ "message": "full access to your wallet and funds.", "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" }, - "holdToRevealLockedLabel": { "message": "hold to reveal circle locked" }, + "holdToRevealLockedLabel": { + "message": "hold to reveal circle locked" + }, "holdToRevealPrivateKey": { "message": "Hold to reveal Private Key" }, @@ -1804,7 +1826,9 @@ "holdToRevealSRPTitle": { "message": "Keep your SRP safe" }, - "holdToRevealUnlockedLabel": { "message": "hold to reveal circle unlocked" }, + "holdToRevealUnlockedLabel": { + "message": "hold to reveal circle unlocked" + }, "id": { "message": "Id" }, @@ -1872,9 +1896,6 @@ "importTokenWarning": { "message": "Anyone can create a token with any name, including fake versions of existing tokens. Add and trade at your own risk!" }, - "importTokens": { - "message": "import tokens" - }, "importTokensCamelCase": { "message": "Import tokens" }, @@ -2114,7 +2135,7 @@ "likeToImportTokens": { "message": "Would you like to import these tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli test network" }, "link": { @@ -2255,9 +2276,6 @@ "missingSettingRequest": { "message": "Request here" }, - "missingToken": { - "message": "Don't see your token?" - }, "mmiAddToken": { "message": "The page at $1 would like to authorise the following custodian token in MetaMask Institutional" }, @@ -2270,6 +2288,10 @@ "moreComingSoon": { "message": "More coming soon..." }, + "multipleSnapConnectionWarning": { + "message": "$1 wants to connect with $2 snaps. Only proceed if you trust this website.", + "description": "$1 is the dapp and $2 is the number of snaps it wants to connect to." + }, "mustSelectOne": { "message": "Must select at least 1 token." }, @@ -2522,6 +2544,9 @@ "notCurrentAccount": { "message": "Is this the correct account? It's different from the currently selected account in your wallet" }, + "notEnoughBalance": { + "message": "Insufficient balance" + }, "notEnoughGas": { "message": "Not enough gas" }, @@ -2648,6 +2673,16 @@ "message": "Ledger and Firefox Users Experiencing Connection Issues", "description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues." }, + "notifications21ActionText": { + "message": "Try it out" + }, + "notifications21Description": { + "message": "We've updated Swaps in the MetaMask extension to be easier and faster to use.", + "description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature." + }, + "notifications21Title": { + "message": "Introducing new and refreshed Swaps!" + }, "notifications3ActionText": { "message": "Read more", "description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website." @@ -2991,6 +3026,9 @@ "message": "You have (1) pending transaction.", "description": "$1 is count of pending transactions" }, + "percentage": { + "message": "$1%" + }, "permissionRequest": { "message": "Permission request" }, @@ -3248,6 +3286,9 @@ "queued": { "message": "Queued" }, + "quoteRate": { + "message": "Quote rate" + }, "reAddAccounts": { "message": "re-add any other accounts" }, @@ -3260,11 +3301,8 @@ "receive": { "message": "Receive" }, - "recents": { - "message": "Recents" - }, "recipientAddressPlaceholder": { - "message": "Search, public address (0x), or ENS" + "message": "Enter public address (0x) or ENS name" }, "recommendedGasLabel": { "message": "Recommended" @@ -3360,9 +3398,6 @@ "reportLastRunTooltip": { "message": "The date and time of when the last AML/CFT report was run" }, - "requestFailed": { - "message": "Request failed" - }, "requestFlaggedAsMaliciousFallbackCopyReason": { "message": "The security provider has not shared additional details" }, @@ -3708,9 +3743,6 @@ "showHexDataDescription": { "message": "Select this to show the hex data field on the send screen" }, - "showHide": { - "message": "Show/hide" - }, "showIncomingTransactions": { "message": "Show incoming transactions" }, @@ -3772,8 +3804,27 @@ "skipAccountSecurityDetails": { "message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets." }, - "smartTransaction": { - "message": "Smart transaction" + "smartSwap": { + "message": "Smart swap" + }, + "smartSwapsAreHere": { + "message": "Smart Swaps are here!" + }, + "smartSwapsDescription": { + "message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Swaps will allow MetaMask to programmatically optimize your Swap to help:" + }, + "smartSwapsErrorNotEnoughFunds": { + "message": "Not enough funds for a smart swap." + }, + "smartSwapsErrorUnavailable": { + "message": "Smart Swaps are temporarily unavailable." + }, + "smartSwapsSubDescription": { + "message": "* Smart Swaps will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through." + }, + "snapConnectionWarning": { + "message": "$1 wants to connect to $2. Only continue if you trust this website.", + "description": "$2 is the snap and $1 is the dapp requesting connection to the snap." }, "snapContent": { "message": "This content is coming from $1", @@ -3787,30 +3838,39 @@ "message": "Install snap" }, "snapInstallRequest": { - "message": "$1 wants to install $2. Make sure you trust the authors before you proceed.", - "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + "message": "Installing $1 gives it the following permissions. Only continue if you trust $1.", + "description": "$1 is the snap name." }, - "snapInstallRequestsPermission": { - "message": "$1 wants to install $2, which is requesting the following permissions. Make sure you trust the authors before you proceed.", - "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + "snapInstallSuccess": { + "message": "Installation complete" }, "snapInstallWarningCheck": { - "message": "Ensure that the permission below aligns with your intended actions. Only proceed with authors you trust." + "message": "Take a moment to review the permission being requested. Only continue if you trust $1.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Ensure that the permissions below align with your intended actions. Only proceed with authors you trust." + "message": "Take a moment to review the permissions being requested. Only continue if you trust $1.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningHeading": { "message": "Proceed with caution" }, "snapInstallWarningKeyAccess": { - "message": "Grant $2 account control to $1", + "message": "Give $2 account control to $1", "description": "The first parameter is the name of the snap and the second one is the protocol" }, "snapInstallWarningPublicKeyAccess": { - "message": "Grant $2 public key access to $1", + "message": "Give $2 public key access to $1", "description": "The first parameter is the name of the snap and the second one is the protocol" }, + "snapInstallationErrorDescription": { + "message": "$1 couldn’t be installed.", + "description": "Error description used when snap installation fails. $1 is the snap name." + }, + "snapInstallationErrorTitle": { + "message": "Installation failed", + "description": "Error title used when snap installation fails." + }, "snapResultError": { "message": "Error" }, @@ -3818,18 +3878,25 @@ "message": "Success" }, "snapResultSuccessDescription": { - "message": "$1 is now available to use." + "message": "$1 is ready to use" }, "snapUpdate": { "message": "Update snap" }, - "snapUpdateRequest": { - "message": "$1 wants to update $2. Make sure you trust the authors before you proceed.", - "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + "snapUpdateErrorDescription": { + "message": "$1 couldn’t be updated.", + "description": "Error description used when snap update fails. $1 is the snap name." }, - "snapUpdateRequestsPermission": { - "message": "$1 wants to update $2, which is requesting the following permissions. Make sure you trust the authors before you proceed.", - "description": "$1 is the dApp origin requesting the snap and $2 is the snap name" + "snapUpdateErrorTitle": { + "message": "Update failed", + "description": "Error title used when snap update fails." + }, + "snapUpdateRequest": { + "message": "$1 wants to update $2 to $3 which gives it the following permissions. Only continue if you trust $2.", + "description": "$1 is the dApp origin requesting the snap, $2 is the snap name and $3 is the snap version." + }, + "snapUpdateSuccess": { + "message": "Update complete" }, "snaps": { "message": "Snaps" @@ -3843,9 +3910,24 @@ "snapsNoInsight": { "message": "The snap didn't return any insight" }, + "snapsPrivacyWarningFirstMessage": { + "message": "You acknowledge that the snap that you are about to install is a Third Party Service as defined in the Consensys $1. Your use of Third Party Services is governed by separate terms and conditions set forth by the Third Party Service provider. You access, rely upon or use the Third Party Service at your own risk. Consensys disclaims all responsibility and liability for any losses on account of your use of Third Party Services.", + "description": "First part of a message in popup modal displayed when installing a snap for the first time. $1 is terms of use link." + }, + "snapsPrivacyWarningSecondMessage": { + "message": "Any information you share with Third Party Services will be collected directly by those Third Party Services in accordance with their privacy policies. Please refer to their privacy policies for more information.", + "description": "Second part of a message in popup modal displayed when installing a snap for the first time." + }, + "snapsPrivacyWarningThirdMessage": { + "message": "Consensys has no access to information you share with these third parties.", + "description": "Third part of a message in popup modal displayed when installing a snap for the first time." + }, "snapsSettingsDescription": { "message": "Manage your Snaps" }, + "snapsTermsOfUse": { + "message": "Terms of Use" + }, "snapsToggle": { "message": "A snap will only run if it is enabled" }, @@ -3957,9 +4039,6 @@ "status": { "message": "Status" }, - "statusConnected": { - "message": "Connected" - }, "statusNotConnected": { "message": "Not connected" }, @@ -4000,9 +4079,6 @@ "strong": { "message": "Strong" }, - "stxAreHere": { - "message": "Smart Transactions are here!" - }, "stxBenefit1": { "message": "Minimize transaction costs" }, @@ -4024,15 +4100,6 @@ "stxCancelledSubDescription": { "message": "Try your swap again. We’ll be here to protect you against similar risks next time." }, - "stxDescription": { - "message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Transactions will allow MetaMask to programmatically optimize your Swap to help:" - }, - "stxErrorNotEnoughFunds": { - "message": "Not enough funds for a smart transaction." - }, - "stxErrorUnavailable": { - "message": "Smart Transactions are temporarily unavailable." - }, "stxFailure": { "message": "Swap failed" }, @@ -4046,9 +4113,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Publicly submitting your Swap..." }, - "stxSubDescription": { - "message": "* Smart Transactions will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through." - }, "stxSuccess": { "message": "Swap complete!" }, @@ -4109,6 +4173,9 @@ "swapAmountReceivedInfo": { "message": "This is the minimum amount you will receive. You may receive more depending on slippage." }, + "swapAnyway": { + "message": "Swap anyway" + }, "swapApproval": { "message": "Approve $1 for swaps", "description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved." @@ -4117,6 +4184,12 @@ "message": "You need $1 more $2 to complete this swap", "description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol." }, + "swapAreYouStillThere": { + "message": "Are you still there?" + }, + "swapAreYouStillThereDescription": { + "message": "We’re ready to show you the latest quotes when you want to continue" + }, "swapBuildQuotePlaceHolderText": { "message": "No tokens available matching $1", "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" @@ -4124,6 +4197,9 @@ "swapConfirmWithHwWallet": { "message": "Confirm with your hardware wallet" }, + "swapContinueSwapping": { + "message": "Continue swapping" + }, "swapContractDataDisabledErrorDescription": { "message": "In the Ethereum app on your Ledger, go to \"Settings\" and allow contract data. Then, try your swap again." }, @@ -4142,6 +4218,9 @@ "swapEditLimit": { "message": "Edit limit" }, + "swapEditTransactionSettings": { + "message": "Edit transaction settings" + }, "swapEnableDescription": { "message": "This is required and gives MetaMask permission to swap your $1.", "description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps." @@ -4150,6 +4229,9 @@ "message": "This will $1 for swapping", "description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'" }, + "swapEnterAmount": { + "message": "Enter an amount" + }, "swapEstimatedNetworkFees": { "message": "Estimated network fees" }, @@ -4163,6 +4245,9 @@ "swapFailedErrorTitle": { "message": "Swap failed" }, + "swapFetchingQuote": { + "message": "Fetching quote" + }, "swapFetchingQuoteNofN": { "message": "Fetching quote $1 of $2", "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." @@ -4203,6 +4288,9 @@ "message": "Includes a $1% MetaMask fee.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, + "swapLearnMore": { + "message": "Learn more about Swaps" + }, "swapLowSlippageError": { "message": "Transaction may fail, max slippage too low." }, @@ -4224,6 +4312,10 @@ "message": "New quotes in $1", "description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00" }, + "swapNoTokensAvailable": { + "message": "No tokens available matching $1", + "description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text" + }, "swapOnceTransactionHasProcess": { "message": "Your $1 will be added to your account once this transaction has processed.", "description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol." @@ -4251,6 +4343,10 @@ "swapQuoteDetails": { "message": "Quote details" }, + "swapQuoteNofM": { + "message": "$1 of $2", + "description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap." + }, "swapQuoteSource": { "message": "Quote source" }, @@ -4260,6 +4356,9 @@ "swapQuotesExpiredErrorTitle": { "message": "Quotes timeout" }, + "swapQuotesNotAvailableDescription": { + "message": "Reduce the size of your trade or use a different token." + }, "swapQuotesNotAvailableErrorDescription": { "message": "Try adjusting the amount or slippage settings and try again." }, @@ -4296,16 +4395,52 @@ "swapSelectQuotePopoverDescription": { "message": "Below are all the quotes gathered from multiple liquidity sources." }, + "swapSelectToken": { + "message": "Select token" + }, + "swapShowLatestQuotes": { + "message": "Show latest quotes" + }, "swapSlippageNegative": { "message": "Slippage must be greater or equal to zero" }, + "swapSlippageNegativeDescription": { + "message": "Slippage must be greater or equal to zero" + }, + "swapSlippageNegativeTitle": { + "message": "Increase slippage to continue" + }, + "swapSlippageOverLimitDescription": { + "message": "Slippage tolerance must be 15% or less. Anything higher will result in a bad rate." + }, + "swapSlippageOverLimitTitle": { + "message": "Reduce slippage to continue" + }, "swapSlippagePercent": { "message": "$1%", "description": "$1 is the amount of % for slippage" }, + "swapSlippageTooLowDescription": { + "message": "Max slippage is too low which may cause your transaction to fail." + }, + "swapSlippageTooLowTitle": { + "message": "Increase slippage to avoid transaction failure" + }, "swapSlippageTooltip": { "message": "If the price changes between the time your order is placed and confirmed it’s called “slippage”. Your swap will automatically cancel if slippage exceeds your “slippage tolerance” setting." }, + "swapSlippageVeryHighDescription": { + "message": "The slippage entered is considered very high and may result in a bad rate" + }, + "swapSlippageVeryHighTitle": { + "message": "Very high slippage" + }, + "swapSlippageZeroDescription": { + "message": "There are fewer zero-slippage quote providers which will result in a less competitive quote." + }, + "swapSlippageZeroTitle": { + "message": "Sourcing zero-slippage providers" + }, "swapSource": { "message": "Liquidity source" }, @@ -4322,7 +4457,7 @@ "message": "Swap from" }, "swapSwapSwitch": { - "message": "Switch from and to tokens" + "message": "Switch token order" }, "swapSwapTo": { "message": "Swap to" @@ -4330,6 +4465,13 @@ "swapToConfirmWithHwWallet": { "message": "to confirm with your hardware wallet" }, + "swapTokenAddedManuallyDescription": { + "message": "Verify this token on $1 and make sure it is the token you want to trade.", + "description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenAddedManuallyTitle": { + "message": "Token added manually" + }, "swapTokenAvailable": { "message": "Your $1 has been added to your account.", "description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol." @@ -4356,6 +4498,13 @@ "message": "Verified on $1 sources.", "description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number." }, + "swapTokenVerifiedOn1SourceDescription": { + "message": "$1 is only verified on 1 source. Consider verifying it on $2 before proceeding.", + "description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\"" + }, + "swapTokenVerifiedOn1SourceTitle": { + "message": "Potentially inauthentic token" + }, "swapTooManyDecimalsError": { "message": "$1 allows up to $2 decimals", "description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token" @@ -4393,9 +4542,16 @@ "message": "Not enough $1 to complete this transaction", "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" }, + "swapsNotEnoughToken": { + "message": "Not enough $1", + "description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol" + }, "swapsViewInActivity": { "message": "View in activity" }, + "switch": { + "message": "Switch" + }, "switchEthereumChainConfirmationDescription": { "message": "This will switch the selected network within MetaMask to a previously added network:" }, @@ -4466,6 +4622,10 @@ "thingsToKeep": { "message": "Things to keep in mind:" }, + "thirdPartySoftware": { + "message": "Third-party software notice", + "description": "Title of a popup modal displayed when installing a snap for the first time." + }, "thisCollection": { "message": "this collection" }, @@ -4525,10 +4685,6 @@ "toggleEthSignOn": { "message": "ON (Not recommended)" }, - "toggleTestNetworks": { - "message": "$1 test networks", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4698,6 +4854,9 @@ "transactionSecurityCheckDescription": { "message": "We use third-party APIs to detect and display risks involved in unsigned transaction and signature requests before you sign them. These services will have access to your unsigned transaction and signature requests, your account address, and your preferred language." }, + "transactionSettings": { + "message": "Transaction settings" + }, "transactionSubmitted": { "message": "Transaction submitted with estimated gas fee of $1 at $2." }, @@ -4710,9 +4869,6 @@ "transfer": { "message": "Transfer" }, - "transferBetweenAccounts": { - "message": "Transfer between my accounts" - }, "transferFrom": { "message": "Transfer from" }, @@ -4739,10 +4895,6 @@ "troubleStarting": { "message": "MetaMask had trouble starting. This error could be intermittent, so try restarting the extension." }, - "troubleTokenBalances": { - "message": "We had trouble loading your token balances. You can view them ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "By granting permission, you are allowing the following $1 to access your funds" }, @@ -4965,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" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index ed4a75cfe..fb2d332bc 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Añadir token personalizado" }, - "addCustomTokenByContractAddress": { - "message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Esto permitirá que la red se utilice en MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Agregar token" }, + "addTokenByContractAddress": { + "message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Dirección" }, @@ -359,12 +359,6 @@ "message": "¿Dar permiso para acceder a todo su $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Aprobar e instalar" - }, - "approveAndUpdate": { - "message": "Aprobar y actualizar" - }, "approveButtonText": { "message": "Aprobar" }, @@ -429,9 +423,6 @@ "back": { "message": "Volver" }, - "backToAll": { - "message": "Volver a Todos" - }, "backup": { "message": "Respaldo" }, @@ -557,13 +548,6 @@ "message": "Para $1 una transacción, la tarifa de gas debe aumentar al menos un 10% para que sea reconocida por la red.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Cancelar el swap por ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Cancelar el swap gratuitamente" - }, "cancelled": { "message": "Cancelado" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Conectar cuenta o crear nueva" }, - "connectHardwareWallet": { - "message": "Conectar la cartera de hardware" - }, "connectManually": { "message": "Conectarse manualmente al sitio actual" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Estableciendo conexión a la red de prueba Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Conectando a la red de prueba Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Crear" }, - "createAccount": { - "message": "Crear cuenta" - }, "createNewWallet": { "message": "Crear una cartera nueva" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada." }, - "enableSmartTransactions": { - "message": "Habilitar transacciones inteligentes" - }, "enableToken": { "message": "activar $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Toda persona puede crear un token con cualquier nombre, incluso versiones falsas de tokens existentes. ¡Agréguelo y realice transacciones bajo su propio riesgo!" }, - "importTokens": { - "message": "agregar activo" - }, "importTokensCamelCase": { "message": "Importar tokens" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "¿Le gustaría agregar estos tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Red de prueba Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Solicítelo aquí" }, - "missingToken": { - "message": "¿No ve su token?" - }, "moreComingSoon": { "message": "Más próximamente..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "No hay gas suficiente" }, - "notifications": { - "message": "Notificaciones" - }, "notifications10ActionText": { "message": "Ir a configuración", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Recibir" }, - "recents": { - "message": "Recientes" - }, "recipientAddressPlaceholder": { "message": "Búsqueda, dirección pública (0x) o ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Seleccione esta opción para mostrar el campo de datos hexadecimales en la pantalla de envío" }, - "showHide": { - "message": "Mostrar/ocultar" - }, "showIncomingTransactions": { "message": "Mostrar transacciones entrantes" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Entiendo que hasta que no haga una copia de seguridad de mi frase secreta de recuperación, puedo perder mis cuentas y todos los activos asociados." }, - "smartTransaction": { - "message": "Transacción inteligente" - }, "snapContent": { "message": "Este contenido proviene de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Instalar complemento" }, "snapInstallWarningCheck": { - "message": "Para confirmar que comprende, verifique la casilla." + "message": "Para confirmar que comprende, verifique la casilla.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Para confirmar que comprende, marque todas las casillas." + "message": "Para confirmar que comprende, marque todas las casillas.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Está otorgando acceso clave de $2 al complemento \"$1\". Esto es irrevocable y le otorga a \"$1\" el control de sus cuentas y activos de $2. Asegúrese de que confía en \"$1\" antes de continuar.", @@ -3416,9 +3375,6 @@ "status": { "message": "Estado" }, - "statusConnected": { - "message": "Conectado" - }, "statusNotConnected": { "message": "No conectado" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Fuerte" }, - "stxAreHere": { - "message": "¡Las transacciones inteligentes están aquí!" - }, "stxBenefit1": { "message": "Minimizar los costos de transacción" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Intente su swap nuevamente. Estaremos aquí para protegerlo contra riesgos similares la próxima vez." }, - "stxDescription": { - "message": "¡MetaMask Swaps ahora es mucho más inteligente! Habilitar transacciones inteligentes permitirá que MetaMask optimice mediante programación su swap para ayudar:" - }, - "stxErrorNotEnoughFunds": { - "message": "No hay suficientes fondos para una transacción inteligente." - }, - "stxErrorUnavailable": { - "message": "Las transacciones inteligentes no están disponibles temporalmente." - }, "stxFailure": { "message": "Error al canjear" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Enviando su swap de forma pública..." }, - "stxSubDescription": { - "message": "* Transacciones inteligentes intentará enviar su transacción de forma privada varias veces. Si todos los intentos fallan, la transacción se transmitirá públicamente para garantizar que su swap se realice con éxito." - }, "stxSuccess": { "message": "¡Swap finalizado!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Alternar solicitudes de eth_sign" }, - "toggleTestNetworks": { - "message": "$1 redes de prueba", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Transferir" }, - "transferBetweenAccounts": { - "message": "Transferir entre mis cuentas" - }, "transferFrom": { "message": "Transferir desde" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask tuvo problemas para iniciar. Este error podría ser intermitente, así que intente reiniciar la extensión." }, - "troubleTokenBalances": { - "message": "Tuvimos problemas al cargar los saldos de token. Puede verlos ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Al conceder el permiso, usted permite que los siguientes $1 tengan acceso a sus fondos" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index b53e151f5..bec397e5b 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -103,10 +103,6 @@ "addCustomToken": { "message": "Añadir token personalizado" }, - "addCustomTokenByContractAddress": { - "message": "¿No encuentra un token? Para agregar un token, copie su dirección. Puede encontrar la dirección de contrato del token en $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Esto permitirá que la red se utilice en MetaMask." }, @@ -139,6 +135,10 @@ "addToken": { "message": "Agregar token" }, + "addTokenByContractAddress": { + "message": "¿No encuentra un token? Para agregar un token, copie su dirección. Puede encontrar la dirección de contrato del token en $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Dirección" }, @@ -250,9 +250,6 @@ "back": { "message": "Volver" }, - "backToAll": { - "message": "Volver a Todos" - }, "backupApprovalInfo": { "message": "Este código secreto es necesario para que recupere la cartera en caso de que pierda el dispositivo, olvide su contraseña, tenga que volver a instalar MetaMask o quiera acceder a la cartera en otro dispositivo." }, @@ -389,9 +386,6 @@ "connectAccountOrCreate": { "message": "Conectar cuenta o crear nueva" }, - "connectHardwareWallet": { - "message": "Conectar la cartera de hardware" - }, "connectManually": { "message": "Conectarse manualmente al sitio actual" }, @@ -448,7 +442,7 @@ "connectingToGoerli": { "message": "Estableciendo conexión a la red de prueba Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Estableciendo conexión a la red de prueba Linea Goerli" }, "connectingToMainnet": { @@ -499,9 +493,6 @@ "create": { "message": "Crear" }, - "createAccount": { - "message": "Crear cuenta" - }, "createNewWallet": { "message": "Crear una nueva cartera" }, @@ -1091,9 +1082,6 @@ "importTokenWarning": { "message": "Toda persona puede crear un token con cualquier nombre, incluso versiones falsas de tokens existentes. ¡Agréguelo y realice transacciones bajo su propio riesgo!" }, - "importTokens": { - "message": "importar tokens" - }, "importTokensCamelCase": { "message": "Importar tokens" }, @@ -1257,7 +1245,7 @@ "likeToImportTokens": { "message": "¿Quiere agregar estos tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Red de prueba Linea Goerli" }, "link": { @@ -1351,9 +1339,6 @@ "missingNFT": { "message": "¿No ve su NFT?" }, - "missingToken": { - "message": "¿No ve su token?" - }, "mustSelectOne": { "message": "Debe seleccionar al menos 1 token." }, @@ -1812,9 +1797,6 @@ "receive": { "message": "Recibir" }, - "recents": { - "message": "Recientes" - }, "recipientAddressPlaceholder": { "message": "Búsqueda, dirección pública (0x) o ENS" }, @@ -2064,9 +2046,6 @@ "showHexDataDescription": { "message": "Seleccione esta opción para mostrar el campo de datos hexadecimales en la pantalla de envío" }, - "showHide": { - "message": "Mostrar/ocultar" - }, "showIncomingTransactions": { "message": "Mostrar transacciones entrantes" }, @@ -2168,9 +2147,6 @@ "stateLogsDescription": { "message": "Los registros de estado contienen sus direcciones de cuentas públicas y las transacciones enviadas." }, - "statusConnected": { - "message": "Conectado" - }, "statusNotConnected": { "message": "No conectado" }, @@ -2549,10 +2525,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleTestNetworks": { - "message": "$1 redes de prueba", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -2682,9 +2654,6 @@ "transfer": { "message": "Transferir" }, - "transferBetweenAccounts": { - "message": "Transferir entre mis cuentas" - }, "transferFrom": { "message": "Transferir desde" }, @@ -2692,10 +2661,6 @@ "message": "Tuvimos problemas al conectar su $1. Pruebe revisar $2 e inténtelo de nuevo.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" }, - "troubleTokenBalances": { - "message": "Tuvimos problemas al cargar los saldos de token. Puede verlos ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Al conceder el permiso, usted permite que los siguientes $1 tengan acceso a sus fondos" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index f9a64457e..88990874e 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Tagasi" }, - "backToAll": { - "message": "Tagasi kõigile" - }, "backupApprovalInfo": { "message": "See salakood on vajalik teie rahakoti taastamiseks, kui kaotate oma seadme, unustate parooli, peate MetaMaski uuesti alla laadima, või kui soovite avada oma rahakoti mõnel teisel seadmel." }, @@ -154,16 +151,13 @@ "connect": { "message": "Ühendamine" }, - "connectHardwareWallet": { - "message": "Ühendage riistvara rahakott" - }, "connectingTo": { "message": "Ühenduse loomine $1" }, "connectingToGoerli": { "message": "Ühendamine Goerli testvõrguga" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Ühendamine Linea Goerli testvõrguga" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Loo" }, - "createAccount": { - "message": "Loo konto" - }, "createPassword": { "message": "Loo parool" }, @@ -411,7 +402,7 @@ "likeToImportTokens": { "message": "Kas soovite need load lisada?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli testvõrk" }, "links": { @@ -571,9 +562,6 @@ "readdToken": { "message": "Saate selle loa tulevikus tagasi lisada, kui lähete oma kontovalikute menüüs vahelehele „Lisa luba“." }, - "recents": { - "message": "Hiljutised" - }, "recipientAddressPlaceholder": { "message": "Otsing, avalik aadress (0x) või ENS" }, @@ -797,16 +785,9 @@ "transfer": { "message": "Ülekandmine" }, - "transferBetweenAccounts": { - "message": "Ülekandmine minu kontode vahel" - }, "transferFrom": { "message": "Ülekandmine asukohast" }, - "troubleTokenBalances": { - "message": "Teie loasaldode laadimisega oli probleem. Saate neid vaadata", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Proovi uuesti" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index 6d61ed44d..895952bde 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -93,9 +93,6 @@ "back": { "message": "بازگشت" }, - "backToAll": { - "message": "برگشت به همه" - }, "backupApprovalInfo": { "message": "در صورت مفقود شدن دستگاه، فراموش شدن رمز عبور، لزوم نصب-دوباره MetaMask، یا اقدام دسترسی به کیف تان از دستگاه دیگر، کود مخفی جهت بازیابی کیف تان، لازم خواهد بود." }, @@ -154,16 +151,13 @@ "connect": { "message": "اتصال" }, - "connectHardwareWallet": { - "message": "اتصال کیف سخت افزار" - }, "connectingTo": { "message": "در حال اتصال به 1$1" }, "connectingToGoerli": { "message": "در حال اتصال به شبکه آزمایشی Goerli " }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "در حال اتصال به شبکه آزمایشی Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "ایجاد" }, - "createAccount": { - "message": "ایجاد حساب" - }, "createPassword": { "message": "ایجاد رمز عبور" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "آیا میخواهید این رمزیاب ها را اضافه نمایید؟" }, - "lineatestnet": { + "lineaGoerli": { "message": "شبکه آزمایشی Linea Goerli" }, "links": { @@ -581,9 +572,6 @@ "readdToken": { "message": "شما میتوانید این رمزیاب را دوباره برای آینده با رفتن به گزینه \"Add token\" در مینوی تنظیمات حساب ها، اضافه نمایید." }, - "recents": { - "message": "واپسین" - }, "recipientAddressPlaceholder": { "message": "جستجو، آدرس عمومی (0x)، یا ENS" }, @@ -807,16 +795,9 @@ "transfer": { "message": "انتقال" }, - "transferBetweenAccounts": { - "message": "انتقال میان حساب های من" - }, "transferFrom": { "message": "انتقال از" }, - "troubleTokenBalances": { - "message": "ما در بارگیری صورت حساب های رمزیاب تان دچار مشکل شدیم. شما میتوانید آنها را مشاهده کنید", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "امتحان مجدد" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 0cea114d6..5060b9015 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Edellinen" }, - "backToAll": { - "message": "Takaisin kaikkiin" - }, "backupApprovalInfo": { "message": "Tämä salainen koodi vaaditaan kukkarosi palauttamiseen siinä tapauksessa, että kadotat laitteesi, unohdat salasanasi, joudut asentamaan MetaMaskin uudestaan tai haluat käyttää lompakkoasi jollakin toisella laitteella." }, @@ -154,16 +151,13 @@ "connect": { "message": "Muodosta yhteys" }, - "connectHardwareWallet": { - "message": "Yhdistä laitteistokukkaro" - }, "connectingTo": { "message": "Yhdistetään summaan $1 " }, "connectingToGoerli": { "message": "Yhdistetään Goerlin testiverkostoon" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Yhdistetään Linea Goerli testiverkostoon" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Luo" }, - "createAccount": { - "message": "Luo tili" - }, "createPassword": { "message": "Luo salasana" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "Haluaisitko lisätä nämä poletit?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea-testiverkko" }, "links": { @@ -578,9 +569,6 @@ "readdToken": { "message": "Voit lisätä tämän tietueen myöhemmin takaisin siirtymällä tilisi vaihtoehtovalikon kohtaan ”Lisää tietue”." }, - "recents": { - "message": "Viimeaikaiset" - }, "recipientAddressPlaceholder": { "message": "Haku, julkinen osoite (0x) tai ENS" }, @@ -804,16 +792,9 @@ "transfer": { "message": "Siirrä" }, - "transferBetweenAccounts": { - "message": "Siirrä tilieni välillä" - }, "transferFrom": { "message": "Siirto kohteesta" }, - "troubleTokenBalances": { - "message": "Kohtasimme ongelmia ladatessamme tietuesaldojasi. Voit katsella niitä kohteessa", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Yritä uudelleen" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index 3f7d43347..da943eaea 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -81,9 +81,6 @@ "back": { "message": "Bumalik" }, - "backToAll": { - "message": "Bumalik sa Lahat" - }, "backupApprovalInfo": { "message": "Ang secret code na ito ay kinakailangan para ma-recover ang iyong wallet kung sakaling mawawala mo ang iyong device, nakalimutan mo ang iyong password, kailangan mong i-install ulit ang MetaMask, o gusto mong i-access ang iyong wallet sa ibang device." }, @@ -133,16 +130,13 @@ "connect": { "message": "Kumonekta" }, - "connectHardwareWallet": { - "message": "Magkonekta ng Hardware Wallet" - }, "connectingTo": { "message": "Kumokonekta sa $1" }, "connectingToGoerli": { "message": "Kumokonekta sa Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Kumokonekta sa Linea Goerli Test Network" }, "connectingToMainnet": { @@ -172,9 +166,6 @@ "create": { "message": "Gumawa" }, - "createAccount": { - "message": "Gumawa ng Account" - }, "createPassword": { "message": "Gumawa ng Password" }, @@ -505,9 +496,6 @@ "readdToken": { "message": "Puwede mong idagdag ulit ang token na ito sa hinaharap sa pamamagitan ng pagpunta sa “Magdagdag ng token” sa menu ng mga opsyon ng iyong mga accounts." }, - "recents": { - "message": "Kamakailan" - }, "recipientAddressPlaceholder": { "message": "Maghanap, pampublikong address (0x), o ENS" }, @@ -719,16 +707,9 @@ "transfer": { "message": "Mag-transfer" }, - "transferBetweenAccounts": { - "message": "Mag-transfer sa pagitan ng aking mga account" - }, "transferFrom": { "message": "I-transfer Mula Sa" }, - "troubleTokenBalances": { - "message": "Nagkaroon kami ng problema sa pag-loading sa iyong mga token balance. Makikita mo ang mga iyon ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Subukang muli" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 253c30a8b..6aa370f4f 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Ajouter un jeton personnalisé" }, - "addCustomTokenByContractAddress": { - "message": "Vous ne trouvez pas de jeton ? Vous pouvez ajouter manuellement n’importe quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Cela permettra d’utiliser ce réseau dans MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Ajouter le jeton" }, + "addTokenByContractAddress": { + "message": "Vous ne trouvez pas de jeton ? Vous pouvez ajouter manuellement n’importe quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Adresse" }, @@ -359,12 +359,6 @@ "message": "Voulez-vous lui accorder l’autorisation d’accéder et de transférer tous vos $1 ?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Approuver et installer" - }, - "approveAndUpdate": { - "message": "Approuver et mettre à jour" - }, "approveButtonText": { "message": "Approuver" }, @@ -429,9 +423,6 @@ "back": { "message": "Retour" }, - "backToAll": { - "message": "Retour à Tous" - }, "backup": { "message": "Sauvegarder" }, @@ -557,13 +548,6 @@ "message": "Pour $1 la transaction, les gas fees doivent être augmentés d’au moins 10 % pour être reconnus par le réseau.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Annuler le swap pour ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Annuler le swap gratuitement" - }, "cancelled": { "message": "Annulé" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Connecter un compte ou en créer un nouveau" }, - "connectHardwareWallet": { - "message": "Connecter un portefeuille matériel" - }, "connectManually": { "message": "Se connecter manuellement au site actuel" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Connexion au testnet Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Connexion au réseau de test Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Créer" }, - "createAccount": { - "message": "Créer un compte" - }, "createNewWallet": { "message": "Créer un nouveau portefeuille" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Utilisez l’API OpenSea pour récupérer les données de NFT. La détection automatique de NFT repose sur l’API OpenSea et ne sera pas disponible si elle est désactivée." }, - "enableSmartTransactions": { - "message": "Activer les transactions intelligentes" - }, "enableToken": { "message": "activer $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Tout un chacun peut créer un jeton avec n’importe quel nom, y compris de fausses versions de jetons existants. Ajoutez et échangez avec prudence !" }, - "importTokens": { - "message": "importer des jetons" - }, "importTokensCamelCase": { "message": "Importer des jetons" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Souhaitez-vous ajouter ces jetons ?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Réseau de test Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Demandez ici" }, - "missingToken": { - "message": "Vous ne voyez pas votre jeton ?" - }, "moreComingSoon": { "message": "D’autres à venir..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Pas assez de gaz" }, - "notifications": { - "message": "Notifications" - }, "notifications10ActionText": { "message": "Ouvrir les paramètres", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Recevoir" }, - "recents": { - "message": "Récents" - }, "recipientAddressPlaceholder": { "message": "Recherche, adresse publique (0x) ou ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Selectionner ici pour afficher le champs de données hex dans l’écran d’envoi" }, - "showHide": { - "message": "Afficher/masquer" - }, "showIncomingTransactions": { "message": "Afficher les transactions entrantes" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Je suis conscient(e) que tant que je n’aurai pas sauvegardé ma phrase secrète de récupération, je risque de perdre mes comptes et tous leurs actifs." }, - "smartTransaction": { - "message": "Transaction intelligente" - }, "snapContent": { "message": "Ce contenu provient de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Installer Snap" }, "snapInstallWarningCheck": { - "message": "Cochez la case pour confirmer que vous avez compris." + "message": "Cochez la case pour confirmer que vous avez compris.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Veuillez confirmer que vous avez bien compris en cochant toutes les cases." + "message": "Veuillez confirmer que vous avez bien compris en cochant toutes les cases.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Vous autorisez $2 à accéder à la clé du snap « $1 ». Cette action est irréversible et accorde à « $1 » le contrôle de vos comptes et actifs $2. Assurez-vous que vous faites confiance à « $1 » avant de continuer.", @@ -3416,9 +3375,6 @@ "status": { "message": "État" }, - "statusConnected": { - "message": "Connecté" - }, "statusNotConnected": { "message": "Non connecté" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Robuste" }, - "stxAreHere": { - "message": "Les transactions intelligentes sont là !" - }, "stxBenefit1": { "message": "Minimise les frais de transaction" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Réessayez le swap. Nous serons là pour vous protéger contre des risques similaires la prochaine fois." }, - "stxDescription": { - "message": "MetaMask Swaps vient de devenir beaucoup plus intelligent ! Si vous activez les transactions intelligentes, MetaMask pourra optimiser programmatiquement votre swap pour vous aider à :" - }, - "stxErrorNotEnoughFunds": { - "message": "Fonds insuffisants pour une transaction intelligente." - }, - "stxErrorUnavailable": { - "message": "Les transactions intelligentes sont temporairement indisponibles." - }, "stxFailure": { "message": "Échec du swap" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Soumission publique de votre Swap..." }, - "stxSubDescription": { - "message": "* Avec les transactions intelligentes, votre transaction sera soumise plusieurs fois en privé. Si toutes les tentatives échouent, la transaction sera diffusée publiquement pour s’assurer de la réussite de votre swap." - }, "stxSuccess": { "message": "Swap terminé !" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Activer/Désactiver les requêtes eth-sign" }, - "toggleTestNetworks": { - "message": "$1 réseaux de test", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Jeton" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Transfert" }, - "transferBetweenAccounts": { - "message": "Transfert entre mes comptes" - }, "transferFrom": { "message": "Transfert depuis" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "Impossible de démarrer MetaMask. Cette erreur peut être occasionnelle, essayez donc de redémarrer lextension." }, - "troubleTokenBalances": { - "message": "Nous avons eu du mal à charger votre balance de jetons, vous pouvez la consulter ici :", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "En accordant cette autorisation, vous permettez au(x) $1 suivant(s) d’accéder à vos fonds" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 4e3ab6b64..677d3cea9 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -93,9 +93,6 @@ "back": { "message": "חזור" }, - "backToAll": { - "message": "חזרה אל 'הכל'" - }, "backupApprovalInfo": { "message": "קוד סודי זה נדרש כדי לשחזר את הארנק שלך במקרה שתאבד/י את מכשירך, תשכח/י את הססמה, תצטרכ/י להתקין מחדש את MetaMask או שתרצה/י לגשת לארנק שלך במכשיר אחר." }, @@ -154,16 +151,13 @@ "connect": { "message": "התחברות" }, - "connectHardwareWallet": { - "message": "חבר ארנק חומרה" - }, "connectingTo": { "message": "מתחבר ל- $1 " }, "connectingToGoerli": { "message": "מתחבר ל-Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "מתחבר ל-Linea Goerli Test Network" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "צור" }, - "createAccount": { - "message": "פתיחת חשבון" - }, "createPassword": { "message": "יצירת ססמה" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "האם ברצונך להוסיף טוקנים אלה?" }, - "lineatestnet": { + "lineaGoerli": { "message": "רשת בדיקה Linea Goerli" }, "links": { @@ -578,9 +569,6 @@ "readdToken": { "message": "באפשרותך להוסיף טוקן זה בחזרה בעתיד על ידי מעבר אל \"הוסף טוקן\" בתפריט אפשרויות החשבונות שלך." }, - "recents": { - "message": "אחרונים" - }, "recipientAddressPlaceholder": { "message": "חיפוש, כתובת ציבורית (0x), או ENS" }, @@ -804,16 +792,9 @@ "transfer": { "message": "העברה" }, - "transferBetweenAccounts": { - "message": "בצע העברה בין חשבונותיי" - }, "transferFrom": { "message": "העברה מ-" }, - "troubleTokenBalances": { - "message": "לא הצלחנו לטעון את יתרות הטוקנים שלך. באפשרותך להציג אותן", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "ניסיון חוזר" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 48f1459a7..55e28531b 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "कस्टम टोकन जोड़ें" }, - "addCustomTokenByContractAddress": { - "message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का पता पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन अनुबंध पते $1 पर मिल सकते हैं।", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "इससे इस नेटवर्क को MetaMask के अंदर उपयोग करने की अनुमति मिलेगी।" }, @@ -263,6 +259,10 @@ "addToken": { "message": "टोकन जोड़ें" }, + "addTokenByContractAddress": { + "message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का पता पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन अनुबंध पते $1 पर मिल सकते हैं।", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "पता" }, @@ -359,12 +359,6 @@ "message": "आपके सभी $1 को एक्सेस और ट्रांसफर करने के लिए अनुमति दें", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "स्वीकृत और इंस्टॉल करें" - }, - "approveAndUpdate": { - "message": "स्वीकृत और अपडेट करें" - }, "approveButtonText": { "message": "अनुमोदित करें" }, @@ -429,9 +423,6 @@ "back": { "message": "वापस" }, - "backToAll": { - "message": "सभी पर वापस" - }, "backup": { "message": "बैकअप" }, @@ -557,13 +548,6 @@ "message": "किसी लेनदेन को $1 करने के लिए गैस शुल्क में कम से कम 10% की वृद्धि की जानी चाहिए ताकि उसे नेटवर्क द्वारा मान्यता मिल सके।", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "~$1 में स्वैप रद्द करें", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "मुफ्त में स्वैप रद्द करें" - }, "cancelled": { "message": "रद्द किया गया" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "खाता कनेक्ट करें या नया बनाएं" }, - "connectHardwareWallet": { - "message": "हार्डवेयर वॉलेट कनेक्ट करें" - }, "connectManually": { "message": "वर्तमान साइट से मैन्युअल रूप से कनेक्ट करें" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Goerli टेस्ट नेटवर्क से कनेक्ट हो रहा है" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Linea Goerli टेस्ट नेटवर्क से कनेक्ट हो रहा है" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "बनाएं" }, - "createAccount": { - "message": "अकाउंट बनाएं" - }, "createNewWallet": { "message": "एक नया वॉलेट बनाएं" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "NFT डेटा लाने के लिए OpenSea के API का उपयोग करें। NFT ऑटो-डिटेक्शन OpenSea के API पर निर्भर करता है, और इसके बंद होने पर उपलब्ध नहीं होगा।" }, - "enableSmartTransactions": { - "message": "स्मार्ट लेनदेन को सक्षम करें" - }, "enableToken": { "message": "$1 इनेबल करें", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "कोई भी किसी भी नाम के साथ एक टोकन बना सकता है, जिसमें मौजूदा टोकन के नकली संस्करण शामिल हैं। अपने जोखिम पर जोड़ें और व्यापार करें!" }, - "importTokens": { - "message": "टोकन आयात करें" - }, "importTokensCamelCase": { "message": "टोकन आयात करें" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "क्या आप इन टोकन को इंपोर्ट करना चाहते हैं?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli टेस्ट नेटवर्क" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "यहां अनुरोध करें" }, - "missingToken": { - "message": "क्या अपना टोकन नहीं देख रहे हैं?" - }, "moreComingSoon": { "message": "और अधिक जल्द ही आ रहा..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "पर्याप्त गैस नहीं" }, - "notifications": { - "message": "सूचनाएं" - }, "notifications10ActionText": { "message": "सेटिंग्स में जाएं", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "प्राप्त करें" }, - "recents": { - "message": "हाल ही के" - }, "recipientAddressPlaceholder": { "message": "खोज, सार्वजनिक पता (0x) या ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "भेजने की स्क्रीन पर हेक्स डेटा फील्ड दिखाने के लिए इसका चयन करें" }, - "showHide": { - "message": "दिखाएं/छिपाएं" - }, "showIncomingTransactions": { "message": "आने वाले लेन-देन दिखाएं" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "मैं समझता हूं कि जब तक मैं अपने सीक्रेट रिकवरी फ्रेज का बैकअप नहीं लेता, मैं अपने खाते और उनकी सभी संपत्ति खो सकता हूं।" }, - "smartTransaction": { - "message": "स्मार्ट लेनदेन" - }, "snapContent": { "message": "यह सामग्री $1 से आ रही है", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "स्नैप इंस्टाल करें" }, "snapInstallWarningCheck": { - "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं।" + "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं।", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं:" + "message": "ये पुष्टि करने के लिए कि आप समझते हैं, सभी बॉक्स पर सही का निशान लगाएं:", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "आप स्नैप \"$1\" के लिए $2 कुंजी का एक्सेस प्रदान कर रहे हैं। यह अपरिवर्तनीय है और आपके $2 खातों और संपत्तियों पर \"$1\" नियंत्रण प्रदान करता है। आगे बढ़ने से पहले सुनिश्चित करें कि आप \"$1\" पर भरोसा करते हैं।", @@ -3416,9 +3375,6 @@ "status": { "message": "स्टेटस" }, - "statusConnected": { - "message": "कनेक्ट किया गया" - }, "statusNotConnected": { "message": "कनेक्ट नहीं है" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "मजबूत" }, - "stxAreHere": { - "message": "स्मार्ट लेनदेन यहां पर हैं!" - }, "stxBenefit1": { "message": "लेनदेन लागतें मिनिमाइज़ करें" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "अपना स्वैप फिर से कोशिश करें। अगली बार भी इस तरह के जोखिमों से आपको बचाने के लिए हम यहां होंगे।" }, - "stxDescription": { - "message": "MetaMask के स्वैप अब और अधिक स्मार्ट हो गए हैं! इन हेतु सहयता के लिए स्मार्ट लेनदेन को सक्षम करने से MetaMask आपके स्वैप को प्रोग्रामेटिक रूप से ऑप्टिमाइज़ कर पाएगा:" - }, - "stxErrorNotEnoughFunds": { - "message": "एक स्मार्ट लेनदेन के लिए पर्याप्त फंड नहीं है।" - }, - "stxErrorUnavailable": { - "message": "स्मार्ट लेनदेन अस्थाई तौर पर अनुपबल्ध हैं।" - }, "stxFailure": { "message": "स्वैप विफल हुआ" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "आपका स्वैप सार्वजनिक रूप से सबमिट किया जा रहा है..." }, - "stxSubDescription": { - "message": "* स्मार्ट लेनदेन आपके लेनदेन को निजी तौर पर, अनेक बार जमा करने का प्रयास करेंगे। यदि सभी प्रयास विफल हो जाते हैं, तो लेनदेन को सार्वजनिक रूप से प्रसारित किया जाएगा ताकि यह सुनिश्चित हो सके कि आपका स्वैप सफलतापूर्वक पूरा हो।" - }, "stxSuccess": { "message": "स्वैप पूरा हुआ!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Eth_sign अनुरोधों को टॉगल करें" }, - "toggleTestNetworks": { - "message": "$1 परीक्षण नेटवर्क", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "टोकन" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "स्थानांतरण" }, - "transferBetweenAccounts": { - "message": "मेरे खातों के बीच स्थानांतरित करें" - }, "transferFrom": { "message": "इससे स्थानांतरित करें" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask को शुरू करने में परेशानी आई। यह त्रुटि रुक-रुक कर हो सकती है, इसलिए एक्सटेंशन को फिर से शुरू करके देखें।" }, - "troubleTokenBalances": { - "message": "हमें आपके टोकन की शेषराशि लोड करने में परेशानी हुई। आप उन्हें देख सकते हैं ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "अनुमति प्रदान करके, आप निम्नलिखित $1 को अपने फंड तक पहुंचने की इजाजत दे रहे हैं" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index f94ab181c..991a4f96c 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -69,9 +69,6 @@ "create": { "message": "बनाएं" }, - "createAccount": { - "message": "खाता बनाएं" - }, "decimal": { "message": "दशमलव परिशुद्धता" }, @@ -323,10 +320,6 @@ "total": { "message": "कुल" }, - "troubleTokenBalances": { - "message": "मुसीबत... आपके टोकन शेष राशि को लोड करने में हमें परेशानी हुई थी। आप उन्हें देख सकते हैं", - "description": "Followed by a link (here) to view token balances" - }, "typePassword": { "message": "अपना पासवर्ड टाइप करें" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 72c63d315..ab8273274 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Natrag" }, - "backToAll": { - "message": "Natrag na sve" - }, "backupApprovalInfo": { "message": "Ovaj je sigurnosni kôd potreban za obnavljanje novčanika ako izgubite svoj uređaj, zaboravite svoju lozinku, morate ponovno instalirati MetaMask ili želite pristupiti svojem novčaniku na drugom uređaju." }, @@ -154,16 +151,13 @@ "connect": { "message": "Povežite se" }, - "connectHardwareWallet": { - "message": "Poveži hardverski novčanik" - }, "connectingTo": { "message": "Povezivanje na $1" }, "connectingToGoerli": { "message": "Povezivanje na testnu mrežu Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Povezivanje na testnu mrežu Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Stvori" }, - "createAccount": { - "message": "Stvori račun" - }, "createPassword": { "message": "Stvori lozinku" }, @@ -411,7 +402,7 @@ "likeToImportTokens": { "message": "Želite li dodati ove tokene?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Testna mreža Linea Goerli" }, "links": { @@ -574,9 +565,6 @@ "readdToken": { "message": "Ovaj token možete dodati kasnije odlaskom pod stavku „Dodaj token” u izborniku mogućnosti računa. " }, - "recents": { - "message": "Nedavno" - }, "recipientAddressPlaceholder": { "message": "Pretraži, javne adrese (0x) ili ENS" }, @@ -797,16 +785,9 @@ "transfer": { "message": "Prenesi" }, - "transferBetweenAccounts": { - "message": "Prenesi između mojih računa" - }, "transferFrom": { "message": "Prenesi od" }, - "troubleTokenBalances": { - "message": "Imamo problema s učitavanjem raspoloživog stanja vaših tokena. Možete ih vidjeti", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Pokušaj ponovo" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index 5b1aaf04f..fd92854de 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -96,9 +96,6 @@ "connect": { "message": "Konekte" }, - "connectHardwareWallet": { - "message": "Konekte Materyèl Wallet" - }, "connectingToMainnet": { "message": "Konekte ak Prensipal Ethereum Rezo a" }, @@ -120,9 +117,6 @@ "create": { "message": "Kreye" }, - "createAccount": { - "message": "Kreye Kont" - }, "currentLanguage": { "message": "Lang Aktyèl" }, @@ -563,10 +557,6 @@ "transfer": { "message": "Transfè" }, - "troubleTokenBalances": { - "message": "Nou te gen pwoblèm chaje balans token ou. Ou ka wè yo ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Eseye anko" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 51270215d..dc10e6cb6 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Vissza" }, - "backToAll": { - "message": "Vissza az összeshez" - }, "backupApprovalInfo": { "message": "Ez a titkos kód a tárca helyreállításához szükséges, ha elveszíti eszközét, elfelejti jelszavát, újra kell telepítenie a MetaMask alkalmazást, vagy ha a tárcájához egy másik eszközről szeretne hozzáférni." }, @@ -154,16 +151,13 @@ "connect": { "message": "Csatlakozás" }, - "connectHardwareWallet": { - "message": "Hardverpénztárca csatlakoztatása" - }, "connectingTo": { "message": "Kapcsolódás: $1" }, "connectingToGoerli": { "message": "Csatlakozás a Goerli teszthálózathoz" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Csatlakozás a Linea Goerli teszthálózathoz" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Létrehozás" }, - "createAccount": { - "message": "Fiók létrehozása" - }, "createPassword": { "message": "Jelszó létrehozása" }, @@ -411,7 +402,7 @@ "likeToImportTokens": { "message": "Hozzá szeretné adni ezeket az érméket?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli teszthálózat" }, "links": { @@ -574,9 +565,6 @@ "readdToken": { "message": "Ezt a tokent a jövőben is hozzáadhatja, ha a fiókbeállítások menü „Token hozzáadása” elemére lép." }, - "recents": { - "message": "Legutóbbiak" - }, "recipientAddressPlaceholder": { "message": "Keresés, nyilvános cím (0x) vagy ENS" }, @@ -797,16 +785,9 @@ "transfer": { "message": "Átutalás" }, - "transferBetweenAccounts": { - "message": "Fiókok közötti küldés" - }, "transferFrom": { "message": "Átvezetés innen: " }, - "troubleTokenBalances": { - "message": "Gondjaink voltak tokenegyenlegeid betöltésével. Megtekintheted őket", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Újra" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 2e61efe24..baec167e3 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Tambahkan token kustom" }, - "addCustomTokenByContractAddress": { - "message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Tindakan ini akan membantu jaringan ini agar dapat digunakan dengan MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Tambahkan token" }, + "addTokenByContractAddress": { + "message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Alamat" }, @@ -359,12 +359,6 @@ "message": "Berikan izin untuk mengakses dan mentransfer seluruh $1 Anda?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Setujui & instal" - }, - "approveAndUpdate": { - "message": "Setujui dan perbarui" - }, "approveButtonText": { "message": "Setujui" }, @@ -429,9 +423,6 @@ "back": { "message": "Kembali" }, - "backToAll": { - "message": "Kembali ke semua" - }, "backup": { "message": "Pencadangan" }, @@ -557,13 +548,6 @@ "message": "Untuk $1 suatu transaksi, biaya gas harus dinaikkan minimal 10% agar dapat dikenali oleh jaringan.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Batalkan swap untuk ~$", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Batalkan swap gratis" - }, "cancelled": { "message": "Dibatalkan" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Hubungkan akun atau buat baru" }, - "connectHardwareWallet": { - "message": "Hubungkan dompet perangkat keras" - }, "connectManually": { "message": "Hubungkan ke situs saat ini secara manual" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Menghubungkan ke jaringan uji Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Menghubungkan ke jaringan uji Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Buat" }, - "createAccount": { - "message": "Buat akun" - }, "createNewWallet": { "message": "Buat dompet baru" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Gunakan API OpenSea untuk mengambil data NFT. Deteksi otomatis NFT bergantung pada API OpenSea, dan tidak akan tersedia saat API ditutup." }, - "enableSmartTransactions": { - "message": "Aktifkan transaksi pintar" - }, "enableToken": { "message": "aktifkan $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Siapa pun dapat membuat token dengan nama apa pun, termasuk versi palsu dari token yang ada. Tambahkan dan perdagangkan dengan risiko ditanggung sendiri!" }, - "importTokens": { - "message": "impor token" - }, "importTokensCamelCase": { "message": "Impor token" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Apakah Anda ingin menambahkan token ini?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Jaringan uji Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Minta di sini" }, - "missingToken": { - "message": "Tidak melihat token Anda?" - }, "moreComingSoon": { "message": "Selanjutnya akan segera hadir..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Gas tidak cukup" }, - "notifications": { - "message": "Notifikasi" - }, "notifications10ActionText": { "message": "Lihat di Pengaturan", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Terima" }, - "recents": { - "message": "Terkini" - }, "recipientAddressPlaceholder": { "message": "Cari, alamat publik (0x), atau ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Pilih ini untuk menampilkan bidang data hex di layar kirim" }, - "showHide": { - "message": "Tampil/Sembunyi" - }, "showIncomingTransactions": { "message": "Tampilkan transaksi masuk" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Saya memahami bahwa sampai saya mencadangkan Frasa Pemulihan Rahasia, saya dapat kehilangan akun saya dan semua aset yang ada." }, - "smartTransaction": { - "message": "Transaksi pintar" - }, "snapContent": { "message": "Konten ini berasal dari $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Instal Snap" }, "snapInstallWarningCheck": { - "message": "Untuk mengonfirmasikan bahwa Anda sudah paham, centang kotaknya." + "message": "Untuk mengonfirmasikan bahwa Anda sudah paham, centang kotaknya.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Untuk mengonfirmasikan bahwa Anda memahaminya, centang semua kotak." + "message": "Untuk mengonfirmasikan bahwa Anda memahaminya, centang semua kotak.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Anda memberikan $2 akses kunci ke snap \"$1\". Tindakan ini tidak dapat dibatalkan dan memberikan kendali \"$1\" atas akun dan aset $2 Anda. Sebelum melanjutkan, pastikan \"$1\" aman.", @@ -3416,9 +3375,6 @@ "status": { "message": "Status" }, - "statusConnected": { - "message": "Terhubung" - }, "statusNotConnected": { "message": "Tidak terhubung" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Kuat" }, - "stxAreHere": { - "message": "Transaksi Pintar hadir di sini!" - }, "stxBenefit1": { "message": "Meminimalkan biaya transaksi" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Cobalah untuk menukar lagi. Kami akan selalu hadir untuk melindungi Anda dari risiko serupa di lain waktu." }, - "stxDescription": { - "message": "Pertukaran MetaMask menjadi semakin pintar! Mengaktifkan Transaksi Pintar akan memungkinkan MetaMask mengoptimalkan Pertukaran Anda secara terprogram untuk membantu:" - }, - "stxErrorNotEnoughFunds": { - "message": "Dana tidak cukup untuk mengaktifkan transaksi pintar." - }, - "stxErrorUnavailable": { - "message": "Transaksi Pintar tidak tersedia untuk sementara waktu." - }, "stxFailure": { "message": "Pertukaran gagal" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Kirimkan Swap Anda secara publik..." }, - "stxSubDescription": { - "message": "* Transaksi Pintar akan mencoba mengirimkan transaksi Anda secara pribadi, beberapa kali. Jika semua upaya gagal, transaksi akan disiarkan secara publik untuk memastikan Pertukaran telah berhasil dilakukan." - }, "stxSuccess": { "message": "Pertukaran selesai!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Alihkan permintaan eth_sign" }, - "toggleTestNetworks": { - "message": "$1 jaringan pengujian", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Transfer" }, - "transferBetweenAccounts": { - "message": "Transfer antar akun saya" - }, "transferFrom": { "message": "Transfer dari" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask mengalami masalah saat memulai. Kesalahan ini dapat terjadi berselang, coba mulai ulang ekstensi." }, - "troubleTokenBalances": { - "message": "Kami mengalami masalah saat memuat saldo token Anda. Anda dapat melihatnya ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Dengan memberikan izin, Anda mengizinkan $1 berikut untuk mengakses dana Anda" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 99d323da3..9d3c2fd5a 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -158,10 +158,6 @@ "addCustomToken": { "message": "Aggiungi token personalizzato" }, - "addCustomTokenByContractAddress": { - "message": "Non trovi un token? Puoi aggiungere qualsiasi token incollando il suo indirizzo. L'indirizzo del contratto del Token può essere trovato su $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Ciò consentirà a questa rete di essere utilizzata all'interno di MetaMask." }, @@ -204,6 +200,10 @@ "addToken": { "message": "Aggiungi Token" }, + "addTokenByContractAddress": { + "message": "Non trovi un token? Puoi aggiungere qualsiasi token incollando il suo indirizzo. L'indirizzo del contratto del Token può essere trovato su $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Indirizzo" }, @@ -290,12 +290,6 @@ "message": "Consenti l'accesso e il trasferimento di tutti i tuoi $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Approva & installa" - }, - "approveAndUpdate": { - "message": "Approva & aggiorna" - }, "approveButtonText": { "message": "Approva" }, @@ -339,9 +333,6 @@ "back": { "message": "Indietro" }, - "backToAll": { - "message": "Torna a \"Tutti\"" - }, "backupApprovalInfo": { "message": "Questo codice è necessario per recuperare il tuo portafoglio nel caso in cui perdi il tuo dispositivo, dimentichi la tua password, debba reinstallare MetaMask o voglia accedere al tuo portafoglio su un altro dispositivo." }, @@ -428,13 +419,6 @@ "message": "Per $1 una transazione la commissione di gas deve crescere almeno del 10% per essere riconosciuto dalla rete.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Annulla scambio per ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Annulla scambio gratuitamente" - }, "cancelled": { "message": "Annullata" }, @@ -485,9 +469,6 @@ "connectAccountOrCreate": { "message": "Connetti un account o creane uno nuovo" }, - "connectHardwareWallet": { - "message": "Connetti Portafoglio Hardware" - }, "connectManually": { "message": "Connettiti al sito manualmente" }, @@ -544,7 +525,7 @@ "connectingToGoerli": { "message": "Connessione alla Rete di Test Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Connessione alla Rete di test Linea Goerli" }, "connectingToMainnet": { @@ -616,9 +597,6 @@ "create": { "message": "Crea" }, - "createAccount": { - "message": "Crea Account" - }, "createNewWallet": { "message": "Crea un nuovo portafoglio" }, @@ -1121,7 +1099,7 @@ "likeToImportTokens": { "message": "Vorresti aggiungere questi token?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Rete di test Linea Goerli" }, "links": { @@ -1344,9 +1322,6 @@ "receive": { "message": "Ricevi" }, - "recents": { - "message": "Recenti" - }, "recipientAddressPlaceholder": { "message": "Ricerca, indirizzo pubblico (0x) o ENS" }, @@ -1540,9 +1515,6 @@ "stateLogsDescription": { "message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate." }, - "statusConnected": { - "message": "Connesso" - }, "statusNotConnected": { "message": "Non connesso" }, @@ -1833,9 +1805,6 @@ "transfer": { "message": "Trasferisci" }, - "transferBetweenAccounts": { - "message": "Trasferimento tra i miei account" - }, "transferFrom": { "message": "Trasferisci Da" }, @@ -1843,10 +1812,6 @@ "message": "Abbiamo riscontrato un errore nella connessione a $1, guarda la documentazione $2 e prova di nuovo.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" }, - "troubleTokenBalances": { - "message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Prova di nuovo" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 51e949432..611955387 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "カスタムトークンを追加" }, - "addCustomTokenByContractAddress": { - "message": "トークンが見つからない場合、アドレスをペーストして手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります。", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "これにより、このネットワークはMetaMask内で使用できるようになります。" }, @@ -263,6 +259,10 @@ "addToken": { "message": "トークンを追加" }, + "addTokenByContractAddress": { + "message": "トークンが見つからない場合、アドレスをペーストして手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "アドレス" }, @@ -359,12 +359,6 @@ "message": "すべての $1 へのアクセスとその送金を許可しますか?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "承認してインストール" - }, - "approveAndUpdate": { - "message": "承認して更新" - }, "approveButtonText": { "message": "承認" }, @@ -429,9 +423,6 @@ "back": { "message": "戻る" }, - "backToAll": { - "message": "一覧に戻る" - }, "backup": { "message": "バックアップ" }, @@ -557,13 +548,6 @@ "message": "トランザクションを$1するには、ネットワークに認識されるようにガス代を 10% 以上増額する必要があります。", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "$1 以下でスワップをキャンセル", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "無料でスワップをキャンセル" - }, "cancelled": { "message": "キャンセル済み" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "アカウントを接続するか、または新規に作成します" }, - "connectHardwareWallet": { - "message": "ハードウェアウォレットの接続" - }, "connectManually": { "message": "現在のサイトに手動で接続" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Goerliテストネットワークに接続中" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Linea Goerli テストネットワークに接続中" }, "connectingToMainnet": { @@ -760,7 +741,7 @@ "message": "トークンコントラクト" }, "convertTokenToNFTDescription": { - "message": "このアセットは NFT であることが検出されました。Metamask では現在、NFT が完全にネイティブでサポートされています。トークンリストから削除して、NFT として追加しますか?" + "message": "このアセットは NFT であることが検出されました。MetaMask では現在、NFT が完全にネイティブでサポートされています。トークンリストから削除して、NFT として追加しますか?" }, "convertTokenToNFTExistDescription": { "message": "このアセットは NFT として追加されていることが検出されました。トークンリストから削除しますか?" @@ -789,9 +770,6 @@ "create": { "message": "作成" }, - "createAccount": { - "message": "アカウントを作成" - }, "createNewWallet": { "message": "新規ウォレットを作成" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "OpenSea APIを使用してNFTデータを取得します。NFT自動検出はOpenSea APIを使用するため、この設定をオフにすると利用できなくなります。" }, - "enableSmartTransactions": { - "message": "スマートトランザクションを有効にする" - }, "enableToken": { "message": "$1を有効にする", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "誰でも既存のトークンの偽バージョンを含めて、任意の名前でトークンを作成することができます。追加および取引は自己責任となります!" }, - "importTokens": { - "message": "トークンをインポート" - }, "importTokensCamelCase": { "message": "トークンをインポート" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "これらのトークンを追加しますか?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli テストネットワーク" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "ここからリクエスト" }, - "missingToken": { - "message": "トークンが見当たりませんか?" - }, "moreComingSoon": { "message": "さらに近日追加予定..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "ガスが不足しています" }, - "notifications": { - "message": "通知" - }, "notifications10ActionText": { "message": "設定に移動", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "受領" }, - "recents": { - "message": "最近" - }, "recipientAddressPlaceholder": { "message": "検索、パブリックアドレス (0x)、またはENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "これを選択すると、送金画面に16進データフィールドが表示されます" }, - "showHide": { - "message": "表示・非表示" - }, "showIncomingTransactions": { "message": "受信トランザクションを表示" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "私は、シークレットリカバリーフレーズをバックアップするまで、アカウントとそのアセットのすべてを失う可能性があることを理解しています。" }, - "smartTransaction": { - "message": "スマートトランザクション" - }, "snapContent": { "message": "このコンテンツは $1 からのものです", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "スナップをインストール" }, "snapInstallWarningCheck": { - "message": "理解したことを確認するために、次の項目にチェックを入れてください." + "message": "理解したことを確認するために、次の項目にチェックを入れてください.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "理解したことを確認するために、すべての項目にチェックを入れてください。" + "message": "理解したことを確認するために、すべての項目にチェックを入れてください。", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "スナップ「$1」に $2 へのキーアクセスを許可しようとしています。この操作は取り消し不能であり、$2 アカウントとアセットのコントロールを「$1」に許可することになります。続行する前に、必ず「$1」が信頼できることを確認してください。", @@ -3416,9 +3375,6 @@ "status": { "message": "ステータス" }, - "statusConnected": { - "message": "接続済み" - }, "statusNotConnected": { "message": "未接続" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "強" }, - "stxAreHere": { - "message": "スマートトランザクションが利用可能になりました!" - }, "stxBenefit1": { "message": "トランザクションコストを最小化" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "もう一度スワップをお試しください。次回は同様のリスクを避けられるようサポートします。" }, - "stxDescription": { - "message": "MetaMask Swaps がはるかに賢くなりました!スマートトランザクションを有効にすると、MetaMask がプログラムに従ってスワップを最適化できるようになるため、以下のようなメリットがあります。" - }, - "stxErrorNotEnoughFunds": { - "message": "スマートトランザクションに十分な資金がありません。" - }, - "stxErrorUnavailable": { - "message": "スマートトランザクションは一時的に利用できません。" - }, "stxFailure": { "message": "スワップに失敗しました" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "スワップを公開で送信中..." }, - "stxSubDescription": { - "message": "* スマートトランザクションは、非公開でトランザクションのの送信を数回試みます。すべての試みが失敗した場合、スワップが成功するようトランザクションが公開されます。" - }, "stxSuccess": { "message": "スワップ完了!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "eth_sign 要求の設定" }, - "toggleTestNetworks": { - "message": "$1テストネットワーク", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "トークン" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "送金" }, - "transferBetweenAccounts": { - "message": "自分のアカウント間での振替" - }, "transferFrom": { "message": "送金元" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask がうまく起動しませんでした。このエラーは断続的に発生する可能性があるため、拡張機能を再起動してみてください。" }, - "troubleTokenBalances": { - "message": "トークンバランスのロードに問題があります。トークンバランスを表示できます", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "パーミッションを付与することで、次の$1による資金へのアクセスが許可されます" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 77e919931..205103048 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -93,9 +93,6 @@ "back": { "message": "ಹಿಂದೆ" }, - "backToAll": { - "message": "ಎಲ್ಲವನ್ನು ಹಿಂತಿರುಗಿಸಿ" - }, "backupApprovalInfo": { "message": "ನಿಮ್ಮ ಸಾಧನವನ್ನು ನೀವು ಕಳೆದುಕೊಂಡಾಗ, ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರೆತರೆ, MetaMask ಅನ್ನು ಮರು-ಸ್ಥಾಪಿಸಲು ಅಥವಾ ಬೇರೊಂದು ಸಾಧನದಲ್ಲಿ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಬಯಸಿದ ಸಂದರ್ಭದಲ್ಲಿ ನಿಮ್ಮ ವ್ಯಾಲೆಟ್‌ ಅನ್ನು ಮರುಪಡೆದುಕೊಳ್ಳಲು ಈ ರಹಸ್ಯ ಕೋಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ." }, @@ -154,16 +151,13 @@ "connect": { "message": "ಸಂಪರ್ಕಿಸು" }, - "connectHardwareWallet": { - "message": "ಹಾರ್ಡ್‌ವೆರ್ ವ್ಯಾಲೆಟ್‌‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಿ" - }, "connectingTo": { "message": "$1 ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗುತ್ತಿದೆ" }, "connectingToGoerli": { "message": "Goerli ಪರೀಕ್ಷಾ ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Linea Goerli ಪರೀಕ್ಷಾ ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "ರಚಿಸಿ" }, - "createAccount": { - "message": "ಖಾತೆಯನ್ನು ರಚಿಸಿ" - }, "createPassword": { "message": "ಪಾಸ್‌ವರ್ಡ್ ರಚಿಸಿ" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "ನೀವು ಈ ಟೋಕನ್‌ಗಳನ್ನು ಸೇರಿಸಲು ಬಯಸುತ್ತೀರಾ?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli ಪರೀಕ್ಷೆ ನೆಟ್‌ವರ್ಕ್" }, "links": { @@ -581,9 +572,6 @@ "readdToken": { "message": "ನಿಮ್ಮ ಖಾತೆಗಳ ಆಯ್ಕೆಗಳ ಮೆನುವಿನಲ್ಲಿ \"ಟೋಕನ್ ಸೇರಿಸು\" ಗೆ ಹೋಗುವ ಮೂಲಕ ನೀವು ಈ ಟೋಕನ್ ಅನ್ನು ಭವಿಷ್ಯದಲ್ಲಿ ಮರಳಿ ಸೇರಿಸಬಹುದು." }, - "recents": { - "message": "ಇತ್ತೀಚಿನವುಗಳು" - }, "recipientAddressPlaceholder": { "message": "ಸಾರ್ವಜನಿಕ ವಿಳಾಸ (0x) ಅಥವಾ ENS ಹುಡುಕಿ" }, @@ -807,16 +795,9 @@ "transfer": { "message": "ವರ್ಗಾಯಿಸಿ" }, - "transferBetweenAccounts": { - "message": "ನನ್ನ ಖಾತೆಗಳ ನಡುವೆ ವರ್ಗಾಯಿಸಿ" - }, "transferFrom": { "message": "ಇದರಿಂದ ವರ್ಗಾಯಿಸಿ" }, - "troubleTokenBalances": { - "message": "ನಿಮ್ಮ ಟೋಕನ್ ಬ್ಯಾಲೆನ್ಸ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡುವಲ್ಲಿ ನಮಗೆ ಸಮಸ್ಯೆಯಾಗಿದೆ. ನೀವು ಅವುಗಳನ್ನು ನೋಡಬಹುದು", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "ಪುನಃ ಪ್ರಯತ್ನಿಸಿ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index c44c1000f..ab2ebe88b 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "커스텀 토큰 추가" }, - "addCustomTokenByContractAddress": { - "message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "이렇게 하면 MetaMask 내에서 이 네트워크를 사용할 수 있습니다." }, @@ -263,6 +259,10 @@ "addToken": { "message": "토큰 추가" }, + "addTokenByContractAddress": { + "message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "주소" }, @@ -359,12 +359,6 @@ "message": "내 모든 $1에 액세스 및 전송할 수 있는 권한을 부여할까요?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "승인 및 설치" - }, - "approveAndUpdate": { - "message": "승인 및 업데이트" - }, "approveButtonText": { "message": "승인" }, @@ -429,9 +423,6 @@ "back": { "message": "뒤로" }, - "backToAll": { - "message": "전체 목록으로 돌아가기" - }, "backup": { "message": "백업" }, @@ -557,13 +548,6 @@ "message": "거래를 $1하려면 가스비를 최소 10%를 인상해야 네트워크에서 인식될 수 있습니다.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "~$1 비용으로 스왑 취소", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "무료로 스왑 취소" - }, "cancelled": { "message": "취소됨" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "계정 연결 또는 새 계정 만들기" }, - "connectHardwareWallet": { - "message": "하드웨어 지갑 연결" - }, "connectManually": { "message": "현재 사이트에 수동으로 연결" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Goerli 테스트 네트워크에 연결 중" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Linea Goerli 테스트 네트워크에 연결 중" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "생성" }, - "createAccount": { - "message": "계정 생성" - }, "createNewWallet": { "message": "새 지갑 생성" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "OpenSea의 API를 사용하여 NFT 데이터를 가져옵니다. NFT 자동 감지는 OpenSea의 API에 의존하며 이 API가 꺼져 있으면 사용할 수 없습니다." }, - "enableSmartTransactions": { - "message": "스마트 트랜잭션 활성화" - }, "enableToken": { "message": "$1 활성화", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "기존 토큰의 가짜 버전을 포함하여 누구나 어떤 이름으로든 토큰을 만들 수 있습니다. 추가 및 거래는 사용자의 책임입니다." }, - "importTokens": { - "message": "토큰 가져오기" - }, "importTokensCamelCase": { "message": "토큰 가져오기" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "이 토큰을 추가할까요?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli 테스트 네트워크" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "여기에서 요청하세요" }, - "missingToken": { - "message": "토큰이 보이지 않나요?" - }, "moreComingSoon": { "message": "더 추가 예정..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "가스 부족" }, - "notifications": { - "message": "알림" - }, "notifications10ActionText": { "message": "설정으로 이동하기", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "받기" }, - "recents": { - "message": "최근" - }, "recipientAddressPlaceholder": { "message": "검색, 공개 주소(0x) 또는 ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "이 항목을 선택하면 보내기 화면에 16진수 데이터 필드가 표시됩니다." }, - "showHide": { - "message": "보기/숨기기" - }, "showIncomingTransactions": { "message": "수신 거래 표시" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "본인은 본인의 비밀 복구 구문을 백업하지 않는 한 본인의 계정과 모든 자산을 잃을 수 있다는 사실을 이해합니다." }, - "smartTransaction": { - "message": "스마트 트랜잭션" - }, "snapContent": { "message": "콘텐츠 출처: $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "스냅 설치" }, "snapInstallWarningCheck": { - "message": "이해하셨으면 모두 체크해 주세요." + "message": "이해하셨으면 모두 체크해 주세요.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "이해하셨으면 모든 란에 체크하세요." + "message": "이해하셨으면 모든 란에 체크하세요.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "'$1' 스냅 이용에 필요한 $2 키 액세스 권한을 부여하고 있습니다. 이 작업은 사용자의 $2 계정과 자산에 '$1' 제어 권한을 부여하며 취소가 불가능합니다. '$1의 신뢰성을 확인한 후에 진행하세요.", @@ -3416,9 +3375,6 @@ "status": { "message": "상태" }, - "statusConnected": { - "message": "연결됨" - }, "statusNotConnected": { "message": "연결되지 않음" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "강함" }, - "stxAreHere": { - "message": "스마트 거래가 가능합니다!" - }, "stxBenefit1": { "message": "거래 비용 최소화하기" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "스왑을 다시 진행하세요. 다음에도 유사한 위험이 발생한다면 보호해 드리겠습니다." }, - "stxDescription": { - "message": "MetaMask 스왑이 더욱 스마트해졌습니다! 스마트 거래를 활성화하면 MetaMask가 프로그램을 통해 스왑을 최적화하여 다음을 도울 수 있습니다." - }, - "stxErrorNotEnoughFunds": { - "message": "스마트 거래 자금 부족" - }, - "stxErrorUnavailable": { - "message": "스마트 거래를 잠시 사용할 수 없습니다." - }, "stxFailure": { "message": "스왑 실패" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "스왑을 공개로 제출하는 중..." }, - "stxSubDescription": { - "message": "*스마트 거래는 비공개로 거래를 제출하기 위해 여러 번 시도할 것입니다. 모든 시도가 실패하면 성공적인 스왑을 위해 거래는 공개적으로 브로드캐스트될 것입니다." - }, "stxSuccess": { "message": "스왑 완료!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "eth_sign 요청 토글" }, - "toggleTestNetworks": { - "message": "$1 테스트 네트워크", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "토큰" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "전송" }, - "transferBetweenAccounts": { - "message": "내 계정 간 전송" - }, "transferFrom": { "message": "전송 위치" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask 실행 중 오류가 발생했습니다. 일시적인 오류일 수 있으니 확장 프로그램을 재시작해 보세요." }, - "troubleTokenBalances": { - "message": "토큰 잔액을 로드하는 도중 문제가 발생했습니다. 다음에서 잔액을 확인하세요. ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "권한을 부여하면 다음 $1이(가) 귀하의 자금에 액세스할 수 있습니다." }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 3b0831431..927c5f060 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Grįžti" }, - "backToAll": { - "message": "Atgal prie visų" - }, "backupApprovalInfo": { "message": "Šis slaptas kodas reikalingas norint atkurti jūsų slaptažodinę, jeigu pamestumėte savo įrenginį, pamirštumėte savo slaptažodį, iš naujo įdiegtumėte „MetaMask“, taip pat norint pasiekti slaptažodinę iš kito įrenginio." }, @@ -154,16 +151,13 @@ "connect": { "message": "Prisijungti" }, - "connectHardwareWallet": { - "message": "Susieti aparatinės įrangos slaptažodinę" - }, "connectingTo": { "message": "Jungiamasi prie $1" }, "connectingToGoerli": { "message": "Jungiamasi prie „Goerli“ bandomojo tinklo" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Jungiamasi prie „Linea“ bandomojo tinklo" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Sukurti" }, - "createAccount": { - "message": "Sukurti paskyrą" - }, "createPassword": { "message": "Sukurti slaptažodį" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "Ar norėtumėte pridėti šiuos žetonus?" }, - "lineatestnet": { + "lineaGoerli": { "message": "„Linea“ bandomasis tinklas" }, "links": { @@ -581,9 +572,6 @@ "readdToken": { "message": "Šį žetoną galite bet kada galite įtraukti ir vėl, tiesiog savo paskyros parinkčių meniu nueikite į „Įtraukti žetoną“." }, - "recents": { - "message": "Naujausi" - }, "recipientAddressPlaceholder": { "message": "Ieška, viešieji adresai (0x) arba ENS" }, @@ -807,16 +795,9 @@ "transfer": { "message": "Pervesti" }, - "transferBetweenAccounts": { - "message": "Perkėlimas tarp savo paskyrų" - }, "transferFrom": { "message": "Pervedimas iš" }, - "troubleTokenBalances": { - "message": "Susidurta su sunkumais įkeliant jūsų žetonų likučius. Galite juos peržiūrėti ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Bandyti dar kartą" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index 2ffc66e5c..bb8382426 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Atpakaļ" }, - "backToAll": { - "message": "Atgriezties pie visiem" - }, "backupApprovalInfo": { "message": "Šis slepenais kods ir nepieciešams, lai atjaunotu jūsu maku, ja pazaudējat ierīci, aizmirstat paroli, jāpārinstalē MetaMask vai vēlaties piekļūt makam no citas ierīces." }, @@ -154,16 +151,13 @@ "connect": { "message": "Pievienošana" }, - "connectHardwareWallet": { - "message": "Pieslēgt aparatūras maku" - }, "connectingTo": { "message": "Pieslēdzas $1" }, "connectingToGoerli": { "message": "Pieslēdzas Goerli testa tīklam" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Pieslēdzas Linea Goerli testa tīklam" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Izveidot" }, - "createAccount": { - "message": "Izveidot kontu" - }, "createPassword": { "message": "Izveidot paroli" }, @@ -411,7 +402,7 @@ "likeToImportTokens": { "message": "Vai vēlaties pievienot šos marķierus?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli testa tīkls" }, "links": { @@ -577,9 +568,6 @@ "readdToken": { "message": "Jūs varat šo marķieri iestatīt atpakaļ nākotnē, konta opciju izvēlnē atverot \"Pievienot marķieri\"." }, - "recents": { - "message": "Nesenie" - }, "recipientAddressPlaceholder": { "message": "Meklēšana, publiskā adrese (0x) vai ENS" }, @@ -803,16 +791,9 @@ "transfer": { "message": "Pārsūtīt" }, - "transferBetweenAccounts": { - "message": "Pārsūtīt starp saviem kontiem" - }, "transferFrom": { "message": "Nosūtīt no" }, - "troubleTokenBalances": { - "message": "Mums neizdevās ielādēt marķieru bilanci. Varat tos skatīt", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Mēģināt vēlreiz" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 5c304cd47..546834ed4 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Kembali" }, - "backToAll": { - "message": "Kembali kepada Semua" - }, "backupApprovalInfo": { "message": "Kod rahsia ini diperlukan untuk memulihkan dompet anda sekiranya anda kehilangan peranti anda, terlupa kata laluan anda, perlu memasang semua MetaMask, atau mahu mengakses dompet anda menggunakan peranti lain." }, @@ -154,16 +151,13 @@ "connect": { "message": "Sambung" }, - "connectHardwareWallet": { - "message": "Sambungkan Dompet Perkakasan" - }, "connectingTo": { "message": "Menyambungkan kepada $1" }, "connectingToGoerli": { "message": "Menyambung kepada Rangkaian Ujian Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Menyambung kepada Rangkaian Ujian Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Cipta" }, - "createAccount": { - "message": "Cipta Akaun" - }, "createPassword": { "message": "Cipta Kata Laluan" }, @@ -404,7 +395,7 @@ "likeToImportTokens": { "message": "Adakah anda ingin menambah token ini?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Rangkaian Ujian Linea Goerli" }, "links": { @@ -561,9 +552,6 @@ "readdToken": { "message": "Anda boleh tambah token ini kembali pada masa depan dengan pergi ke \"Tambah token\" di dalam menu pilihan akaun anda." }, - "recents": { - "message": "Baru-baru ini" - }, "recipientAddressPlaceholder": { "message": "Cari, alamat awam (0x), atau ENS" }, @@ -784,16 +772,9 @@ "transfer": { "message": "Pindah" }, - "transferBetweenAccounts": { - "message": "Pindahkan antara akaun saya" - }, "transferFrom": { "message": "Pindah Daripada" }, - "troubleTokenBalances": { - "message": "Kami menghadapi masalah memuatkan baki token anda. Anda boleh melihatnya", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Cuba lagi" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index fc8a1e7fd..00bfdf749 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -69,9 +69,6 @@ "create": { "message": "creëren" }, - "createAccount": { - "message": "Account aanmaken" - }, "decimal": { "message": "Decimalen van precisie" }, @@ -313,10 +310,6 @@ "total": { "message": "Totaal" }, - "troubleTokenBalances": { - "message": "We hadden problemen bij het laden van uw tokenbalansen. Je kunt ze bekijken", - "description": "Followed by a link (here) to view token balances" - }, "typePassword": { "message": "Typ uw wachtwoord" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index 2df0252ef..d1ec686b6 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Tilbake" }, - "backToAll": { - "message": "Tilbake til alt" - }, "backupApprovalInfo": { "message": "Denne hemmelige koden behøves for å gjenopprette lommeboken din i tilfelle du mister enheten din, glemmer passordet ditt, trenger å re-installere MetaMask, eller ønsker å få tilgang til lommeboken din på en annen enhet." }, @@ -151,16 +148,13 @@ "connect": { "message": "Koble til" }, - "connectHardwareWallet": { - "message": "Koble til maskinvarelommebok" - }, "connectingTo": { "message": "Forbinder til $1 " }, "connectingToGoerli": { "message": "Oppretter forbindelse med Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Oppretter forbindelse med Linea Goerli Test Network" }, "connectingToMainnet": { @@ -190,9 +184,6 @@ "create": { "message": "Opprett" }, - "createAccount": { - "message": "Opprett konto" - }, "createPassword": { "message": "Opprett passord " }, @@ -565,9 +556,6 @@ "readdToken": { "message": "Du kan legge til dette tokenet igjen i fremtiden ved å gå til \"Legg til token\" i menyen for kontoalternativer." }, - "recents": { - "message": "Nylige" - }, "recipientAddressPlaceholder": { "message": "Søk, offentlig adresse (0x) eller ENS" }, @@ -782,16 +770,9 @@ "transfer": { "message": "Overfør" }, - "transferBetweenAccounts": { - "message": "Overfør mellom kontoene mine" - }, "transferFrom": { "message": "Overfør fra" }, - "troubleTokenBalances": { - "message": "Vi hadde problemer med å laste inn sjetongsaldoen din. Du kan se dem", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Prøv igjen" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index c6e24bd52..0aa23709a 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -42,10 +42,6 @@ "addContact": { "message": "Magdagdag ng contact" }, - "addCustomTokenByContractAddress": { - "message": "Walang makitang token? Puwede kang manual na magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Makikita ang mga address ng kontrata ng token sa $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Bibigyang-daan nito na magamit ang network na ito sa MetaMask." }, @@ -75,6 +71,10 @@ "addToken": { "message": "Magdagdag ng Token" }, + "addTokenByContractAddress": { + "message": "Walang makitang token? Puwede kang manual na magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Makikita ang mga address ng kontrata ng token sa $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "advanced": { "message": "Advanced" }, @@ -161,9 +161,6 @@ "back": { "message": "Bumalik" }, - "backToAll": { - "message": "Bumalik sa Lahat" - }, "backupApprovalInfo": { "message": "Ang lihim na code na ito ay kinakailangan para ma-recover ang iyong wallet sakaling maiwala mo ang iyong device, makalimutan ang iyong password, kailanganin mong i-install ulit ang MetaMask, o gusto mong i-access ang iyong wallet sa ibang device." }, @@ -252,9 +249,6 @@ "connectAccountOrCreate": { "message": "Ikonekta ang account o gumawa ng bago" }, - "connectHardwareWallet": { - "message": "Ikonekta ang Hardware Wallet" - }, "connectManually": { "message": "Manu-manong kumonekta sa kasalukuyang site" }, @@ -311,7 +305,7 @@ "connectingToGoerli": { "message": "Kumokonekta sa Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Kumokonekta sa Linea Goerli Test Network" }, "connectingToMainnet": { @@ -356,9 +350,6 @@ "create": { "message": "Gumawa" }, - "createAccount": { - "message": "Gumawa ng Account" - }, "createPassword": { "message": "Gumawa ng Password" }, @@ -804,7 +795,7 @@ "likeToImportTokens": { "message": "Gusto mo bang idagdag ang mga token na ito?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli Test Network" }, "links": { @@ -1135,9 +1126,6 @@ "receive": { "message": "Tumanggap" }, - "recents": { - "message": "Mga Kamakailan" - }, "recipientAddressPlaceholder": { "message": "Maghanap, pampublikong address (0x), o ENS" }, @@ -1394,9 +1382,6 @@ "stateLogsDescription": { "message": "Naglalaman ang mga log ng estado ng iyong mga address ng pampublikong account at ipinadalang transaksyon." }, - "statusConnected": { - "message": "Nakakonekta" - }, "statusNotConnected": { "message": "Hindi nakakonekta" }, @@ -1787,9 +1772,6 @@ "transfer": { "message": "Mag-transfer" }, - "transferBetweenAccounts": { - "message": "Mag-transfer sa iba't ibang account ko" - }, "transferFrom": { "message": "Mag-transfer Mula Kay/Sa" }, @@ -1797,10 +1779,6 @@ "message": "Nagkaproblema kami sa pagkonekta sa iyong $1, subukang suriin ang $2 at subukan ulit.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" }, - "troubleTokenBalances": { - "message": "Nagkaproblema kami sa pag-load ng mga balanse ng iyong token. Puwede mong tingnan ang mga iyon ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Subukan ulit" }, diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 348798703..eed32951c 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Wstecz" }, - "backToAll": { - "message": "Wróć do wszystkich" - }, "backupApprovalInfo": { "message": "Ten tajny kod jest wymagany do odzyskania portfela w przypadku zgubienia urządzenia, zapomnienia hasła, ponownego zainstalowania MetaMask lub potrzeby uzyskania dostępu do portfela na innym urządzeniu." }, @@ -154,16 +151,13 @@ "connect": { "message": "Połącz" }, - "connectHardwareWallet": { - "message": "Podłącz portfel sprzętowy" - }, "connectingTo": { "message": "Łączenie z $1" }, "connectingToGoerli": { "message": "Łączenie z siecią testową Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Łączenie z siecią testową Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Utwórz" }, - "createAccount": { - "message": "Utwórz konto" - }, "createPassword": { "message": "Utwórz hasło" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "Czy chcesz dodać te tokeny?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Sieć testowa Linea Goerli" }, "links": { @@ -575,9 +566,6 @@ "readdToken": { "message": "Możesz później ponownie dodać ten token poprzez \"Dodaj token\" w opcjach menu swojego konta." }, - "recents": { - "message": "Ostatnie" - }, "recipientAddressPlaceholder": { "message": "Szukaj, adres publiczny (0x) lub ENS" }, @@ -795,16 +783,9 @@ "transfer": { "message": "Przelew" }, - "transferBetweenAccounts": { - "message": "Przelew między moimi kontami" - }, "transferFrom": { "message": "Przelew z" }, - "troubleTokenBalances": { - "message": "Wystąpił problem z załadowaniem informacji o Twoich tokenach. Można je zobaczyć ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Spróbuj ponownie" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 1a35b57fd..6289de75b 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Adicionar token personalizado" }, - "addCustomTokenByContractAddress": { - "message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Isso permitirá que essa rede seja usada dentro da MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Adicionar token" }, + "addTokenByContractAddress": { + "message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Endereço" }, @@ -359,12 +359,6 @@ "message": "Permitir acesso a todos os seus $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Aprovar e instalar" - }, - "approveAndUpdate": { - "message": "Aprovar e atualizar" - }, "approveButtonText": { "message": "Aprovar" }, @@ -429,9 +423,6 @@ "back": { "message": "Voltar" }, - "backToAll": { - "message": "Voltar para todos" - }, "backup": { "message": "Backup" }, @@ -557,13 +548,6 @@ "message": "Para $1 uma transação, a taxa de gás deve ser aumentada em pelo menos 10% para que seja reconhecida pela rede.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Cancelar swap por ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Cancelar swap gratuitamente" - }, "cancelled": { "message": "Cancelada" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Conectar conta ou criar nova" }, - "connectHardwareWallet": { - "message": "Conectar carteira de hardware" - }, "connectManually": { "message": "Conectar manualmente ao site atual" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Conectando à rede de testes Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Conectando à rede de teste Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Criar" }, - "createAccount": { - "message": "Criar conta" - }, "createNewWallet": { "message": "Criar uma nova carteira" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada." }, - "enableSmartTransactions": { - "message": "Ativar transações inteligentes" - }, "enableToken": { "message": "ativar $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Qualquer pessoa pode criar um token com qualquer nome, incluindo versões falsas de tokens existentes. Adicione e negocie por sua conta e risco!" }, - "importTokens": { - "message": "importar tokens" - }, "importTokensCamelCase": { "message": "Importar tokens" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Gostaria de adicionar estes tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Rede de teste Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Solicite aqui" }, - "missingToken": { - "message": "Não está vendo seu token?" - }, "moreComingSoon": { "message": "Mais em breve..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Não há gás suficiente" }, - "notifications": { - "message": "Notificações" - }, "notifications10ActionText": { "message": "Visite nas configurações", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Receber" }, - "recents": { - "message": "Recentes" - }, "recipientAddressPlaceholder": { "message": "Pesquisa, endereço público (0x) ou ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Selecione essa opção para mostrar o campo de dados hexadecimais na tela de envio" }, - "showHide": { - "message": "Mostrar/ocultar" - }, "showIncomingTransactions": { "message": "Mostrar transações recebidas" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Compreendo que, até fazer o backup da minha Frase de Recuperação Secreta, poderei perder minhas contas e todos os ativos contidos nela." }, - "smartTransaction": { - "message": "Transação inteligente" - }, "snapContent": { "message": "Esse conteúdo vem de $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Instalar snap" }, "snapInstallWarningCheck": { - "message": "Para confirmar que você entende, marque a caixa." + "message": "Para confirmar que você entende, marque a caixa.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Para confirmar que você entende, marque todas as caixas." + "message": "Para confirmar que você entende, marque todas as caixas.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Você está concedendo ao snap \"$1\" acesso à sua chave $2. Isso é irrevogável e concede a \"$1\" controle de suas contas e ativos $2. Certifique-se de que confia em \"$1\" antes de prosseguir.", @@ -3416,9 +3375,6 @@ "status": { "message": "Status" }, - "statusConnected": { - "message": "Conectado" - }, "statusNotConnected": { "message": "Não conectado" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Forte" }, - "stxAreHere": { - "message": "As transações inteligentes chegaram!" - }, "stxBenefit1": { "message": "Minimize os custos das transações" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Tente fazer sua swap novamente. Estaremos aqui para te proteger contra riscos semelhantes no futuro." }, - "stxDescription": { - "message": "As swaps na MetaMask ficaram muito mais inteligentes! A ativação de Transações Inteligentes permite que o MetaMask otimize programaticamente suas swaps para evitar:" - }, - "stxErrorNotEnoughFunds": { - "message": "Insuficiência de fundos para fazer uma transação inteligente." - }, - "stxErrorUnavailable": { - "message": "Indisponibilidade temporária de Transações Inteligentes." - }, "stxFailure": { "message": "Falha na troca" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Enviando seu swap de forma pública..." }, - "stxSubDescription": { - "message": "* A função de Transações Inteligentes tentará enviar a sua transação várias vezes de forma privada. Se todas as tentativas falharem, a transação será transmitida publicamente para garantir que sua Swap seja realizada com sucesso." - }, "stxSuccess": { "message": "Swap concluído!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Ativar/desativar solicitações eth_sign" }, - "toggleTestNetworks": { - "message": "$1 redes de teste", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Transferir" }, - "transferBetweenAccounts": { - "message": "Transferir entre minhas contas" - }, "transferFrom": { "message": "Transferir de" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "A MetaMask teve problemas para iniciar. Esse erro pode ser intermitente, por isso tente reiniciar a extensão." }, - "troubleTokenBalances": { - "message": "Tivemos um problema a carregar o balanço dos seus tokens. Pode vê-los em ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Ao conceder permissão, você estará autorizando que o $1 a seguir acesse seus fundos" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index 5dfacf001..f85a1ccd2 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -103,10 +103,6 @@ "addCustomToken": { "message": "Adicionar token personalizado" }, - "addCustomTokenByContractAddress": { - "message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Isso permitirá que essa rede seja usada dentro da MetaMask." }, @@ -139,6 +135,10 @@ "addToken": { "message": "Adicionar token" }, + "addTokenByContractAddress": { + "message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Endereço" }, @@ -250,9 +250,6 @@ "back": { "message": "Voltar" }, - "backToAll": { - "message": "Voltar para todos" - }, "backupApprovalInfo": { "message": "Esse código secreto é obrigatório para recuperar sua carteira caso você perca seu dispositivo, esqueça sua senha, precise reinstalar a MetaMask ou queira acessar sua carteira em outro dispositivo." }, @@ -389,9 +386,6 @@ "connectAccountOrCreate": { "message": "Conectar conta ou criar nova" }, - "connectHardwareWallet": { - "message": "Conectar carteira de hardware" - }, "connectManually": { "message": "Conectar manualmente ao site atual" }, @@ -448,7 +442,7 @@ "connectingToGoerli": { "message": "Conectando à rede de testes Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Conectando à rede de testes Linea Goerli" }, "connectingToMainnet": { @@ -499,9 +493,6 @@ "create": { "message": "Criar" }, - "createAccount": { - "message": "Criar conta" - }, "createNewWallet": { "message": "Criar uma nova carteira" }, @@ -1091,9 +1082,6 @@ "importTokenWarning": { "message": "Qualquer pessoa pode criar um token com qualquer nome, incluindo versões falsas de tokens existentes. Adicione e negocie por sua conta e risco!" }, - "importTokens": { - "message": "importar tokens" - }, "importTokensCamelCase": { "message": "Importar tokens" }, @@ -1257,7 +1245,7 @@ "likeToImportTokens": { "message": "Você gostaria de importar esses tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Rede de testes Linea Goerli" }, "link": { @@ -1351,9 +1339,6 @@ "missingNFT": { "message": "Não está vendo o seu NFT?" }, - "missingToken": { - "message": "Não está vendo o seu token?" - }, "mustSelectOne": { "message": "Selecione pelo menos 1 token." }, @@ -1812,9 +1797,6 @@ "receive": { "message": "Receber" }, - "recents": { - "message": "Recentes" - }, "recipientAddressPlaceholder": { "message": "Busca, endereço público (0x) ou ENS" }, @@ -2064,9 +2046,6 @@ "showHexDataDescription": { "message": "Selecione essa opção para mostrar o campo de dados hexadecimais na tela de envio" }, - "showHide": { - "message": "Mostrar/ocultar" - }, "showIncomingTransactions": { "message": "Mostrar transações recebidas" }, @@ -2168,9 +2147,6 @@ "stateLogsDescription": { "message": "Os registros de estado contêm os endereços da sua conta pública e as transações enviadas." }, - "statusConnected": { - "message": "Conectado" - }, "statusNotConnected": { "message": "Não conectado" }, @@ -2549,10 +2525,6 @@ "message": "Para: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, - "toggleTestNetworks": { - "message": "$1 redes de teste", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -2682,9 +2654,6 @@ "transfer": { "message": "Transferir" }, - "transferBetweenAccounts": { - "message": "Transferência entre minhas contas" - }, "transferFrom": { "message": "Transferir de" }, @@ -2692,10 +2661,6 @@ "message": "Tivemos dificuldade para conectar-nos à sua $1. Revise $2 e tente novamente.", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" }, - "troubleTokenBalances": { - "message": "Tivemos dificuldade para carregar os saldos do seu token. Você pode vê-los ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Ao conceder permissão, você estará autorizando que o $1 a seguir acesse seus fundos" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 2ecf4a992..9e64935a9 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Înapoi" }, - "backToAll": { - "message": "Înapoi la toate" - }, "backupApprovalInfo": { "message": "Acest cod secret este necesar pentru recuperarea portofelului în cazul în care pierdeți dispozitivul, uitați parola, trebuie să reinstalați MetaMask sau vreți să accesați portofelul de pe alt dispozitiv." }, @@ -154,16 +151,13 @@ "connect": { "message": "Conectează-te" }, - "connectHardwareWallet": { - "message": "Conectare portofel hardware" - }, "connectingTo": { "message": "Se conectează la $1" }, "connectingToGoerli": { "message": "Se conectează la rețeaua de test Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Se conectează la rețeaua de test Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Creați" }, - "createAccount": { - "message": "Creați cont" - }, "createPassword": { "message": "Creare parolă" }, @@ -405,7 +396,7 @@ "likeToImportTokens": { "message": "Adăugați aceste indicative?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Rețea de test Linea Goerli" }, "links": { @@ -568,9 +559,6 @@ "readdToken": { "message": "Puteți adăuga din nou acest indicativ în viitor accesând „Adăugați indicativ” din meniul de opțiuni al contului dvs." }, - "recents": { - "message": "Recente" - }, "recipientAddressPlaceholder": { "message": "Căutare, adresa publică (0x) sau ENS" }, @@ -788,16 +776,9 @@ "transactionUpdated": { "message": "Tranzacție actualizată la $2." }, - "transferBetweenAccounts": { - "message": "Transferă între conturile mele" - }, "transferFrom": { "message": "Transferați de la" }, - "troubleTokenBalances": { - "message": "Am avut dificultăți cu încărcarea soldurilor dvs. de tokenuri. Le puteți vizualiza", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Încearcă din nou" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 77b9d7f8d..98143fa7a 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Добавить пользовательский токен" }, - "addCustomTokenByContractAddress": { - "message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Это позволит использовать эту сеть в MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Добавить токен" }, + "addTokenByContractAddress": { + "message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Адрес" }, @@ -359,12 +359,6 @@ "message": "Разрешить доступ ко всем вашим $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Одобрить и установить" - }, - "approveAndUpdate": { - "message": "Утвердить и обновить" - }, "approveButtonText": { "message": "Одобрить" }, @@ -429,9 +423,6 @@ "back": { "message": "Назад" }, - "backToAll": { - "message": "Назад ко всем" - }, "backup": { "message": "Резервное копирование" }, @@ -557,13 +548,6 @@ "message": "Чтобы $1 транзакции плата за газ должна быть увеличена как минимум на 10%. Это позволит обеспечить прием транзакции сетью.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Отменить обмен на ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Отменить обмен бесплатно" - }, "cancelled": { "message": "Отменено" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Подключите счет или создайте новый" }, - "connectHardwareWallet": { - "message": "Подключить аппаратный кошелек" - }, "connectManually": { "message": "Подключиться к текущему сайту вручную" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Подключение к тестовой сети Goerli..." }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Подключение к тестовой сети Linea..." }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Создать" }, - "createAccount": { - "message": "Создать счет" - }, "createNewWallet": { "message": "Создать новый кошелек" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Используйте API OpenSea для получения данных NFT. Для автоматического обнаружения NFT используется API OpenSea, и такое обнаружение будет недоступно, если этот API отключен." }, - "enableSmartTransactions": { - "message": "Включить смарт-транзакции" - }, "enableToken": { "message": "активирует для $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Кто угодно может создать токен с любым именем, включая поддельные версии существующих токенов. Добавляйте и торгуйте на свой страх и риск!" }, - "importTokens": { - "message": "импорт токенов" - }, "importTokensCamelCase": { "message": "Импорт токенов" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Вы хотели бы импортировать эти токены?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Тестовая сеть Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Запросите здесь" }, - "missingToken": { - "message": "Не видите свой токен?" - }, "moreComingSoon": { "message": "Скоро появится больше..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Недостаточно газа" }, - "notifications": { - "message": "Уведомления" - }, "notifications10ActionText": { "message": "Смотреть в настройках", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Получить" }, - "recents": { - "message": "Недавние" - }, "recipientAddressPlaceholder": { "message": "Поиск, открытый адрес (0x) или ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Выберите эту опцию, чтобы отобразить поле шестнадцатеричных данных на экране отправки" }, - "showHide": { - "message": "Показать/скрыть" - }, "showIncomingTransactions": { "message": "Показать входящие транзакции" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Я понимаю, что, если я не создам резервную копию своей секретной фразы для восстановления, я могу потерять доступ ко всем своим счетам и всем средствам на них." }, - "smartTransaction": { - "message": "Смарт-транзакция" - }, "snapContent": { "message": "Этот контент поступает от $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Установить снап" }, "snapInstallWarningCheck": { - "message": "Чтобы подтвердить, что вы понимаете, отметьте все." + "message": "Чтобы подтвердить, что вы понимаете, отметьте все.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Чтобы подтвердить, что вы понимаете, отметьте все ячейки." + "message": "Чтобы подтвердить, что вы понимаете, отметьте все ячейки.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Вы предоставляете ключ доступа $2 к привязке \"$1\". Это действие нельзя отменить, и оно предоставляет \"$1\" управление всеми счетами и активами $2. Перед тем как продолжить, убедитесь, что доверяете \"$1\".", @@ -3416,9 +3375,6 @@ "status": { "message": "Статус" }, - "statusConnected": { - "message": "Подключено" - }, "statusNotConnected": { "message": "Не подключено" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Сильный" }, - "stxAreHere": { - "message": "Появились смарт-транзакции!" - }, "stxBenefit1": { "message": "Минимизируйте транзакционные издержки" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Попробуйте обменять еще раз. Мы готовы защитить вас от подобных рисков в следующий раз." }, - "stxDescription": { - "message": "Функция обмена в MetaMask стала намного умнее! Включение смарт-транзакций позволит MetaMask программно оптимизировать ваш обмен, чтобы помочь:" - }, - "stxErrorNotEnoughFunds": { - "message": "Недостаточно средств для смарт-транзакции." - }, - "stxErrorUnavailable": { - "message": "Смарт-транзакции временно недоступны." - }, "stxFailure": { "message": "Обмен не удался" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Публичная отправка вашей операции обмена..." }, - "stxSubDescription": { - "message": "* Смарт-транзакции попытаются несколько раз отправить вашу транзакцию в конфиденциальном порядке. Если все попытки не увенчаются успехом, транзакция будет показана публично, чтобы гарантировать успешное завершение вашего обмена." - }, "stxSuccess": { "message": "Обмен завершен!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Переключить запросы eth_sign" }, - "toggleTestNetworks": { - "message": "$1 тестовые сети", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Токен" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Перевести" }, - "transferBetweenAccounts": { - "message": "Перевод между моими счетами" - }, "transferFrom": { "message": "Перевести с" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "У MetaMask возникли проблемы с запуском. Эта ошибка может быть непостоянной, поэтому попробуйте перезапустить расширение." }, - "troubleTokenBalances": { - "message": "У нас возникли проблемы с загрузкой вашего баланса токенов. Вы можете просмотреть их ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Давая разрешение, вы предоставляете следующему $1 доступ к вашим средствам." }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 6b0057045..a9e8111ba 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -90,9 +90,6 @@ "back": { "message": "Zpět" }, - "backToAll": { - "message": "Späť na všetko" - }, "backupApprovalInfo": { "message": "Tento tajný kód je potrebný na obnovenie peňaženky v prípade straty zariadenia, zabudnutia hesla, preinštalovania MetaMask alebo prístupu k peňaženke na inom zariadení." }, @@ -148,16 +145,13 @@ "connect": { "message": "Pripojenie" }, - "connectHardwareWallet": { - "message": "Pripojiť hardvérovú peňaženku" - }, "connectingTo": { "message": "Pripája sa k $1" }, "connectingToGoerli": { "message": "Pripája sa k testovacej sieti Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Pripája sa k testovacej sieti Linea Goerli" }, "connectingToMainnet": { @@ -187,9 +181,6 @@ "create": { "message": "Vytvořit" }, - "createAccount": { - "message": "Vytvořit účet" - }, "createPassword": { "message": "Vytvoriť heslo" }, @@ -402,7 +393,7 @@ "likeToImportTokens": { "message": "Chcete přidat tyto tokeny?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Testovacia sieť Linea Goerli" }, "links": { @@ -553,9 +544,6 @@ "readdToken": { "message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu." }, - "recents": { - "message": "Posledné" - }, "recipientAddressPlaceholder": { "message": "Vyhľadávať verejnú adresu (0x) alebo ENS" }, @@ -773,16 +761,9 @@ "transfer": { "message": "Prevod" }, - "transferBetweenAccounts": { - "message": "Prevod medzi mojimi účtami" - }, "transferFrom": { "message": "Presun z" }, - "troubleTokenBalances": { - "message": "Měli jsme problém s načtením vašich tokenových zůstatků. Můžete je vidět ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Skúsiť znova" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index bbc88645e..3f5d61814 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Nazaj" }, - "backToAll": { - "message": "Nazaj na vse" - }, "backupApprovalInfo": { "message": "Ta skrivna koda je potrebna za obnovitev denarnice, če izgubite napravo, pozabite geslo, ponovno namestite MetaMask ali želite dostopati do denarnice v drugi napravi." }, @@ -154,16 +151,13 @@ "connect": { "message": "Poveži" }, - "connectHardwareWallet": { - "message": "Poveži strojno denarnico" - }, "connectingTo": { "message": "Povezovanje na $1" }, "connectingToGoerli": { "message": "Povezovanje na testno omrežje Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Povezovanje na testno omrežje Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Ustvari" }, - "createAccount": { - "message": "Ustvari račun" - }, "createPassword": { "message": "Ustvari geslo" }, @@ -409,7 +400,7 @@ "likeToImportTokens": { "message": "Želite dodati te žetone?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Testno omrežje Linea Goerli" }, "links": { @@ -569,9 +560,6 @@ "readdToken": { "message": "Ta žeton lahko dodate tudi kasneje z uporabo gumba “Dodaj žeton” v možnostih vašega računa." }, - "recents": { - "message": "Nedavno" - }, "recipientAddressPlaceholder": { "message": "Iskanje, javni naslov (0x) ali ENS" }, @@ -795,16 +783,9 @@ "transfer": { "message": "Prenesi" }, - "transferBetweenAccounts": { - "message": "Prenos med mojimi računi" - }, "transferFrom": { "message": "Prenesi od" }, - "troubleTokenBalances": { - "message": "Če imate težave pri ogledu zneskov žetonov si jih lahko ogledate ", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Poskusi znova" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index b95229b50..858eb96f9 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Назад" }, - "backToAll": { - "message": "Povratak na Sve" - }, "backupApprovalInfo": { "message": "Potreban je tajni kôd za oporavak vašeg novčanika u slučaju da izgubite uređaj, zaboravite lozinku, morate ponovo da instalirate MetaMask ili želite da pristupite novčaniku sa drugog uređaja." }, @@ -151,16 +148,13 @@ "connect": { "message": "Повезивање" }, - "connectHardwareWallet": { - "message": "Povežite Hardverski novčanik" - }, "connectingTo": { "message": "Povezuje se na $1" }, "connectingToGoerli": { "message": "Povezuje se sa test mrežom Goerli " }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Povezuje se sa test mrežom Linea Goerli" }, "connectingToMainnet": { @@ -190,9 +184,6 @@ "create": { "message": "Направи" }, - "createAccount": { - "message": "Kreirajte nalog" - }, "createPassword": { "message": "Kreirajte lozinku" }, @@ -412,7 +403,7 @@ "likeToImportTokens": { "message": "Želite li da dodate ove tokene?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Test mreža Linea Goerli" }, "links": { @@ -572,9 +563,6 @@ "readdToken": { "message": "U budućnosti možete vratiti ovaj token tako što ćete otvoriti „Dodaj token“ u meniju opcija vašeg naloga." }, - "recents": { - "message": "Skorašnje" - }, "recipientAddressPlaceholder": { "message": "Pretraga, javna adresa (0x) ili ENS" }, @@ -795,16 +783,9 @@ "transfer": { "message": "Prenos" }, - "transferBetweenAccounts": { - "message": "Prenos između mojih naloga" - }, "transferFrom": { "message": "Prebacite iz" }, - "troubleTokenBalances": { - "message": "Imali smo problema sa učitavanjem bilansa Vaših tokena. Možete ih pogledati", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Пробај поново" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 75a70e467..b9c1f3ea7 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Bakåt" }, - "backToAll": { - "message": "Tillbaka till Alla" - }, "backupApprovalInfo": { "message": "Den hemliga koden krävs för att komma åt din plånbok ifall du tappar bort din enhet, glömmer ditt lösenord, behöver installera om MetaMask eller om du vill komma åt din plånbok på en annan enhet." }, @@ -148,16 +145,13 @@ "connect": { "message": "Ansluta" }, - "connectHardwareWallet": { - "message": "Anslut hårdvaruplånbok" - }, "connectingTo": { "message": "Ansluter till $1" }, "connectingToGoerli": { "message": "Ansluter till Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Ansluter till Linea Goerli Test Network" }, "connectingToMainnet": { @@ -187,9 +181,6 @@ "create": { "message": "Skapa" }, - "createAccount": { - "message": "Skapa konto" - }, "createPassword": { "message": "Skapa lösenord" }, @@ -405,7 +396,7 @@ "likeToImportTokens": { "message": "Vill du lägga till dessa tokens?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli testnätverk" }, "links": { @@ -565,9 +556,6 @@ "readdToken": { "message": "Du kan lägga till denna token i framtiden genom att välja \"Lägg till token\" i kontots alternativmeny." }, - "recents": { - "message": "Senaste" - }, "recipientAddressPlaceholder": { "message": "Sök, allmän adress (0x) eller ENS" }, @@ -782,16 +770,9 @@ "transfer": { "message": "Överföring" }, - "transferBetweenAccounts": { - "message": "Överför mellan mina konton" - }, "transferFrom": { "message": "Överför från" }, - "troubleTokenBalances": { - "message": "Vi hade problem med att ladda dina token-saldon. Du kan se dem", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Försök igen" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 5ec90f52f..0dbae2de3 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Nyuma" }, - "backToAll": { - "message": "Rudi kwenye Zote" - }, "backupApprovalInfo": { "message": "Msimbo huu wa siri unahitajika ili kurejesha waleti yako ikitokea umepoteza kifaa chako, umesahau nenosiri lako, umelazimika kusakinisha MetaMask, au unataka kufikia waleti yako kwenye kifaa kingine." }, @@ -148,16 +145,13 @@ "connect": { "message": "Unganisha" }, - "connectHardwareWallet": { - "message": "Unganisha Waleti ya Programu Maunzi" - }, "connectingTo": { "message": "Inaunganisha kwenye $1" }, "connectingToGoerli": { "message": "Inaunganisha kwenye Mtandao wa Majaribio wa Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Inaunganisha kwenye Mtandao wa Majaribio wa Linea Goerli" }, "connectingToMainnet": { @@ -187,9 +181,6 @@ "create": { "message": "Unda" }, - "createAccount": { - "message": "Fungua Akaunti" - }, "createPassword": { "message": "Unda Nenosiri" }, @@ -402,7 +393,7 @@ "likeToImportTokens": { "message": "Je, ungependa kuongeza vianzio hivi?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Mtandao wa Majaribio wa Linea Goerli" }, "links": { @@ -559,9 +550,6 @@ "readdToken": { "message": "Unaweza kuongeza tena kianzio hiki hapo baadaye kwa kwenda kwenye \"Ongeza kianzio\" kwenye machaguo yako ya menyu ya akaunti." }, - "recents": { - "message": "Za hivi karibuni" - }, "recipientAddressPlaceholder": { "message": "Tafuta, anwani za umma (0x), au ENS" }, @@ -785,16 +773,9 @@ "transfer": { "message": "Kutuma" }, - "transferBetweenAccounts": { - "message": "Kutuma baina ya akaunti zangu" - }, "transferFrom": { "message": "Tuma Kutoka" }, - "troubleTokenBalances": { - "message": "Tulipata shida kupakia salio lako la kianzio. Unaweza kuliona", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Jaribu tena" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index 53b5a906e..0f042e0ac 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -96,9 +96,6 @@ "create": { "message": "உருவாக்கு" }, - "createAccount": { - "message": "உங்கள் கணக்கை துவங்குங்கள்" - }, "custom": { "message": "மேம்பட்டவை" }, @@ -434,10 +431,6 @@ "transactionError": { "message": "பரிவர்த்தனை பிழை. விதிமுறை ஒப்பந்தத்தில் விதிவிலக்கு." }, - "troubleTokenBalances": { - "message": "உங்கள் டோக்கன் நிலுவைகளை ஏற்றுவதில் சிக்கல் ஏற்பட்டது. நீங்கள் அவர்களை பார்க்க முடியும்.", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "மீண்டும் முயல்க" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 23e312863..9bb799f8f 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -87,9 +87,6 @@ "create": { "message": "สร้าง" }, - "createAccount": { - "message": "สร้างบัญชี" - }, "currencyConversion": { "message": "การแปลงสกุลเงิน" }, @@ -398,13 +395,6 @@ "transactionDropped": { "message": "ธุรกรรมถูกยกเลิกเมื่อ $2" }, - "transferBetweenAccounts": { - "message": "โอนระหว่างบัญชีของฉัน" - }, - "troubleTokenBalances": { - "message": "เรามีปัญหาในการโหลดยอดโทเค็นคงเหลือของคุณ คุณสามารถดูได้ที่นี่", - "description": "Followed by a link (here) to view token balances" - }, "typePassword": { "message": "พิมพ์รหัสผ่านของคุณ" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index e0f0fd92d..df0b61a19 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Magdagdag ng Custom na Token" }, - "addCustomTokenByContractAddress": { - "message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng token contract ay matatagpuan sa $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Magpapahintulot ito sa network na ito na gamitin sa loob ng MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Magdagdag ng Token" }, + "addTokenByContractAddress": { + "message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng token contract ay matatagpuan sa $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Address" }, @@ -359,12 +359,6 @@ "message": "Payagan ang pag-access at paglipat ng lahat ng iyong $1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Aprubahan at I-install" - }, - "approveAndUpdate": { - "message": "Aprubahan at i-update" - }, "approveButtonText": { "message": "Aprubahan" }, @@ -429,9 +423,6 @@ "back": { "message": "Bumalik" }, - "backToAll": { - "message": "Bumalik sa Lahat" - }, "backup": { "message": "I-backup" }, @@ -557,13 +548,6 @@ "message": "Sa $1 na transaksyon ang singil sa gas ay dapat tumaas nang hindi bababa sa 10% para ito ay makilala ng network.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Kanselahin ang swap sa halagang ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Kanselahin ang swap nang libre" - }, "cancelled": { "message": "Nakansela" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Ikonekta ang account o gumawa ng bago" }, - "connectHardwareWallet": { - "message": "Ikonekta ang hardware wallet" - }, "connectManually": { "message": "Manu-manong kumonekta sa kasalukuyang site" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Kumokonekta sa Goerli Test Network" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Kumokonekta sa Linea Goerli test network" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Gumawa" }, - "createAccount": { - "message": "Gumawa ng account" - }, "createNewWallet": { "message": "Gumawa ng bagong wallet" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Gamitin ang API ng Opensea upang kunin ang NFT data. ang NFT auto-detection ay umaasa sa API ng OpenSea, at hindi magiging available kapag ito ay isinara." }, - "enableSmartTransactions": { - "message": "Payagan ang mga smart transaction" - }, "enableToken": { "message": "paganahin ang $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Sinumang ay maaaring lumikha ng token gamit ang alinmang pangalan, kabilang ang mga pekeng bersyon ng umiiral na mga token. Magdagdag at mag-trade sa sarili mong panganib!" }, - "importTokens": { - "message": "magdagdag ng mga token" - }, "importTokensCamelCase": { "message": "Mag-import ng mga Token" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Gusto mo bang idagdag ang mga token na ito?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli test network" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Hilingin dito" }, - "missingToken": { - "message": "Hindi mo ba nakikita ang iyong mga token?" - }, "moreComingSoon": { "message": "Marami pang parating..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Hindi Sapat ang Gas" }, - "notifications": { - "message": "Mga Abiso" - }, "notifications10ActionText": { "message": "Bisitahin sa mga setting", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Tumanggap" }, - "recents": { - "message": "Mga Kamakailan" - }, "recipientAddressPlaceholder": { "message": "Maghanap, pampublikong address (0x), o ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Piliin ito para ipakita ang field ng hex data sa screen ng pagpapadala" }, - "showHide": { - "message": "Ipakita/itago" - }, "showIncomingTransactions": { "message": "Ipakita ang Mga Papasok na Transaksyon" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Nauunawaan ko na hanggang sa i-back up ko ang aking Secret Recovery Phrase, maaari kong maiwala ang aking mga account at lahat ng kanilang mga asset." }, - "smartTransaction": { - "message": "Smart Transaction" - }, "snapContent": { "message": "Ang nilalamang ito ay nagmumula sa $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "I-install ang snap" }, "snapInstallWarningCheck": { - "message": "Para kumpirmahing naunawaan mo, tsekan lahat." + "message": "Para kumpirmahing naunawaan mo, tsekan lahat.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Para kumpirmahin na naiintindihan mo, lagyan ng tsek ang lahat ng kahon." + "message": "Para kumpirmahin na naiintindihan mo, lagyan ng tsek ang lahat ng kahon.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Binibigyan mo ang $2 ng key access sa snap na \"$1\". Hindi na ito mababawi at nagbibigay ito sa \"$1\" ng kontrol sa iyong mga $2 account at asset. Tiyaking pinagkakatiwalaan mo ang \"$1\" bago magpatuloy.", @@ -3416,9 +3375,6 @@ "status": { "message": "Istado" }, - "statusConnected": { - "message": "Nakakonekta" - }, "statusNotConnected": { "message": "Hindi konektado" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Mahirap" }, - "stxAreHere": { - "message": "Narito na ang mga Smart Transaction!" - }, "stxBenefit1": { "message": "Bawasan ang mga gastos sa transaksyon" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Subukan muli ang iyong pagpapalit. Narito kami para protektahan ka sa mga katulad na panganib sa susunod." }, - "stxDescription": { - "message": "Mas humusay pa ang mga Pagpapalit sa MetaMask! Papayagan ng Pagpapagana sa mga Smart Transaction ang MetaMask na pahusayin ang iyong Pagpapalit gamit ang program para makatulong sa: " - }, - "stxErrorNotEnoughFunds": { - "message": "Hindi sapat na pondo para sa smart transaction." - }, - "stxErrorUnavailable": { - "message": "Pansamantalang hindi available ang mga Smart Transaction." - }, "stxFailure": { "message": "Hindi matagumpay ang pag-swap" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Pampublikong isinusumite ang iyong Swap..." }, - "stxSubDescription": { - "message": "* Susubukan ng mga Smart Transaction na isumite nang pribado ang iyong transaksyon, maraming beses. Kapag nabigo ang lahat ng pagsubok, ipapakita sa publiko ang transaksyon upang matiyak na ang Pagpapalit ay naging matagupay." - }, "stxSuccess": { "message": "Nakumpleto ang pagpapalit!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "I-toggle ang mga kahilingan sa eth_sign" }, - "toggleTestNetworks": { - "message": "$1 na test network", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Mag-transfer" }, - "transferBetweenAccounts": { - "message": "Mag-transfer sa iba't ibang account ko" - }, "transferFrom": { "message": "Mag-transfer Mula Kay/Sa" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "Nagkaproblema ang MetaMask sa pagsisimula. Maaaring paulit-ulit ang error na ito, kaya subukang i-restart ang extension." }, - "troubleTokenBalances": { - "message": "Nagkaproblema kami sa pag-load ng mga balanse ng iyong token. Puwede mong tingnan ang mga iyon ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Sa pamamagitan ng pagbibigay ng pahintulot, pinapayagan mo ang sumusunod na $1 para ma-access ang pondo mo" }, @@ -4361,7 +4291,7 @@ "message": "Magsimula na tayo" }, "welcomeToMetaMaskIntro": { - "message": "Ang Metamask na pinagkakatiwalaan ng milyun-milyon ay isang ligtas na wallet na ginagawang accessible ang mundo ng web3 para sa lahat." + "message": "Ang MetaMask na pinagkakatiwalaan ng milyun-milyon ay isang ligtas na wallet na ginagawang accessible ang mundo ng web3 para sa lahat." }, "whatsNew": { "message": "Ano'ng bago", diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 0ca8ba63a..d629b046c 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Özel token ekle" }, - "addCustomTokenByContractAddress": { - "message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Bu, bu ağın MetaMas dahilinde kullanılmasına olanak tanıyacaktır." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Token ekle" }, + "addTokenByContractAddress": { + "message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Adres" }, @@ -359,12 +359,6 @@ "message": "Sahip olduğun tüm $1 için erişim ve transfer izni verilsin mi?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Onayla ve Yükle" - }, - "approveAndUpdate": { - "message": "Onayla ve güncelle" - }, "approveButtonText": { "message": "Onayla" }, @@ -429,9 +423,6 @@ "back": { "message": "Geri" }, - "backToAll": { - "message": "Tümüne Geri Git" - }, "backup": { "message": "Yedekle" }, @@ -557,13 +548,6 @@ "message": "İşlemi $1 için, gaz ücretinin ağ tarafından tanınması amacıyla en az %10 oranında artırılması gerekir.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "~$1 için swap işlemini iptal edin", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Swap işlemini ücretsiz iptal edin" - }, "cancelled": { "message": "İptal edildi" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Hesabı bağla ya da yeni hesap oluştur" }, - "connectHardwareWallet": { - "message": "Donanım Cüzdanı Bağla" - }, "connectManually": { "message": "Mevcut siteye manuel olarak bağlan" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Goerli Test Ağına Bağlanıyor" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Linea Goerli test ağına bağlanılıyor" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Oluştur" }, - "createAccount": { - "message": "Hesap Oluştur" - }, "createNewWallet": { "message": "Yeni bir cüzdan oluştur" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "NFT verilerini almak için OpenSea API'sini kullanın. NFT otomatik algılama OpenSea API'ye dayalıdır ve bu kapatılırsa mevcut olmayacaktır." }, - "enableSmartTransactions": { - "message": "Akıllı işlemleri etkinleştir" - }, "enableToken": { "message": "şunu etkinleştir: $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Herkes, mevcut tokenlerin sahte sürümleri de dahil olmak üzere herhangi bir ada sahip bir token oluşturabilir. Riski tamamen size ait olacak şekikde token ekleyin ve işlem yapın!" }, - "importTokens": { - "message": "tokenleri içe aktar" - }, "importTokensCamelCase": { "message": "Tokenleri içe aktar" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Bu tokenleri içe aktarmak ister misiniz?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli test ağı" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Buradan talep et" }, - "missingToken": { - "message": "Tokeninizi görmüyor musunuz?" - }, "moreComingSoon": { "message": "Daha fazlası çok yakında..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Yeterli gaz yok" }, - "notifications": { - "message": "Bildirimler" - }, "notifications10ActionText": { "message": "Ayarlarda ziyaret et", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2365,7 +2331,7 @@ "description": "Description on an action button that appears in the What's New popup. Tells the user that if they click it, they will go to our Advanced settings page." }, "notifications8DescriptionOne": { - "message": "MetaMask 10.4.0 sürümü itibariyle Kayıt Defteri cihazınızı Metamask'e bağlamak için artık Ledger Live'e ihtiyacınız yok.", + "message": "MetaMask 10.4.0 sürümü itibariyle Kayıt Defteri cihazınızı MetaMask'e bağlamak için artık Ledger Live'e ihtiyacınız yok.", "description": "Description of a notification in the 'See What's New' popup. Describes changes for how Ledger Live is no longer needed to connect the device." }, "notifications8DescriptionTwo": { @@ -2524,7 +2490,7 @@ "message": "Hemen erişim sağlamak için tarayıcı uzantısı simgesine tıklayın" }, "onboardingPinExtensionLabel": { - "message": "Metamask'i sabitle" + "message": "MetaMask'i sabitle" }, "onboardingPinExtensionStep1": { "message": "1" @@ -2817,9 +2783,6 @@ "receive": { "message": "Al" }, - "recents": { - "message": "Son Kullanılanlar" - }, "recipientAddressPlaceholder": { "message": "Ara, genel adres (0x) veya ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Gönder ekranında on altılık veri alanını göstermek için bunu seçin" }, - "showHide": { - "message": "Göster/gizle" - }, "showIncomingTransactions": { "message": "Gelen işlemleri göster" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Gizli Kurtarma İfademi yedekleyene kadar hesaplarımı ve tüm varlıkları kaybedebileceğimi anlıyorum." }, - "smartTransaction": { - "message": "Akıllı işlem" - }, "snapContent": { "message": "Bu içerik $1 kaynaklıdır", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Snap'i yükle" }, "snapInstallWarningCheck": { - "message": "Anladığını doğrulamak için kutucuğu işaretle." + "message": "Anladığını doğrulamak için kutucuğu işaretle.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Anladığınızı kontrol etmek için tüm kutuları işaretleyin." + "message": "Anladığınızı kontrol etmek için tüm kutuları işaretleyin.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "\"$1\" için $2 anahtar erişimi veriyorsunuz. Bu iptal edilemez ve $2 hesaplarınıza ve varlıklarınıza \"$1\" kontrolü verir. İlerlemeden önce \"$1\" alanına güvendiğinizden emin olun.", @@ -3416,9 +3375,6 @@ "status": { "message": "Durum" }, - "statusConnected": { - "message": "Bağlandı" - }, "statusNotConnected": { "message": "Bağlanmadı" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Güçlü" }, - "stxAreHere": { - "message": "Akıllı İşlemler burada!" - }, "stxBenefit1": { "message": "İşlem maliyetlerini en aza indir" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Takasını tekrar dene. Bir dahaki sefere seni benzer risklere karşı korumak için burada olacağız." }, - "stxDescription": { - "message": "MetaMask Swapları artık çok daha akıllı! Akıllı İşlemleri, MetaMask'in renklerine yardımcı olmak için Swap'ını programlı olarak optimize etme bölümünden yararlanmak:" - }, - "stxErrorNotEnoughFunds": { - "message": "Akıllı işlem için yeterli para yok." - }, - "stxErrorUnavailable": { - "message": "Akıllı İşlemler geçici olarak kullanılamıyor." - }, "stxFailure": { "message": "Takas başarısız oldu" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Swap işlemin herkese açık olarak gönderiliyor..." }, - "stxSubDescription": { - "message": "* Akıllı İşlemler, işlemini birden çok kez özel olarak göndermeye çalışır. Tüm denemeler başarısız olursa Takas'ının başarılı bir şekilde gerçekleşmesini sağlamak için işlem herkese açık olarak yayınlanacaktır." - }, "stxSuccess": { "message": "Takas tamamlandı!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "eth_sign taleplerini değiştirin" }, - "toggleTestNetworks": { - "message": "Test ağlarını $1", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Transfer et" }, - "transferBetweenAccounts": { - "message": "Hesaplarım arası transfer" - }, "transferFrom": { "message": "Transfer kaynağı:" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask başlatılırken bir sorun oldu. Bu hata sürekli bir hata olmayabilir; bu yüzden uzantıyı yeniden başlatmayı dene." }, - "troubleTokenBalances": { - "message": "Token bakiyeleriniz yüklenirken sorun yaşadık. Burada görüntüleyebilirsiniz ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "İzin verdiğinizde paranıza aşağıdaki $1 erişimine izin verirsiniz" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index acfaf09ab..08288b336 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -93,9 +93,6 @@ "back": { "message": "Назад" }, - "backToAll": { - "message": "Повернутися до всіх" - }, "backupApprovalInfo": { "message": "Цей секретний код буде потрібен, щоб відновити ваш гаманець у разі втрати вашого пристрою, забуття паролю, потреби у перевстановленні MetaMask або виникнення бажання отримати доступ до вашого гаманця з іншого пристрою." }, @@ -154,16 +151,13 @@ "connect": { "message": "Під’єднатися" }, - "connectHardwareWallet": { - "message": "Приєднайте апаратний гаманець" - }, "connectingTo": { "message": "Під'єднуємось до $1" }, "connectingToGoerli": { "message": "Підключення до тестової мережі Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Підключення до тестової мережі Linea Goerli" }, "connectingToMainnet": { @@ -193,9 +187,6 @@ "create": { "message": "Створити" }, - "createAccount": { - "message": "Створити обліковий запис" - }, "createPassword": { "message": "Створити пароль" }, @@ -415,7 +406,7 @@ "likeToImportTokens": { "message": "Ви б хотіли додати ці токени?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Тестова мережа Linea Goerli" }, "links": { @@ -581,9 +572,6 @@ "readdToken": { "message": "Ви можете знову додати цей токен у меню облікового запису у розділі “Додати токен”. " }, - "recents": { - "message": "Останні" - }, "recipientAddressPlaceholder": { "message": "Пошук, публічна адреса (0x), або ENS" }, @@ -807,16 +795,9 @@ "transfer": { "message": "Передати" }, - "transferBetweenAccounts": { - "message": "Передати між моїми обліковими записами" - }, "transferFrom": { "message": "Передати від" }, - "troubleTokenBalances": { - "message": "В нас виникли складнощі при завантаженні ваших залишків токенів. Ви можете переглянути їх", - "description": "Followed by a link (here) to view token balances" - }, "tryAgain": { "message": "Повторити" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index ab97ee5c7..270272b6c 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "Thêm token tùy chỉnh" }, - "addCustomTokenByContractAddress": { - "message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1.", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "Thao tác này sẽ cho phép sử dụng mạng này trong MetaMask." }, @@ -263,6 +259,10 @@ "addToken": { "message": "Thêm token" }, + "addTokenByContractAddress": { + "message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "Địa chỉ" }, @@ -359,12 +359,6 @@ "message": "Cấp quyền truy cập vào và chuyển tất cả $1 của bạn?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "Chấp nhận và cài đặt" - }, - "approveAndUpdate": { - "message": "Phê duyệt và cập nhật" - }, "approveButtonText": { "message": "Phê duyệt" }, @@ -429,9 +423,6 @@ "back": { "message": "Quay lại" }, - "backToAll": { - "message": "Quay lại toàn bộ danh sách" - }, "backup": { "message": "Sao lưu" }, @@ -557,13 +548,6 @@ "message": "Để $1 một giao dịch, phí gas phải tăng tối thiểu 10% để mạng nhận ra giao dịch này.", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "Hủy hoán đổi với giá ~$1", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "Hủy hoán đổi miễn phí" - }, "cancelled": { "message": "Đã hủy" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "Kết nối tài khoản hoặc tạo tài khoản mới" }, - "connectHardwareWallet": { - "message": "Kết nối với ví cứng" - }, "connectManually": { "message": "Kết nối thủ công với trang web hiện tại" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "Đang kết nối với mạng thử nghiệm Goerli" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "Đang kết nối với mạng thử nghiệm Linea Goerli" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "Tạo" }, - "createAccount": { - "message": "Tạo tài khoản" - }, "createNewWallet": { "message": "Tạo ví mới" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "Sử dụng API của OpenSea để tìm nạp dữ liệu NFT. Tính năng tự động phát hiện NFT dựa vào API của OpenSea và sẽ không khả dụng nếu tính năng này bị tắt." }, - "enableSmartTransactions": { - "message": "Bật giao dịch thông minh" - }, "enableToken": { "message": "bật $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "Bất kỳ ai cũng tạo được token bằng bất kỳ tên nào, kể cả phiên bản giả của token hiện có. Bạn tự chịu rủi ro khi thêm và giao dịch!" }, - "importTokens": { - "message": "nhập token" - }, "importTokensCamelCase": { "message": "Nhập token" }, @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "Bạn có muốn nhập những token này không?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Mạng thử nghiệm Linea Goerli" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "Yêu cầu tại đây" }, - "missingToken": { - "message": "Không thấy token của mình?" - }, "moreComingSoon": { "message": "Sắp có thêm..." }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "Không đủ gas" }, - "notifications": { - "message": "Thông báo" - }, "notifications10ActionText": { "message": "Xem trong phần Cài đặt", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2817,9 +2783,6 @@ "receive": { "message": "Nhận" }, - "recents": { - "message": "Gần đây" - }, "recipientAddressPlaceholder": { "message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "Chọn tùy chọn này để hiển thị trường dữ liệu thập lục phân trên màn hình gửi" }, - "showHide": { - "message": "Hiển thị/ẩn" - }, "showIncomingTransactions": { "message": "Hiển thị các giao dịch đến" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "Tôi hiểu rằng nếu chưa sao lưu Cụm Mật Khẩu Khôi Phục Bí Mật của mình, tôi có thể bị mất tài khoản và toàn bộ tài sản bên trong." }, - "smartTransaction": { - "message": "Giao dịch thông minh" - }, "snapContent": { "message": "Nội dung này đến từ $1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "Cài đặt Snap" }, "snapInstallWarningCheck": { - "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào ô." + "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào ô.", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào tất cả các ô." + "message": "Để xác nhận rằng bạn hiểu, hãy đánh dấu vào tất cả các ô.", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "Bạn đang cấp quyền truy cập khóa $2 cho Snap \"$1\". Hành động này không thể hủy bỏ và sẽ cấp quyền kiểm soát tài khoản và tài sản $2 của bạn cho \"$1\". Đảm bảo bạn tin tưởng \"$1\" trước khi tiếp tục.", @@ -3416,9 +3375,6 @@ "status": { "message": "Trạng thái" }, - "statusConnected": { - "message": "Đã kết nối" - }, "statusNotConnected": { "message": "Chưa kết nối" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "Mạnh" }, - "stxAreHere": { - "message": "Giao dịch thông minh đã ra mắt!" - }, "stxBenefit1": { "message": "Giảm thiểu chi phí giao dịch" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "Hãy thử hoán đổi lại. Chúng tôi ở đây để bảo vệ bạn trước những rủi ro tương tự trong lần tới." }, - "stxDescription": { - "message": "Tính năng Hoán đổi của MetaMask nay đã thông minh hơn rất nhiều! Kích hoạt Giao dịch thông minh sẽ cho phép MetaMask tối ưu quy trình Hoán đổi để giúp bạn:" - }, - "stxErrorNotEnoughFunds": { - "message": "Không có đủ tiền để thực hiện giao dịch thông minh." - }, - "stxErrorUnavailable": { - "message": "Giao dịch thông minh tạm thời không khả dụng." - }, "stxFailure": { "message": "Hoán đổi không thành công" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "Đang công khai gửi yêu cầu Hoán đổi của bạn..." }, - "stxSubDescription": { - "message": "* Giao dịch thông minh sẽ cố gắng gửi giao dịch của bạn nhiều lần theo cách riêng tư. Nếu tất cả các lần thử đều không thành công, giao dịch sẽ được phát công khai để đảm bảo Hoán đổi của bạn được thực hiện thành công." - }, "stxSuccess": { "message": "Hoán đổi hoàn tất!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "Bật/tắt yêu cầu eth_sign" }, - "toggleTestNetworks": { - "message": "$1 mạng thử nghiệm", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "Token" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "Chuyển" }, - "transferBetweenAccounts": { - "message": "Chuyển giữa các tài khoản của tôi" - }, "transferFrom": { "message": "Chuyển từ" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask đã gặp sự cố khi khởi động. Lỗi này có thể xảy ra không liên tục, vì vậy hãy thử khởi động lại tiện ích." }, - "troubleTokenBalances": { - "message": "Chúng tôi đã gặp phải vấn đề khi tải số dư token của bạn. Bạn có thể xem số dư ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "Bằng cách cấp quyền, bạn cho phép $1 sau đây truy cập vào các khoản tiền của mình" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 0fa856db0..192838fc4 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -195,10 +195,6 @@ "addCustomToken": { "message": "添加自定义代币" }, - "addCustomTokenByContractAddress": { - "message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到。", - "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" - }, "addEthereumChainConfirmationDescription": { "message": "这将允许在 MetaMask 中使用此网络。" }, @@ -263,6 +259,10 @@ "addToken": { "message": "添加代币" }, + "addTokenByContractAddress": { + "message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到", + "description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum" + }, "address": { "message": "地址" }, @@ -359,12 +359,6 @@ "message": "是否允许访问您的所有$1?", "description": "$1 is the symbol of the token for which the user is granting approval" }, - "approveAndInstall": { - "message": "批准并安装" - }, - "approveAndUpdate": { - "message": "批准并更新" - }, "approveButtonText": { "message": "批准" }, @@ -429,9 +423,6 @@ "back": { "message": "返回" }, - "backToAll": { - "message": "返回全部" - }, "backup": { "message": "备份" }, @@ -557,13 +548,6 @@ "message": "若要$1交易,燃料费用必须增加至少10%才能被网络认可。", "description": "$1 is string 'cancel' or 'speed up'" }, - "cancelSwapForFee": { - "message": "以~$1取消兑换", - "description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction" - }, - "cancelSwapForFree": { - "message": "免费取消兑换" - }, "cancelled": { "message": "已取消" }, @@ -642,9 +626,6 @@ "connectAccountOrCreate": { "message": "连接账户或创建新账户" }, - "connectHardwareWallet": { - "message": "连接硬件钱包" - }, "connectManually": { "message": "手动连接到当前站点" }, @@ -701,7 +682,7 @@ "connectingToGoerli": { "message": "正在连接 Goerli 测试网络" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "正在连接Linea测试网络" }, "connectingToMainnet": { @@ -789,9 +770,6 @@ "create": { "message": "创建" }, - "createAccount": { - "message": "创建账户" - }, "createNewWallet": { "message": "创建新钱包" }, @@ -1215,9 +1193,6 @@ "enableOpenSeaAPIDescription": { "message": "使用 OpenSea 的 API 获取 NFT 数据。NFT 自动检测依赖于 OpenSea 的 API,在后者关闭时自动检测将不可用。" }, - "enableSmartTransactions": { - "message": "启用智能交易" - }, "enableToken": { "message": "启用 $1", "description": "$1 is a token symbol, e.g. ETH" @@ -1636,9 +1611,6 @@ "importTokenWarning": { "message": "任何人都可以用任何名称创建代币,包括现有代币的虚假版本。添加和交易风险自负!" }, - "importTokens": { - "message": "添加资产" - }, "importTokensCamelCase": { "message": "添加代币" }, @@ -1823,7 +1795,7 @@ "message": "打开和解锁 Ledger Live 应用程序" }, "ledgerConnectionPreferenceDescription": { - "message": "自定义连接您的 Ledger 到 Metamask 的方式。建议使用 $1,但也可使用其他选项。请在这里阅读更多信息:$2", + "message": "自定义连接您的 Ledger 到 MetaMask 的方式。建议使用 $1,但也可使用其他选项。请在这里阅读更多信息:$2", "description": "A description that appears above a dropdown where users can select between up to three options - Ledger Live, U2F or WebHID - depending on what is supported in their browser. $1 is the recommended browser option, it will be either WebHID or U2f. $2 is a link to an article where users can learn more, but will be the translation of the learnMore message." }, "ledgerDeviceOpenFailureMessage": { @@ -1858,7 +1830,7 @@ "likeToImportTokens": { "message": "您想导入这些代币吗?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea测试网络" }, "link": { @@ -1990,9 +1962,6 @@ "missingSettingRequest": { "message": "在这里请求" }, - "missingToken": { - "message": "没有看到您的代币?" - }, "moreComingSoon": { "message": "更多即将到来……" }, @@ -2235,9 +2204,6 @@ "notEnoughGas": { "message": "燃料不足" }, - "notifications": { - "message": "通知" - }, "notifications10ActionText": { "message": "在设置中访问", "description": "The 'call to action' on the button, or link, of the 'Visit in Settings' notification. Upon clicking, users will be taken to Settings page." @@ -2337,7 +2303,7 @@ "description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update." }, "notifications6DescriptionThree": { - "message": "在 Metamask 中与您的 Ledger 账户交互时,将打开一个新选项卡,并且将要求您打开 Ledger Live 应用程序。应用程序打开后,您将被要求允许 WebSocket 连接到您的 MetaMask 账户。就是这样!", + "message": "在 MetaMask 中与您的 Ledger 账户交互时,将打开一个新选项卡,并且将要求您打开 Ledger Live 应用程序。应用程序打开后,您将被要求允许 WebSocket 连接到您的 MetaMask 账户。就是这样!", "description": "Description of a notification in the 'See What's New' popup. Describes the Ledger support update." }, "notifications6DescriptionTwo": { @@ -2817,9 +2783,6 @@ "receive": { "message": "接收" }, - "recents": { - "message": "最近" - }, "recipientAddressPlaceholder": { "message": "搜索、公共地址 (0x) 或 ENS" }, @@ -3214,9 +3177,6 @@ "showHexDataDescription": { "message": "选择此项以在发送屏幕上显示十六进制数据字段" }, - "showHide": { - "message": "显示/隐藏" - }, "showIncomingTransactions": { "message": "显示传入的交易" }, @@ -3269,9 +3229,6 @@ "skipAccountSecurityDetails": { "message": "我明白,在我备份我的账户助记词之前,我可能会丢失我的账户及其所有资产。" }, - "smartTransaction": { - "message": "智能交易" - }, "snapContent": { "message": "此内容来自$1", "description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap." @@ -3284,10 +3241,12 @@ "message": "安装Snap" }, "snapInstallWarningCheck": { - "message": "请勾选选项框以确认您理解。" + "message": "请勾选选项框以确认您理解。", + "description": "Warning message used in popup displayed on snap install. $1 is the snap name." }, "snapInstallWarningCheckPlural": { - "message": "请勾选所有方框以确认您理解。" + "message": "请勾选所有方框以确认您理解。", + "description": "Warning message used in popup displayed on snap install when having multiple permissions. $1 is the snap name." }, "snapInstallWarningKeyAccess": { "message": "您正在向snap \"$1\"授予$2的密钥访问权限。此操作不可撤销,并会向\"$1\"授予对您的$2账户和资产的控制权。在继续之前,请确保您信任\"$1\"。", @@ -3416,9 +3375,6 @@ "status": { "message": "状态" }, - "statusConnected": { - "message": "已连接" - }, "statusNotConnected": { "message": "未连接" }, @@ -3456,9 +3412,6 @@ "strong": { "message": "强" }, - "stxAreHere": { - "message": "智能交易已推出!" - }, "stxBenefit1": { "message": "将交易成本减至最低" }, @@ -3480,15 +3433,6 @@ "stxCancelledSubDescription": { "message": "再次尝试进行交换。下次我们会在这里保护您免受类似风险。 " }, - "stxDescription": { - "message": "MetaMask Swap变得更加智能!启用智能交易将允许MetaMask从编程上优化您的交换,以帮助:" - }, - "stxErrorNotEnoughFunds": { - "message": "没有足够的资金进行智能交易。" - }, - "stxErrorUnavailable": { - "message": "智能交易暂时不可用。" - }, "stxFailure": { "message": "交换失败" }, @@ -3502,9 +3446,6 @@ "stxPendingPubliclySubmittingSwap": { "message": "正在公开提交您的Swap..." }, - "stxSubDescription": { - "message": "*智能交易将尝试多次隐秘提交您的交易。如果所有尝试都失败,交易将会公开广播,以确保您的交换能成功进行。" - }, "stxSuccess": { "message": "交换完成!" }, @@ -3923,10 +3864,6 @@ "toggleEthSignField": { "message": "切换eth_sign请求" }, - "toggleTestNetworks": { - "message": "$1 测试网络", - "description": "$1 is a clickable link with text defined by the 'showHide' key. The link will open Settings > Advanced where users can enable the display of test networks in the network dropdown." - }, "token": { "message": "代币" }, @@ -4093,9 +4030,6 @@ "transfer": { "message": "转移" }, - "transferBetweenAccounts": { - "message": "在我的账户之间转移" - }, "transferFrom": { "message": "转移自" }, @@ -4106,10 +4040,6 @@ "troubleStarting": { "message": "MetaMask无法启动。可能发生间歇性错误,因此请尝试重新启动扩展程序。" }, - "troubleTokenBalances": { - "message": "我们在加载您的代币余额时遇到问题。您可以查看它们 ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "通过授予权限,您允许以下 $1 访问您的资金" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index fedf06a65..5af0125f6 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -160,9 +160,6 @@ "back": { "message": "上一頁" }, - "backToAll": { - "message": "回到全部" - }, "backupApprovalInfo": { "message": "在裝置遺失、忘記密碼、需要重新安裝 MetaMask、或是想在另一裝置開啟錢包的情形下,你需要此秘密代碼來復原錢包。" }, @@ -251,9 +248,6 @@ "connectAccountOrCreate": { "message": "連結帳戶或建立新的" }, - "connectHardwareWallet": { - "message": "連線硬體錢包" - }, "connectManually": { "message": "手動連結到目前的網站" }, @@ -310,7 +304,7 @@ "connectingToGoerli": { "message": "連線到 Goerli 測試網路" }, - "connectingToLineaTestnet": { + "connectingToLineaGoerli": { "message": "連線到 Linea Goerli 測試網路" }, "connectingToMainnet": { @@ -355,9 +349,6 @@ "create": { "message": "建立" }, - "createAccount": { - "message": "建立帳戶" - }, "createPassword": { "message": "建立密碼" }, @@ -812,7 +803,7 @@ "likeToImportTokens": { "message": "確定要加入代幣?" }, - "lineatestnet": { + "lineaGoerli": { "message": "Linea Goerli 測試網路" }, "links": { @@ -1081,9 +1072,6 @@ "receive": { "message": "接收" }, - "recents": { - "message": "最近" - }, "recipientAddressPlaceholder": { "message": "搜尋、公開位址 (0x)、或 ENS" }, @@ -1322,9 +1310,6 @@ "stateLogsDescription": { "message": "狀態紀錄包含您的公開帳戶位址和已傳送的交易資訊" }, - "statusConnected": { - "message": "已連結" - }, "statusNotConnected": { "message": "未連結" }, @@ -1463,9 +1448,6 @@ "transfer": { "message": "交易" }, - "transferBetweenAccounts": { - "message": "在我的帳戶間轉帳" - }, "transferFrom": { "message": "交易來源" }, @@ -1473,10 +1455,6 @@ "message": "我們在連線到您的 $1 的時候遇到問題,試著檢查 $2 然後再試一次。", "description": "$1 is the wallet device name; $2 is a link to wallet connection guide" }, - "troubleTokenBalances": { - "message": "無法取得代幣餘額。您可以到這裡查看 ", - "description": "Followed by a link (here) to view token balances" - }, "trustSiteApprovePermission": { "message": "您信任這個網站嗎?當您授予這個權限,$1 就能提領您的 $2 並且代替您自動發送交易。" }, diff --git a/app/images/linea-logo-testnet.png b/app/images/linea-logo-testnet.png new file mode 100644 index 000000000..d9f78720b Binary files /dev/null and b/app/images/linea-logo-testnet.png differ diff --git a/app/images/swaps-redesign.svg b/app/images/swaps-redesign.svg new file mode 100644 index 000000000..08bf92a87 --- /dev/null +++ b/app/images/swaps-redesign.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/scripts/background.js b/app/scripts/background.js index 547330168..bd80b71ac 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -676,10 +676,8 @@ export function setupController( // // User Interface setup // + updateBadge(); - controller.txController.initApprovals().then(() => { - updateBadge(); - }); controller.txController.on( METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE, updateBadge, @@ -706,6 +704,8 @@ export function setupController( updateBadge, ); + controller.txController.initApprovals(); + /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. * The number reflects the current number of pending transactions or message signatures needing user approval. diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index fd0102666..722f1f56a 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -183,6 +183,20 @@ export default class AppStateController extends EventEmitter { }); } + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + /** + * Record if popover for snaps privacy warning has been shown + * on the first install of a snap. + * + * @param {boolean} shown - shown status + */ + setSnapsInstallPrivacyWarningShownStatus(shown) { + this.store.updateState({ + snapsInstallPrivacyWarningShown: shown, + }); + } + ///: END:ONLY_INCLUDE_IN + /** * Record the timestamp of the last time the user has seen the outdated browser warning * diff --git a/app/scripts/controllers/app-state.test.js b/app/scripts/controllers/app-state.test.js index 02d3cda3c..b96d39a4c 100644 --- a/app/scripts/controllers/app-state.test.js +++ b/app/scripts/controllers/app-state.test.js @@ -346,4 +346,23 @@ describe('AppStateController', () => { ); }); }); + + describe('setSnapsInstallPrivacyWarningShownStatus', () => { + it('updates the status of snaps install privacy warning', () => { + appStateController = createAppStateController(); + const updateStateSpy = jest.spyOn( + appStateController.store, + 'updateState', + ); + + appStateController.setSnapsInstallPrivacyWarningShownStatus(true); + + expect(updateStateSpy).toHaveBeenCalledTimes(1); + expect(updateStateSpy).toHaveBeenCalledWith({ + snapsInstallPrivacyWarningShown: true, + }); + + updateStateSpy.mockRestore(); + }); + }); }); diff --git a/app/scripts/controllers/decrypt-message.ts b/app/scripts/controllers/decrypt-message.ts index 779b22f78..b9f37fea2 100644 --- a/app/scripts/controllers/decrypt-message.ts +++ b/app/scripts/controllers/decrypt-message.ts @@ -45,7 +45,7 @@ export type CoreMessage = AbstractMessage & { }; export type StateMessage = Required< - Omit + Omit >; export type DecryptMessageControllerState = { diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index a85230668..a1fee90ce 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -9,7 +9,7 @@ import { TokensController, AssetsContractController, } from '@metamask/assets-controllers'; -import { convertHexToDecimal } from '@metamask/controller-utils'; +import { toHex } from '@metamask/controller-utils'; import { NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; @@ -204,7 +204,7 @@ describe('DetectTokensController', function () { name: 'TokenListController', }); tokenListController = new TokenListController({ - chainId: '1', + chainId: toHex(1), preventPollingOnNetworkRestart: false, onNetworkStateChange: sinon.spy(), onPreferencesStateChange: sinon.spy(), @@ -246,23 +246,7 @@ describe('DetectTokensController', function () { onPreferencesStateChange: preferences.store.subscribe.bind( preferences.store, ), - onNetworkStateChange: (cb) => - networkControllerMessenger.subscribe( - 'NetworkController:networkDidChange', - () => { - const networkState = network.store.getState(); - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: convertHexToDecimal( - networkState.providerConfig.chainId, - ), - }, - }; - return cb(modifiedNetworkState); - }, - ), + onNetworkStateChange: network.store.subscribe.bind(network.store), }); }); @@ -311,7 +295,7 @@ describe('DetectTokensController', function () { name: 'TokenListController', }); tokenListController = new TokenListController({ - chainId: '11155111', + chainId: toHex(11155111), onNetworkStateChange: sinon.spy(), onPreferencesStateChange: sinon.spy(), messenger: tokenListMessengerSepolia, diff --git a/app/scripts/controllers/encryption-public-key.ts b/app/scripts/controllers/encryption-public-key.ts index 4ea2c1afe..904bed476 100644 --- a/app/scripts/controllers/encryption-public-key.ts +++ b/app/scripts/controllers/encryption-public-key.ts @@ -45,7 +45,7 @@ export type CoreMessage = AbstractMessage & { }; export type StateMessage = Required< - Omit + Omit > & { msgParams: string; }; diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 0f7553ab2..780da42b4 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -1,5 +1,6 @@ import { strict as assert } from 'assert'; import sinon from 'sinon'; +import { toHex } from '@metamask/controller-utils'; import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { createSegmentMock } from '../lib/segment'; import { @@ -885,7 +886,7 @@ describe('MetaMetricsController', function () { describe('_buildUserTraitsObject', function () { it('should return full user traits object on first call', function () { const MOCK_ALL_TOKENS = { - '0x1': { + [toHex(1)]: { '0x1235ce91d74254f29d4609f25932fe6d97bf4842': [ { address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', @@ -900,7 +901,7 @@ describe('MetaMetricsController', function () { }, ], }, - '0x4': { + [toHex(4)]: { '0x1235ce91d74254f29d4609f25932fe6d97bf4842': [ { address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', @@ -920,7 +921,7 @@ describe('MetaMetricsController', function () { }, allNfts: { '0xac706cE8A9BF27Afecf080fB298d0ee13cfb978A': { - 56: [ + [toHex(56)]: [ { address: '0xd2cea331e5f5d8ee9fb1055c297795937645de91', tokenId: '100', @@ -936,7 +937,7 @@ describe('MetaMetricsController', function () { ], }, '0xe04AB39684A24D8D4124b114F3bd6FBEB779cacA': { - 69: [ + [toHex(59)]: [ { address: '0x63d646bc7380562376d5de205123a57b1718184d', tokenId: '14', @@ -1022,7 +1023,9 @@ describe('MetaMetricsController', function () { [CHAIN_IDS.GOERLI]: [{ address: '0x' }, { address: '0x0' }], }, allTokens: { - '0x1': { '0xabcde': [{ '0x12345': { address: '0xtestAddress' } }] }, + [toHex(1)]: { + '0xabcde': [{ '0x12345': { address: '0xtestAddress' } }], + }, }, networkConfigurations: { 'network-configuration-id-1': { chainId: CHAIN_IDS.MAINNET }, diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js new file mode 100644 index 000000000..ca9933cfb --- /dev/null +++ b/app/scripts/controllers/mmi-controller.js @@ -0,0 +1,586 @@ +import EventEmitter from 'events'; +import log from 'loglevel'; +import { captureException } from '@sentry/browser'; +import { + PersonalMessageManager, + TypedMessageManager, +} from '@metamask/message-manager'; +import { CUSTODIAN_TYPES } from '@metamask-institutional/custody-keyring'; +import { + updateCustodianTransactions, + custodianEventHandlerFactory, +} from '@metamask-institutional/extension'; +import { + REFRESH_TOKEN_CHANGE_EVENT, + INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, +} from '@metamask-institutional/sdk'; +import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; +import { + BUILD_QUOTE_ROUTE, + CONNECT_HARDWARE_ROUTE, +} from '../../../ui/helpers/constants/routes'; +import { getPermissionBackgroundApiMethods } from './permissions'; + +export default class MMIController extends EventEmitter { + constructor(opts) { + super(); + + this.opts = opts; + this.mmiConfigurationController = opts.mmiConfigurationController; + this.keyringController = opts.keyringController; + this.txController = opts.txController; + this.securityProviderRequest = opts.securityProviderRequest; + this.preferencesController = opts.preferencesController; + this.appStateController = opts.appStateController; + this.transactionUpdateController = opts.transactionUpdateController; + this.custodyController = opts.custodyController; + this.institutionalFeaturesController = opts.institutionalFeaturesController; + this.getState = opts.getState; + this.getPendingNonce = opts.getPendingNonce; + this.accountTracker = opts.accountTracker; + this.metaMetricsController = opts.metaMetricsController; + this.networkController = opts.networkController; + this.permissionController = opts.permissionController; + this.platform = opts.platform; + this.extension = opts.extension; + + this.personalMessageManager = new PersonalMessageManager( + undefined, + undefined, + this.securityProviderRequest, + ); + this.typedMessageManager = new TypedMessageManager( + undefined, + undefined, + this.securityProviderRequest, + ); + + // Prepare event listener after transactionUpdateController gets initiated + this.transactionUpdateController.prepareEventListener( + this.custodianEventHandlerFactory.bind(this), + ); + + // Get configuration from MMIConfig controller + if (!process.env.IN_TEST) { + this.mmiConfigurationController.storeConfiguration().then(() => { + // This must happen after the configuration is fetched + // Otherwise websockets will always be disabled in the first run + + this.transactionUpdateController.subscribeToEvents(); + }); + } + } // End of constructor + + async persistKeyringsAfterRefreshTokenChange() { + this.keyringController.persistAllKeyrings(); + } + + async trackTransactionEventFromCustodianEvent(txMeta, event) { + this.txController._trackTransactionMetricsEvent(txMeta, event); + } + + async addKeyringIfNotExists(type) { + let keyring = await this.keyringController.getKeyringsByType(type)[0]; + if (!keyring) { + keyring = await this.keyringController.addNewKeyring(type); + } + return keyring; + } + + custodianEventHandlerFactory() { + return custodianEventHandlerFactory({ + log, + getState: () => this.getState(), + getPendingNonce: (address) => this.getPendingNonce(address), + setTxHash: (txId, txHash) => this.txController.setTxHash(txId, txHash), + typedMessageManager: this.typedMessageManager, + personalMessageManager: this.personalMessageManager, + txStateManager: this.txController.txStateManager, + custodyController: this.custodyController, + trackTransactionEvent: + this.trackTransactionEventFromCustodianEvent.bind(this), + }); + } + + async storeCustodianSupportedChains(address) { + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + const keyring = await this.addKeyringIfNotExists(custodyType); + + const supportedChains = await keyring.getSupportedChains(address); + + if (supportedChains?.status === 401) { + return; + } + + const accountDetails = this.custodyController.getAccountDetails(address); + + await this.custodyController.storeSupportedChainsForAddress( + toChecksumHexAddress(address), + supportedChains, + accountDetails.custodianName, + ); + } + + async onSubmitPassword() { + // Create a keyring for each custodian type + let addresses = []; + const custodyTypes = this.custodyController.getAllCustodyTypes(); + for (const type of custodyTypes) { + try { + const keyring = await this.addKeyringIfNotExists(type); + + keyring.on(REFRESH_TOKEN_CHANGE_EVENT, () => { + log.info(`Refresh token change event for ${type}`); + this.persistKeyringsAfterRefreshTokenChange(); + }); + + // Trigger this event, listen to sdk, sdk change the state and then Ui is listening for the state changed + keyring.on(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, (payload) => { + log.info(`Interactive refresh token change event for ${payload}`); + this.appStateController.showInteractiveReplacementTokenBanner( + payload, + ); + }); + + // store the supported chains for this custodian type + const accounts = await keyring.getAccounts(); + addresses = addresses.concat(...accounts); + for (const address of accounts) { + try { + await this.storeCustodianSupportedChains(address); + } catch (error) { + captureException(error); + log.error('Error while unlocking extension.', error); + } + } + + const txList = this.txController.txStateManager.getTransactions( + {}, + [], + false, + ); // Includes all transactions, but we are looping through keyrings. Currently filtering is done in updateCustodianTransactions :-/ + + try { + updateCustodianTransactions({ + keyring, + type, + txList, + getPendingNonce: this.getPendingNonce.bind(this), + txStateManager: this.txController.txStateManager, + setTxHash: this.txController.setTxHash.bind(this.txController), + custodyController: this.custodyController, + transactionUpdateController: this.transactionUpdateController, + }); + } catch (error) { + log.error('Error doing offline transaction updates', error); + captureException(error); + } + } catch (error) { + log.error( + `Error while unlocking extension with custody type ${type}`, + error, + ); + captureException(error); + } + } + + try { + await this.mmiConfigurationController.storeConfiguration(); + } catch (error) { + log.error('Error while unlocking extension.', error); + } + + try { + await this.transactionUpdateController.subscribeToEvents(); + } catch (error) { + log.error('Error while unlocking extension.', error); + } + + const mmiConfigData = + await this.mmiConfigurationController.store.getState(); + + if ( + mmiConfigData && + mmiConfigData.mmiConfiguration.features?.websocketApi + ) { + this.transactionUpdateController.getCustomerProofForAddresses(addresses); + } + + try { + if (this.institutionalFeaturesController.getComplianceProjectId()) { + this.institutionalFeaturesController.startPolling(); + } + } catch (e) { + log.error('Failed to start Compliance polling'); + log.error(e); + } + } + + async connectCustodyAddresses(custodianType, custodianName, accounts) { + if (!custodianType) { + throw new Error('No custodian'); + } + + const custodian = CUSTODIAN_TYPES[custodianType.toUpperCase()]; + if (!custodian) { + throw new Error('No such custodian'); + } + + const newAccounts = Object.keys(accounts); + + // Check if any address is already added + const identities = Object.keys( + this.preferencesController.store.getState().identities, + ); + if (newAccounts.some((address) => identities.indexOf(address) !== -1)) { + throw new Error('Cannot import duplicate accounts'); + } + + const keyring = await this.addKeyringIfNotExists( + custodian.keyringClass.type, + ); + + keyring.on(REFRESH_TOKEN_CHANGE_EVENT, () => { + log.info(`Refresh token change event for ${keyring.type}`); + this.persistKeyringsAfterRefreshTokenChange(); + }); + + // Trigger this event, listen to sdk, sdk change the state and then Ui is listening for the state changed + keyring.on(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, (payload) => { + log.info(`Interactive refresh token change event for ${payload}`); + this.appStateController.showInteractiveReplacementTokenBanner(payload); + }); + + if (!keyring) { + throw new Error('Unable to get keyring'); + } + const oldAccounts = await this.keyringController.getAccounts(); + + await keyring.setSelectedAddresses( + newAccounts.map((item) => ({ + address: toChecksumHexAddress(item), + name: accounts[item].name, + custodianDetails: accounts[item].custodianDetails, + labels: accounts[item].labels, + token: accounts[item].token, + apiUrl: accounts[item].apiUrl, + custodyType: custodian.keyringClass.type, + chainId: accounts[item].chainId, + })), + ); + this.custodyController.setAccountDetails( + newAccounts.map((item) => ({ + address: toChecksumHexAddress(item), + name: accounts[item].name, + custodianDetails: accounts[item].custodianDetails, + labels: accounts[item].labels, + apiUrl: accounts[item].apiUrl, + custodyType: custodian.keyringClass.type, + custodianName, + chainId: accounts[item].chainId, + })), + ); + + newAccounts.forEach( + async () => await this.keyringController.addNewAccount(keyring), + ); + + const allAccounts = await this.keyringController.getAccounts(); + + this.preferencesController.setAddresses(allAccounts); + const accountsToTrack = [ + ...new Set(oldAccounts.concat(allAccounts.map((a) => a.toLowerCase()))), + ]; + + allAccounts.forEach((address) => { + if (!oldAccounts.includes(address.toLowerCase())) { + const label = newAccounts + .filter((item) => item.toLowerCase() === address) + .map((item) => accounts[item].name)[0]; + this.preferencesController.setAccountLabel(address, label); + } + }); + + this.accountTracker.syncWithAddresses(accountsToTrack); + + for (const address of newAccounts) { + try { + await this.storeCustodianSupportedChains(address); + } catch (error) { + captureException(error); + } + } + + // FIXME: status maps are not a thing anymore + this.custodyController.storeCustodyStatusMap( + custodian.name, + keyring.getStatusMap(), + ); + + // MMI - get a WS stream for this account + const mmiConfigData = + await this.mmiConfigurationController.store.getState(); + + if ( + mmiConfigData && + mmiConfigData.mmiConfiguration.features?.websocketApi + ) { + this.transactionUpdateController.getCustomerProofForAddresses( + newAccounts, + ); + } + + return newAccounts; + } + + async getCustodianAccounts( + token, + apiUrl, + custodianType, + getNonImportedAccounts, + ) { + let currentCustodyType; + if (!custodianType) { + const address = this.preferencesController.getSelectedAddress(); + currentCustodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + } + + let keyring; + + if (custodianType) { + const custodian = CUSTODIAN_TYPES[custodianType.toUpperCase()]; + if (!custodian) { + throw new Error('No such custodian'); + } + + keyring = await this.addKeyringIfNotExists(custodian.keyringClass.type); + } else if (currentCustodyType) { + keyring = await this.addKeyringIfNotExists(currentCustodyType); + } else { + throw new Error('No custodian specified'); + } + + const accounts = await keyring.getCustodianAccounts( + token, + apiUrl, + null, + getNonImportedAccounts, + ); + return accounts; + } + + async getCustodianAccountsByAddress(token, apiUrl, address, custodianType) { + let keyring; + + if (custodianType) { + const custodian = CUSTODIAN_TYPES[custodianType.toUpperCase()]; + if (!custodian) { + throw new Error('No such custodian'); + } + + keyring = await this.addKeyringIfNotExists(custodian.keyringClass.type); + } else { + throw new Error('No custodian specified'); + } + + const accounts = await keyring.getCustodianAccounts(token, apiUrl, address); + return accounts; + } + + async getCustodianTransactionDeepLink(address, txId) { + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + const keyring = await this.addKeyringIfNotExists(custodyType); + return keyring.getTransactionDeepLink(address, txId); + } + + async getCustodianConfirmDeepLink(txId) { + const txMeta = this.txController.txStateManager.getTransaction(txId); + + const address = txMeta.txParams.from; + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + const keyring = await this.addKeyringIfNotExists(custodyType); + return { + deepLink: await keyring.getTransactionDeepLink( + txMeta.txParams.from, + txMeta.custodyId, + ), + custodyId: txMeta.custodyId, + }; + } + + async getCustodianSignMessageDeepLink(from, custodyTxId) { + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(from), + ); + const keyring = await this.addKeyringIfNotExists(custodyType); + return keyring.getTransactionDeepLink(from, custodyTxId); + } + + async getCustodianToken(custodianType) { + let currentCustodyType; + + const address = this.preferencesController.getSelectedAddress(); + + if (!custodianType) { + const resultCustody = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + currentCustodyType = resultCustody; + } + let keyring = await this.keyringController.getKeyringsByType( + currentCustodyType || `Custody - ${custodianType}`, + )[0]; + if (!keyring) { + keyring = await this.keyringController.addNewKeyring( + currentCustodyType || `Custody - ${custodianType}`, + ); + } + const { authDetails } = keyring.getAccountDetails(address); + return keyring ? authDetails.jwt || authDetails.refreshToken : ''; + } + + // Based on a custodian name, get all the tokens associated with that custodian + async getCustodianJWTList(custodianName) { + console.log('getCustodianJWTList', custodianName); + + const { identities } = this.preferencesController.store.getState(); + + const { mmiConfiguration } = + this.mmiConfigurationController.store.getState(); + + const addresses = Object.keys(identities); + const tokenList = []; + + const { custodians } = mmiConfiguration; + + const custodian = custodians.find((item) => item.name === custodianName); + + if (!custodian) { + return []; + } + + const keyrings = await this.keyringController.getKeyringsByType( + `Custody - ${custodian.type}`, + ); + + for (const address of addresses) { + for (const keyring of keyrings) { + // Narrow down to custodian Type + const accountDetails = keyring.getAccountDetails(address); + + if (!accountDetails) { + log.debug(`${address} does not belong to ${custodian.type} keyring`); + continue; + } + + const custodyAccountDetails = + this.custodyController.getAccountDetails(address); + + if ( + !custodyAccountDetails || + custodyAccountDetails.custodianName !== custodianName + ) { + log.debug(`${address} does not belong to ${custodianName} keyring`); + continue; + } + + const { authDetails } = accountDetails; + + let token; + if (authDetails.jwt) { + token = authDetails.jwt; + } else if (authDetails.refreshToken) { + token = authDetails.refreshToken; + } + + if (!tokenList.includes(token)) { + tokenList.push(token); + } + } + } + return tokenList; + } + + async getAllCustodianAccountsWithToken(custodyType, token) { + const keyring = await this.keyringController.getKeyringsByType( + `Custody - ${custodyType}`, + )[0]; + return keyring ? keyring.getAllAccountsWithToken(token) : []; + } + + async setCustodianNewRefreshToken({ address, newAuthDetails }) { + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + + const keyring = await this.addKeyringIfNotExists(custodyType); + + await keyring.replaceRefreshTokenAuthDetails(address, newAuthDetails); + } + + async handleMmiCheckIfTokenIsPresent(req) { + const { token, apiUrl } = req.params; + const custodyType = 'Custody - JSONRPC'; // Only JSONRPC is supported for now + + // This can only work if the extension is unlocked + await this.appStateController.getUnlockPromise(true); + + const keyring = await this.addKeyringIfNotExists(custodyType); + + return await this.custodyController.handleMmiCheckIfTokenIsPresent({ + token, + apiUrl, + keyring, + }); + } + + async setAccountAndNetwork(origin, address, chainId) { + await this.appStateController.getUnlockPromise(true); + const selectedAddress = this.preferencesController.getSelectedAddress(); + if (selectedAddress.toLowerCase() !== address.toLowerCase()) { + this.preferencesController.setSelectedAddress(address); + } + const selectedChainId = parseInt( + this.networkController.getCurrentChainId(), + 16, + ); + if (selectedChainId !== chainId && chainId === 1) { + this.networkController.setProviderType('mainnet'); + } else if (selectedChainId !== chainId) { + const network = this.preferencesController + .getFrequentRpcListDetail() + .find((item) => parseInt(item.chainId, 16) === chainId); + this.networkController.setRpcTarget( + network.rpcUrl, + network.chainId, + network.ticker, + network.nickname, + ); + } + getPermissionBackgroundApiMethods( + this.permissionController, + ).addPermittedAccount(origin, address); + + return true; + } + + async handleMmiOpenSwaps(origin, address, chainId) { + await this.setAccountAndNetwork(origin, address, chainId); + this.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE); + return true; + } + + async handleMmiOpenAddHardwareWallet() { + await this.appStateController.getUnlockPromise(true); + this.platform.openExtensionInBrowser(CONNECT_HARDWARE_ROUTE); + return true; + } +} diff --git a/app/scripts/controllers/mmi-controller.test.js b/app/scripts/controllers/mmi-controller.test.js new file mode 100644 index 000000000..f6f9cc5f7 --- /dev/null +++ b/app/scripts/controllers/mmi-controller.test.js @@ -0,0 +1,129 @@ +/* eslint-disable */ +import { KeyringController } from '@metamask/eth-keyring-controller'; +import { MmiConfigurationController } from '@metamask-institutional/custody-keyring'; +import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; + +import MMIController from './mmi-controller'; +import TransactionController from './transactions'; +import PreferencesController from './preferences'; +import AppStateController from './app-state'; + +describe('MMIController', function () { + let mmiController; + + beforeEach(function () { + mmiController = new MMIController({ + mmiConfigurationController: new MmiConfigurationController(), + keyringController: new KeyringController({ + initState: {}, + }), + transactionUpdateController: new TransactionUpdateController({ + getCustodyKeyring: jest.fn(), + }), + txController: new TransactionController({ + initState: {}, + provider: { + chainId: 'fail', + nickname: '', + rpcTarget: 'https://api.myetherwallet.com/eth', + ticker: 'ETH', + type: 'rinkeby', + }, + getCurrentChainId: jest.fn(), + getNetworkId: jest.fn(), + onNetworkStateChange: jest.fn(), + }), + preferencesController: new PreferencesController({ + initState: {}, + onInfuraIsBlocked: jest.fn(), + onInfuraIsUnblocked: jest.fn(), + provider: {}, + }), + appStateController: new AppStateController({ + addUnlockListener: jest.fn(), + isUnlocked: jest.fn(() => true), + initState: {}, + onInactiveTimeout: jest.fn(), + showUnlockRequest: jest.fn(), + preferencesStore: { + subscribe: jest.fn(), + getState: jest.fn(() => ({ + preferences: { + autoLockTimeLimit: 0, + }, + })), + }, + qrHardwareStore: { + subscribe: jest.fn(), + }, + messenger: { + call: jest.fn(() => ({ + catch: jest.fn(), + })), + }, + }), + custodianEventHandlerFactory: jest.fn(), + }); + }); + + describe('mmiController constructor', function () { + it('should instantiate correctly', function () { + expect(mmiController).toBeInstanceOf(MMIController); + }); + + it('should have all required properties', function () { + expect(mmiController.opts).toBeDefined(); + expect(mmiController.mmiConfigurationController).toBeDefined(); + expect(mmiController.preferencesController).toBeDefined(); + expect(mmiController.transactionUpdateController).toBeDefined(); + }); + }); + + describe('persistKeyringsAfterRefreshTokenChange', function () { + it('should call keyringController.persistAllKeyrings', async function () { + mmiController.keyringController.persistAllKeyrings = jest.fn(); + + await mmiController.persistKeyringsAfterRefreshTokenChange(); + + expect( + mmiController.keyringController.persistAllKeyrings, + ).toHaveBeenCalled(); + }); + }); + + describe('trackTransactionEventFromCustodianEvent', function () { + it('should call txController._trackTransactionMetricsEvent', function () { + const txMeta = {}; + const event = 'event'; + mmiController.txController._trackTransactionMetricsEvent = jest.fn(); + + mmiController.trackTransactionEventFromCustodianEvent(txMeta, event); + + expect( + mmiController.txController._trackTransactionMetricsEvent, + ).toHaveBeenCalledWith(txMeta, event); + }); + }); + + describe('custodianEventHandlerFactory', function () { + it('should call custodianEventHandlerFactory', async function () { + mmiController.custodianEventHandlerFactory = jest.fn(); + + mmiController.custodianEventHandlerFactory(); + + expect(mmiController.custodianEventHandlerFactory).toHaveBeenCalled(); + }); + }); + + describe('storeCustodianSupportedChains', function () { + it('should call storeCustodianSupportedChains', async function () { + mmiController.storeCustodianSupportedChains = jest.fn(); + + mmiController.storeCustodianSupportedChains('0x1'); + + expect(mmiController.storeCustodianSupportedChains).toHaveBeenCalledWith( + '0x1', + ); + }); + }); +}); diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts index abb1a3bfa..9a8a2f933 100644 --- a/app/scripts/controllers/network/network-controller.test.ts +++ b/app/scripts/controllers/network/network-controller.test.ts @@ -15,9 +15,9 @@ import { NetworkStatus, NETWORK_TYPES, } from '../../../../shared/constants/network'; -import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics'; import { NetworkController, + NetworkControllerAction, NetworkControllerEvent, NetworkControllerOptions, NetworkControllerState, @@ -53,7 +53,6 @@ type Block = { }; const createNetworkClientMock = jest.mocked(createNetworkClient); -// const providerFromEngineMock = jest.mocked(providerFromEngine); const uuidV4Mock = jest.mocked(v4); /** @@ -79,13 +78,6 @@ const POST_1559_BLOCK: Block = { */ const BLOCK: Block = POST_1559_BLOCK; -/** - * A dummy value for the `projectId` option that `createInfuraClient` needs. - * (Infura should not be hit during tests, but just in case, this should not - * refer to a real project ID.) - */ -const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id'; - /** * The networks that NetworkController recognizes as built-in Infura networks, * along with information we expect to be true for those networks. @@ -93,19 +85,19 @@ const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id'; const INFURA_NETWORKS = [ { networkType: NETWORK_TYPES.MAINNET, - chainId: '0x1' as const, + chainId: toHex(1), ticker: 'ETH', blockExplorerUrl: 'https://etherscan.io', }, { networkType: NETWORK_TYPES.GOERLI, - chainId: '0x5' as const, + chainId: toHex(5), ticker: 'GoerliETH', blockExplorerUrl: 'https://goerli.etherscan.io', }, { networkType: NETWORK_TYPES.SEPOLIA, - chainId: '0xaa36a7' as const, + chainId: toHex(11155111), ticker: 'SepoliaETH', blockExplorerUrl: 'https://sepolia.etherscan.io', }, @@ -158,17 +150,45 @@ describe('NetworkController', () => { describe('constructor', () => { const invalidInfuraProjectIds = [undefined, null, {}, 1]; invalidInfuraProjectIds.forEach((invalidProjectId) => { - it(`throws if an invalid Infura ID of "${inspect( + it(`throws given an invalid Infura ID of "${inspect( invalidProjectId, - )}" is provided`, () => { + )}"`, () => { + const messenger = buildMessenger(); + const restrictedMessenger = buildNetworkControllerMessenger(messenger); expect( - // @ts-expect-error We are intentionally passing bad input. - () => new NetworkController({ infuraProjectId: invalidProjectId }), + () => + new NetworkController({ + messenger: restrictedMessenger, + // @ts-expect-error We are intentionally passing bad input. + infuraProjectId: invalidProjectId, + }), ).toThrow('Invalid Infura project ID'); }); }); - it('accepts initial state', async () => { + it('initializes the state with some defaults', async () => { + await withController(({ controller }) => { + expect(controller.store.getState()).toMatchInlineSnapshot(` + { + "networkConfigurations": {}, + "networkDetails": { + "EIPS": {}, + }, + "networkId": null, + "networkStatus": "unknown", + "providerConfig": { + "chainId": "0x539", + "nickname": "Localhost 8545", + "rpcUrl": "http://localhost:8545", + "ticker": "ETH", + "type": "rpc", + }, + } + `); + }); + }); + + it('merges the given state into the default state', async () => { await withController( { state: { @@ -180,7 +200,7 @@ describe('NetworkController', () => { }, networkDetails: { EIPS: { - 1559: false, + 1559: true, }, }, }, @@ -191,7 +211,7 @@ describe('NetworkController', () => { "networkConfigurations": {}, "networkDetails": { "EIPS": { - "1559": false, + "1559": true, }, }, "networkId": null, @@ -207,30 +227,6 @@ describe('NetworkController', () => { }, ); }); - - it('sets default state without initial state', async () => { - await withController(({ controller }) => { - expect(controller.store.getState()).toMatchInlineSnapshot(` - { - "networkConfigurations": {}, - "networkDetails": { - "EIPS": { - "1559": undefined, - }, - }, - "networkId": null, - "networkStatus": "unknown", - "providerConfig": { - "chainId": "0x539", - "nickname": "Localhost 8545", - "rpcUrl": "http://localhost:8545", - "ticker": "ETH", - "type": "rpc", - }, - } - `); - }); - }); }); describe('destroy', () => { @@ -252,7 +248,7 @@ describe('NetworkController', () => { blockTracker.addListener('latest', () => { // do nothing }); - expect(blockTracker.isRunning()).toBeTruthy(); + expect(blockTracker.isRunning()).toBe(true); await controller.destroy(); @@ -262,37 +258,34 @@ describe('NetworkController', () => { }); describe('initializeProvider', () => { - it('throws if the provider configuration is invalid', async () => { - const invalidProviderConfig = {}; - await withController( - /* @ts-expect-error We're intentionally passing bad input. */ - { - state: { - providerConfig: invalidProviderConfig, + describe('when the type in the provider config is invalid', () => { + it('throws', async () => { + const invalidProviderConfig = {}; + await withController( + /* @ts-expect-error We're intentionally passing bad input. */ + { + state: { + providerConfig: invalidProviderConfig, + }, }, - }, - async ({ controller }) => { - await expect(async () => { - await controller.initializeProvider(); - }).rejects.toThrow( - 'NetworkController - #configureProvider - unknown type "undefined"', - ); - }, - ); + async ({ controller }) => { + await expect(async () => { + await controller.initializeProvider(); + }).rejects.toThrow("Unrecognized network type: 'undefined'"); + }, + ); + }); }); for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { + describe(`when the type in the provider config is "${networkType}"`, () => { + it(`creates a network client for the ${networkType} Infura network, capturing the resulting provider`, async () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -300,7 +293,8 @@ describe('NetworkController', () => { const fakeProvider = buildFakeProvider([ { request: { - method: 'test', + method: 'test_method', + params: [], }, response: { result: 'test response', @@ -308,27 +302,27 @@ describe('NetworkController', () => { }, ]); const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClient); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); await controller.initializeProvider(); + expect(createNetworkClientMock).toHaveBeenCalledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }); const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); + assert(provider, 'Provider is not set'); const promisifiedSendAsync = promisify(provider.sendAsync).bind( provider, ); - const response = await promisifiedSendAsync({ - id: '1', + const { result } = await promisifiedSendAsync({ + id: 1, jsonrpc: '2.0', - method: 'test', + method: 'test_method', + params: [], }); - expect(response.result).toBe('test response'); + expect(result).toBe('test response'); }, ); }); @@ -346,76 +340,181 @@ describe('NetworkController', () => { } describe(`when the type in the provider configuration is "rpc"`, () => { - it('initializes a provider pointed to the given RPC URL whose chain ID matches the configured chain ID', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - ticker: 'TEST', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'TEST', - id: 'testNetworkConfigurationId', + describe('if chainId and rpcUrl are present in the provider config', () => { + it('creates a network client for a custom RPC endpoint using the provider config, capturing the resulting provider', async () => { + await withController( + { + state: { + providerConfig: { + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), + rpcUrl: 'http://example.com', }, }, }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test', + async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'test_method', + params: [], + }, + response: { + result: 'test response', + }, }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + await controller.initializeProvider(); + + expect(createNetworkClientMock).toHaveBeenCalledWith({ + chainId: toHex(1337), + rpcUrl: 'http://example.com', type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + }); + const { provider } = controller.getProviderAndBlockTracker(); + assert(provider, 'Provider is not set'); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const { result } = await promisifiedSendAsync({ + id: 1, + jsonrpc: '2.0', + method: 'test_method', + params: [], + }); + expect(result).toBe('test response'); + }, + ); + }); - await controller.initializeProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NETWORK_TYPES.RPC, - }), - initialState: { - providerConfig: buildProviderConfig({ + lookupNetworkTests({ + expectedProviderConfig: buildProviderConfig({ type: NETWORK_TYPES.RPC, }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, + initialState: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + }), + }, + operation: async (controller: NetworkController) => { + await controller.initializeProvider(); + }, + }); + }); + + describe('if chainId is missing from the provider config', () => { + it('throws', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: undefined, + }), + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + await expect(() => + controller.initializeProvider(), + ).rejects.toThrow( + 'chainId must be provided for custom RPC endpoints', + ); + }, + ); + }); + + it('does not create a network client or capture a provider', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: undefined, + }), + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + try { + await controller.initializeProvider(); + } catch { + // ignore the error + } + + expect(createNetworkClientMock).not.toHaveBeenCalled(); + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + expect(provider).toBeNull(); + expect(blockTracker).toBeNull(); + }, + ); + }); + }); + + describe('if rpcUrl is missing from the provider config', () => { + it('throws', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + rpcUrl: undefined, + }), + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + await expect(() => + controller.initializeProvider(), + ).rejects.toThrow( + 'rpcUrl must be provided for custom RPC endpoints', + ); + }, + ); + }); + + it('does not create a network client or capture a provider', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + rpcUrl: undefined, + }), + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + try { + await controller.initializeProvider(); + } catch { + // ignore the error + } + + expect(createNetworkClientMock).not.toHaveBeenCalled(); + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + expect(provider).toBeNull(); + expect(blockTracker).toBeNull(); + }, + ); + }); }); }); }); @@ -623,294 +722,36 @@ describe('NetworkController', () => { }); describe('getEIP1559Compatibility', () => { - describe('when the latest block has a baseFeePerGas property', () => { - it('stores the fact that the network supports EIP-1559', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: {}, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - await controller.getEIP1559Compatibility(); - - expect(controller.store.getState().networkDetails.EIPS[1559]).toBe( - true, - ); - }, - ); - }); - - it('returns true', async () => { + describe('if no provider has been set yet', () => { + it('does not make any state changes', async () => { await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, + const promiseForNoStateChanges = waitForStateChanges({ + controller, + count: 0, + operation: async () => { + await controller.getEIP1559Compatibility(); }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + }); - const supportsEIP1559 = await controller.getEIP1559Compatibility(); - - expect(supportsEIP1559).toBeTruthy(); + expect(Boolean(promiseForNoStateChanges)).toBe(true); }); }); - }); - - describe('when the latest block does not have a baseFeePerGas property', () => { - it('stores the fact that the network does not support EIP-1559', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: {}, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - await controller.getEIP1559Compatibility(); - - expect(controller.store.getState().networkDetails.EIPS[1559]).toBe( - false, - ); - }, - ); - }); it('returns false', async () => { await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const supportsEIP1559 = await controller.getEIP1559Compatibility(); - - expect(supportsEIP1559).toBe(false); - }); - }); - }); - - describe('when the request for the latest block responds with null', () => { - it('persists false to state as whether the network supports EIP-1559', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: {}, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: null, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - + const isEIP1559Compatible = await controller.getEIP1559Compatibility(); - expect(controller.store.getState().networkDetails.EIPS[1559]).toBe( - false, - ); - }, - ); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: null, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const supportsEIP1559 = await controller.getEIP1559Compatibility(); - - expect(supportsEIP1559).toBe(false); + expect(isEIP1559Compatible).toBe(false); }); }); }); - it('does not make multiple requests to eth_getBlockByNumber when called multiple times and the request to eth_getBlockByNumber succeeded the first time', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingGetEIP1559Compatibility({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await controller.getEIP1559Compatibility(); - await controller.getEIP1559Compatibility(); - - expect( - fakeProvider.calledStubs.filter( - (stub) => stub.request.method === 'eth_getBlockByNumber', - ), - ).toHaveLength(1); - }); - }); - }); - - describe('lookupNetwork', () => { - describe('if the provider has not been initialized', () => { - it('does not update state in any way', async () => { - const providerConfig = { - type: NETWORK_TYPES.RPC, - rpcUrl: 'http://example-custom-rpc.metamask.io', - chainId: '0x9999' as const, - nickname: 'Test initial state', - }; - const initialState = { - providerConfig, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }; - + describe('if a provider has been set but networkDetails.EIPS in state already has a "1559" property', () => { + it('does not make any state changes', async () => { await withController( - { - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - const stateAfterConstruction = controller.store.getState(); - - await controller.lookupNetwork(); - - expect(controller.store.getState()).toStrictEqual( - stateAfterConstruction, - ); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }); - }); - }); - - describe('if the provider has initialized, but the current network has no chainId', () => { - it('does not update state in any way', async () => { - await withController( - /* @ts-expect-error We are intentionally not including a chainId in the provider config. */ { state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', - }, networkDetails: { EIPS: { 1559: true, @@ -919,102 +760,270 @@ describe('NetworkController', () => { }, }, async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const stateAfterInitialization = controller.store.getState(); + setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); + const promiseForNoStateChanges = waitForStateChanges({ + controller, + count: 0, + operation: async () => { + await controller.getEIP1559Compatibility(); + }, + }); - await controller.lookupNetwork(); - - expect(controller.store.getState()).toStrictEqual( - stateAfterInitialization, - ); + expect(Boolean(promiseForNoStateChanges)).toBe(true); }, ); }); - it('does not emit infuraIsUnblocked', async () => { + it('returns the value of the "1559" property', async () => { await withController( - /* @ts-expect-error We are intentionally not including a chainId in the provider config. */ { state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', + networkDetails: { + EIPS: { + 1559: true, + }, }, }, }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, + async ({ controller }) => { + setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, }); + const isEIP1559Compatible = + await controller.getEIP1559Compatibility(); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - /* @ts-expect-error We are intentionally not including a chainId in the provider config. */ - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); + expect(isEIP1559Compatible).toBe(true); }, ); }); }); - INFURA_NETWORKS.forEach(({ networkType }) => { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { + describe('if a provider has been set and networkDetails.EIPS in state does not already have a "1559" property', () => { + describe('if the request for the latest block is successful', () => { + describe('if the latest block has a "baseFeePerGas" property', () => { + it('sets the "1559" property to true', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: POST_1559_BLOCK, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await controller.getEIP1559Compatibility(); + + expect( + controller.store.getState().networkDetails.EIPS[1559], + ).toBe(true); + }); + }); + + it('returns true', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: POST_1559_BLOCK, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const isEIP1559Compatible = + await controller.getEIP1559Compatibility(); + + expect(isEIP1559Compatible).toBe(true); + }); + }); + }); + + describe('if the latest block does not have a "baseFeePerGas" property', () => { + it('sets the "1559" property to false', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: PRE_1559_BLOCK, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await controller.getEIP1559Compatibility(); + + expect( + controller.store.getState().networkDetails.EIPS[1559], + ).toBe(false); + }); + }); + + it('returns false', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: PRE_1559_BLOCK, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const isEIP1559Compatible = + await controller.getEIP1559Compatibility(); + + expect(isEIP1559Compatible).toBe(false); + }); + }); + }); + + describe('if the request for the latest block responds with null', () => { + it('sets the "1559" property to false', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: null, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await controller.getEIP1559Compatibility(); + + expect( + controller.store.getState().networkDetails.EIPS[1559], + ).toBe(false); + }); + }); + + it('returns false', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: null, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const isEIP1559Compatible = + await controller.getEIP1559Compatibility(); + + expect(isEIP1559Compatible).toBe(false); + }); + }); + }); + }); + + describe('if the request for the latest block is unsuccessful', () => { + it('does not make any state changes', async () => { + await withController(async ({ controller }) => { + setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const promiseForNoStateChanges = waitForStateChanges({ + controller, + count: 0, + operation: async () => { + try { + await controller.getEIP1559Compatibility(); + } catch (error) { + // ignore error + } + }, + }); + + expect(Boolean(promiseForNoStateChanges)).toBe(true); + }); + }); + }); + }); + }); + + describe('lookupNetwork', () => { + describe('if a provider has not been set', () => { + it('does not change network in state', async () => { + await withController(async ({ controller }) => { + const promiseForNetworkChanges = waitForStateChanges({ + controller, + propertyPath: ['networkId'], + }); + + await controller.lookupNetwork(); + + await expect(promiseForNetworkChanges).toNeverResolve(); + }); + }); + }); + + [ + NETWORK_TYPES.MAINNET, + NETWORK_TYPES.GOERLI, + NETWORK_TYPES.SEPOLIA, + ].forEach((networkType) => { + describe(`when the provider config in state contains a network type of "${networkType}"`, () => { + describe('if the network was switched after the net_version request started but before it completed', () => { it('stores the network status of the second network, not the first', async () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1024,13 +1033,12 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: { - result: '1', - }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, }, { request: { @@ -1038,27 +1046,29 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setActiveNetwork( + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( 'testNetworkConfigurationId', ); }, }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1079,19 +1089,12 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1103,6 +1106,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe( 'unknown', ); @@ -1114,17 +1118,12 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1134,12 +1133,13 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: { - result: '111', + result: '1', }, }, { @@ -1147,20 +1147,38 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, }, - ]), - buildFakeProvider([ + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, response: { - result: '222', + result: '1', + }, + beforeCompleting: async () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: { + result: '2', }, }, ]), @@ -1177,17 +1195,19 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ + await waitForStateChanges({ controller, + propertyPath: ['networkId'], operation: async () => { await controller.initializeProvider(); }, }); + expect(controller.store.getState().networkId).toBe('1'); await waitForStateChanges({ controller, @@ -1197,7 +1217,7 @@ describe('NetworkController', () => { }, }); - expect(controller.store.getState().networkId).toBe('222'); + expect(controller.store.getState().networkId).toBe('2'); }, ); }); @@ -1206,17 +1226,12 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, + providerConfig: buildProviderConfig({ type: networkType }), networkConfigurations: { testNetworkConfigurationId: { id: 'testNetworkConfigurationId', rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), ticker: 'ABC', }, }, @@ -1226,6 +1241,15 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, { request: { method: 'eth_getBlockByNumber', @@ -1233,14 +1257,42 @@ describe('NetworkController', () => { response: { result: POST_1559_BLOCK, }, - beforeCompleting: async () => { - await controller.setActiveNetwork( + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( 'testNetworkConfigurationId', ); }, }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, ]), buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, + }, { request: { method: 'eth_getBlockByNumber', @@ -1263,15 +1315,17 @@ describe('NetworkController', () => { }) .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); + await controller.initializeProvider(); + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: true, }, }); @@ -1294,25 +1348,18 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { - const anotherNetwork = INFURA_NETWORKS.find( - (network) => network.networkType !== networkType, - ); - /* eslint-disable-next-line jest/no-if */ - if (!anotherNetwork) { - throw new Error( - "Could not find another network to use. You've probably commented out all INFURA_NETWORKS but one. Please uncomment another one.", - ); - } - + it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { await withController( { state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, }, }, infuraProjectId: 'some-infura-project-id', @@ -1320,19 +1367,33 @@ describe('NetworkController', () => { async ({ controller, messenger }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, { request: { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType( - anotherNetwork.networkType, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', ); }, }, - ]), - buildFakeProvider([ { request: { method: 'eth_getBlockByNumber', @@ -1340,6 +1401,21 @@ describe('NetworkController', () => { error: BLOCKED_INFURA_JSON_RPC_ERROR, }, ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), ]; const fakeNetworkClients = [ buildFakeClient(fakeProviders[0]), @@ -1351,34 +1427,485 @@ describe('NetworkController', () => { infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ - network: anotherNetwork.networkType, + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const promiseForInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + }); + const promiseForNoInfuraIsBlockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + }); + + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + await expect(promiseForInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForNoInfuraIsBlockedEvents).toBeFulfilled(); + }, + ); + }); + }); + + describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { + it('stores the network status of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect(controller.store.getState().networkStatus).toBe( + 'available', + ); + + await waitForStateChanges({ controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); + }, + ); + }); + + it('stores the ID of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: async () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], operation: async () => { await controller.initializeProvider(); }, }); - const promiseForNoInfuraIsUnblockedEvents = + expect(controller.store.getState().networkId).toBe('1'); + + await waitForStateChanges({ + controller, + propertyPath: ['networkId'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + expect(controller.store.getState().networkId).toBe('2'); + }, + ); + }); + + it('stores the EIP-1559 support of the second network, not the first', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: { + result: '1', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: { + result: '2', + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: PRE_1559_BLOCK, + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); + + await waitForStateChanges({ + controller, + propertyPath: ['networkDetails'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: { + 1559: false, + }, + }); + }, + ); + }); + + it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ type: networkType }), + networkConfigurations: { + testNetworkConfigurationId: { + id: 'testNetworkConfigurationId', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'ABC', + }, + }, + }, + infuraProjectId: 'some-infura-project-id', + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setActiveNetwork( + 'testNetworkConfigurationId', + ); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + mockCreateNetworkClient() + .calledWith({ + network: networkType, + infuraProjectId: 'some-infura-project-id', + type: NetworkClientType.Infura, + }) + .mockReturnValue(fakeNetworkClients[0]) + .calledWith({ + chainId: toHex(1337), + rpcUrl: 'https://mock-rpc-url', + type: NetworkClientType.Custom, + }) + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); + const promiseForInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsUnblocked', + }); + const promiseForNoInfuraIsBlockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', count: 0, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', + + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, }); - await controller.lookupNetwork(); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForNoInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -1396,23 +1923,24 @@ describe('NetworkController', () => { }); }); - describe('when the type in the provider configuration is "rpc"', () => { + describe(`when the provider config in state contains a network type of "rpc"`, () => { describe('if the network was switched after the net_version request started but before it completed', () => { it('stores the network status of the second network, not the first', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1425,13 +1953,16 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1442,9 +1973,10 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { - method: 'eth_getBlockByNumber', + method: 'net_version', }, error: GENERIC_JSON_RPC_ERROR, }, @@ -1456,24 +1988,18 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1485,6 +2011,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); @@ -1494,18 +2021,18 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1520,6 +2047,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -1528,7 +2056,9 @@ describe('NetworkController', () => { result: '1', }, beforeCompleting: async () => { - await controller.setProviderType('goerli'); + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1539,6 +2069,7 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1547,12 +2078,6 @@ describe('NetworkController', () => { result: '2', }, }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, ]), ]; const fakeNetworkClients = [ @@ -1561,17 +2086,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); + .mockReturnValue(fakeNetworkClients[1]); await waitForStateChanges({ controller, propertyPath: ['networkId'], @@ -1598,27 +2123,25 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '1', + }, }, { request: { @@ -1628,13 +2151,18 @@ describe('NetworkController', () => { result: POST_1559_BLOCK, }, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + response: { + result: '1', + }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1647,11 +2175,14 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '2', + }, }, { request: { @@ -1669,37 +2200,27 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, }, - other: 'details', }); await waitForStateChanges({ controller, propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, operation: async () => { await controller.lookupNetwork(); }, @@ -1714,29 +2235,44 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, { @@ -1747,6 +2283,7 @@ describe('NetworkController', () => { }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1767,38 +2304,39 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsUnblocked', count: 0, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ + const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsBlocked', }); - await controller.lookupNetwork(); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -1809,21 +2347,18 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1836,6 +2371,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -1847,15 +2383,18 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { - method: 'eth_getBlockByNumber', + method: 'net_version', }, error: GENERIC_JSON_RPC_ERROR, }, @@ -1867,24 +2406,18 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkStatus).toBe( 'available', ); @@ -1896,27 +2429,28 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkStatus).toBe('unknown'); }, ); }); - it('stores the network ID of the second network, not the first', async () => { + it('stores the ID of the second network, not the first', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', @@ -1931,6 +2465,7 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', @@ -1945,11 +2480,14 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, beforeCompleting: async () => { - await controller.setProviderType('goerli'); + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -1958,12 +2496,6 @@ describe('NetworkController', () => { result: '2', }, }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, ]), ]; const fakeNetworkClients = [ @@ -1972,17 +2504,17 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); + .mockReturnValue(fakeNetworkClients[1]); await waitForStateChanges({ controller, propertyPath: ['networkId'], @@ -1999,6 +2531,7 @@ describe('NetworkController', () => { await controller.lookupNetwork(); }, }); + expect(controller.store.getState().networkId).toBe('2'); }, ); @@ -2008,27 +2541,25 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '1', + }, }, { request: { @@ -2038,13 +2569,13 @@ describe('NetworkController', () => { result: POST_1559_BLOCK, }, }, + // Called via `lookupNetwork` directly { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); + response: { + result: '1', }, }, { @@ -2054,14 +2585,22 @@ describe('NetworkController', () => { response: { result: POST_1559_BLOCK, }, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); + }, }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, + response: { + result: '2', + }, }, { request: { @@ -2079,37 +2618,27 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, }, - other: 'details', }); await waitForStateChanges({ controller, propertyPath: ['networkDetails'], - // setProviderType clears networkDetails first, and then updates - // it to what we expect it to be - count: 2, operation: async () => { await controller.lookupNetwork(); }, @@ -2124,30 +2653,27 @@ describe('NetworkController', () => { ); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network is blocked, even if the first one is not', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'RPC', - }, + }), }, infuraProjectId: 'some-infura-project-id', }, async ({ controller, messenger }) => { const fakeProviders = [ buildFakeProvider([ + // Called during provider initialization { request: { method: 'net_version', }, response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: async () => { - await controller.setProviderType('goerli'); - }, }, { request: { @@ -2155,8 +2681,27 @@ describe('NetworkController', () => { }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, + // Called via `lookupNetwork` directly + { + request: { + method: 'net_version', + }, + response: SUCCESSFUL_NET_VERSION_RESPONSE, + }, + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // Intentionally not awaited because don't want this to + // block the `net_version` request + controller.setProviderType(NETWORK_TYPES.GOERLI); + }, + }, ]), buildFakeProvider([ + // Called when switching networks { request: { method: 'net_version', @@ -2177,38 +2722,39 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - chainId: '0x1337', + chainId: toHex(1337), rpcUrl: 'https://mock-rpc-url', type: NetworkClientType.Custom, }) - .mockReturnValueOnce(fakeNetworkClients[0]) + .mockReturnValue(fakeNetworkClients[0]) .calledWith({ network: NETWORK_TYPES.GOERLI, infuraProjectId: 'some-infura-project-id', type: NetworkClientType.Infura, }) - .mockReturnValueOnce(fakeNetworkClients[1]); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); + .mockReturnValue(fakeNetworkClients[1]); + await controller.initializeProvider(); const promiseForNoInfuraIsUnblockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsUnblocked', count: 0, }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ + const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ messenger, eventType: 'NetworkController:infuraIsBlocked', }); - await controller.lookupNetwork(); + await waitForStateChanges({ + controller, + propertyPath: ['networkStatus'], + operation: async () => { + await controller.lookupNetwork(); + }, + }); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); }, ); }); @@ -2235,6 +2781,7 @@ describe('NetworkController', () => { chainId: toHex(111), ticker: 'TEST', nickname: 'something existing', + id: 'testNetworkConfigurationId', rpcPrefs: undefined, type: NETWORK_TYPES.RPC, }, @@ -2255,34 +2802,146 @@ describe('NetworkController', () => { }, }); - it('throws if the given networkConfigurationId does not match one in networkConfigurations', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', + describe('if the given ID does not match a network configuration in networkConfigurations', () => { + it('throws', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, }, }, }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await expect(() => - controller.setActiveNetwork('invalid-network-configuration-id'), - ).rejects.toThrow( - new Error( - 'networkConfigurationId invalid-network-configuration-id does not match a configured networkConfiguration', - ), - ); - }, - ); + await expect(() => + controller.setActiveNetwork('invalidNetworkConfigurationId'), + ).rejects.toThrow( + new Error( + 'networkConfigurationId invalidNetworkConfigurationId does not match a configured networkConfiguration', + ), + ); + }, + ); + }); + }); + + describe('if the network config does not contain an RPC URL', () => { + it('throws', async () => { + await withController( + // @ts-expect-error RPC URL intentionally omitted + { + state: { + providerConfig: { + type: NETWORK_TYPES.RPC, + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + nickname: 'something existing', + rpcPrefs: undefined, + }, + networkConfigurations: { + testNetworkConfigurationId1: { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + nickname: 'something existing', + id: 'testNetworkConfigurationId1', + rpcPrefs: undefined, + }, + testNetworkConfigurationId2: { + rpcUrl: undefined, + chainId: toHex(222), + ticker: 'something existing', + nickname: 'something existing', + id: 'testNetworkConfigurationId2', + rpcPrefs: undefined, + }, + }, + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + await expect(() => + controller.setActiveNetwork('testNetworkConfigurationId2'), + ).rejects.toThrow( + 'rpcUrl must be provided for custom RPC endpoints', + ); + + expect(createNetworkClientMock).not.toHaveBeenCalled(); + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + expect(provider).toBeNull(); + expect(blockTracker).toBeNull(); + }, + ); + }); + }); + + describe('if the network config does not contain a chain ID', () => { + it('throws', async () => { + await withController( + // @ts-expect-error chain ID intentionally omitted + { + state: { + providerConfig: { + type: NETWORK_TYPES.RPC, + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + nickname: 'something existing', + rpcPrefs: undefined, + }, + networkConfigurations: { + testNetworkConfigurationId1: { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + nickname: 'something existing', + id: 'testNetworkConfigurationId1', + rpcPrefs: undefined, + }, + testNetworkConfigurationId2: { + rpcUrl: 'http://somethingexisting.com', + chainId: undefined, + ticker: 'something existing', + nickname: 'something existing', + id: 'testNetworkConfigurationId2', + rpcPrefs: undefined, + }, + }, + }, + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + await expect(() => + controller.setActiveNetwork('testNetworkConfigurationId2'), + ).rejects.toThrow( + 'chainId must be provided for custom RPC endpoints', + ); + + expect(createNetworkClientMock).not.toHaveBeenCalled(); + const { provider, blockTracker } = + controller.getProviderAndBlockTracker(); + expect(provider).toBeNull(); + expect(blockTracker).toBeNull(); + }, + ); + }); }); it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { @@ -2339,7 +2998,7 @@ describe('NetworkController', () => { ticker, blockExplorerUrl, } of INFURA_NETWORKS) { - describe(`given a type of "${networkType}"`, () => { + describe(`given a network type of "${networkType}"`, () => { refreshNetworkTests({ expectedProviderConfig: buildProviderConfig({ type: networkType, @@ -2348,52 +3007,113 @@ describe('NetworkController', () => { await controller.setProviderType(networkType); }, }); + }); - it(`overwrites the provider configuration using type: "${networkType}", chainId: "${chainId}", ticker "${ticker}", and blockExplorerUrl "${blockExplorerUrl}", clearing rpcUrl and nickname`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, + it(`overwrites the provider configuration using a predetermined chainId, ticker, and blockExplorerUrl for "${networkType}", clearing id, rpcUrl, and nickname`, async () => { + await withController( + { + state: { + providerConfig: { + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + nickname: 'test-chain', + ticker: 'TEST', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', }, }, }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + }, + async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - controller.setProviderType(networkType); + await controller.setProviderType(networkType); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, - }); - }, - ); - }); + expect(controller.store.getState().providerConfig).toStrictEqual({ + type: networkType, + rpcUrl: undefined, + chainId, + ticker, + nickname: undefined, + rpcPrefs: { blockExplorerUrl }, + id: undefined, + }); + }, + ); }); } - describe('given a type of "rpc"', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - await expect(() => controller.setProviderType('rpc')).rejects.toThrow( - new Error( + describe('given a network type of "rpc"', () => { + it('throws because there is no way to switch to a custom RPC endpoint using this method', async () => { + await withController( + { + state: { + providerConfig: { + type: NETWORK_TYPES.RPC, + rpcUrl: 'http://somethingexisting.com', + chainId: toHex(99999), + ticker: 'something existing', + nickname: 'something existing', + }, + }, + }, + async ({ controller }) => { + await expect(() => + controller.setProviderType(NETWORK_TYPES.RPC), + ).rejects.toThrow( 'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"', - ), - ); + ); + }, + ); + }); + + it("doesn't set a provider", async () => { + await withController(async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + try { + await controller.setProviderType(NETWORK_TYPES.RPC); + } catch { + // catch the rejection (it is tested above) + } + + expect(createNetworkClientMock).not.toHaveBeenCalled(); + expect(controller.getProviderAndBlockTracker().provider).toBeNull(); + }); + }); + + it('does not update networkDetails.EIPS in state', async () => { + await withController(async ({ controller }) => { + const fakeProvider = buildFakeProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + + try { + await controller.setProviderType(NETWORK_TYPES.RPC); + } catch { + // catch the rejection (it is tested above) + } + + expect( + controller.store.getState().networkDetails.EIPS[1559], + ).toBeUndefined(); }); }); }); @@ -2402,9 +3122,9 @@ describe('NetworkController', () => { it('throws', async () => { await withController(async ({ controller }) => { await expect(() => - controller.setProviderType('sadlflaksdj'), + controller.setProviderType('invalid-infura-network'), ).rejects.toThrow( - new Error('Unknown Infura provider type "sadlflaksdj".'), + new Error('Unknown Infura provider type "invalid-infura-network".'), ); }); }); @@ -2445,36 +3165,176 @@ describe('NetworkController', () => { }); }); + describe('NetworkController:getProviderConfig action', () => { + it('returns the provider config in state', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.MAINNET, + }), + }, + }, + async ({ messenger }) => { + const providerConfig = await messenger.call( + 'NetworkController:getProviderConfig', + ); + + expect(providerConfig).toStrictEqual( + buildProviderConfig({ + type: NETWORK_TYPES.MAINNET, + }), + ); + }, + ); + }); + }); + + describe('NetworkController:getEthQuery action', () => { + it('returns a EthQuery object that can be used to make requests to the currently selected network', async () => { + await withController(async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'test_method', + params: [], + }, + response: { + result: 'test response', + }, + }, + ], + }); + + const ethQuery = messenger.call('NetworkController:getEthQuery'); + assert(ethQuery, 'ethQuery is not set'); + + const promisifiedSendAsync = promisify(ethQuery.sendAsync).bind( + ethQuery, + ); + const result = await promisifiedSendAsync({ + id: 1, + jsonrpc: '2.0', + method: 'test_method', + params: [], + }); + expect(result).toBe('test response'); + }); + }); + + it('returns undefined if the provider has not been set yet', async () => { + await withController(({ messenger }) => { + const ethQuery = messenger.call('NetworkController:getEthQuery'); + + expect(ethQuery).toBeUndefined(); + }); + }); + }); + describe('rollbackToPreviousProvider', () => { for (const { networkType } of INFURA_NETWORKS) { describe(`if the previous provider configuration had a type of "${networkType}"`, () => { + it('emits networkWillChange', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: networkType, + }), + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setActiveNetwork('testNetworkConfiguration'); + + const networkWillChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkWillChange).toBeFulfilled(); + }, + ); + }); + + it('emits networkDidChange', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: networkType, + }), + networkConfigurations: { + testNetworkConfiguration: { + id: 'testNetworkConfiguration', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }, + }, + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setActiveNetwork('testNetworkConfiguration'); + + const networkDidChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkDidChange).toBeFulfilled(); + }, + ); + }); + it('overwrites the the current provider configuration with the previous provider configuration', async () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x111', - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - rpcUrl: 'https://mock-rpc-url-1', - ticker: 'TEST1', - nickname: 'test network 1', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-1.com', - }, - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', + blockExplorerUrl: 'https://test-block-explorer.com', }, }, }, @@ -2489,8 +3349,8 @@ describe('NetworkController', () => { ]; mockCreateNetworkClient() .calledWith({ - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -2504,111 +3364,38 @@ describe('NetworkController', () => { expect(controller.store.getState().providerConfig).toStrictEqual({ type: 'rpc', id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url-2', - chainId: '0x222', - ticker: 'TEST2', - nickname: 'test network 2', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + ticker: 'TEST', + nickname: 'test network', rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', + blockExplorerUrl: 'https://test-block-explorer.com', }, }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: networkType, - chainId: '0x111', - rpcUrl: 'https://mock-rpc-url-1', - ticker: 'TEST1', - nickname: 'test network 1', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-1.com', - }, - }); + expect(controller.store.getState().providerConfig).toStrictEqual( + buildProviderConfig({ + type: networkType, + }), + ); }, ); }); - it('emits networkWillChange', async () => { + it('resets the network status to "unknown" before updating the provider', async () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(networkWillChange).toBeTruthy(); - }, - }); - }, - ); - }); - - it('resets the network status to "unknown" before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, @@ -2640,7 +3427,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -2655,54 +3442,42 @@ describe('NetworkController', () => { 'available', ); - await waitForLookupNetworkToComplete({ + await waitForStateChanges({ controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); + propertyPath: ['networkStatus'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); }, }); }, ); }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + it('clears EIP-1559 support for the network from state before updating the provider', async () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, - networkDetails: { - EIPS: {}, - other: 'details', - }, }, infuraProjectId: 'some-infura-project-id', }, @@ -2727,7 +3502,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -2744,29 +3519,22 @@ describe('NetworkController', () => { }, }); - await waitForLookupNetworkToComplete({ + await waitForStateChanges({ controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, + propertyPath: ['networkDetails'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: {}, }); }, }); @@ -2778,17 +3546,14 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, @@ -2816,7 +3581,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -2828,12 +3593,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[1]); await controller.setActiveNetwork('testNetworkConfiguration'); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); const { provider } = controller.getProviderAndBlockTracker(); assert(provider, 'Provider is somehow unset'); @@ -2854,17 +3614,14 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, @@ -2880,7 +3637,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -2894,12 +3651,7 @@ describe('NetworkController', () => { const { provider: providerBefore } = controller.getProviderAndBlockTracker(); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); const { provider: providerAfter } = controller.getProviderAndBlockTracker(); @@ -2908,82 +3660,18 @@ describe('NetworkController', () => { ); }); - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - - await controller.setActiveNetwork('testNetworkConfiguration'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(networkDidChange).toBeTruthy(); - }, - }); - }, - ); - }); - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, @@ -3009,7 +3697,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -3031,15 +3719,10 @@ describe('NetworkController', () => { eventType: 'NetworkController:infuraIsBlocked', }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - expect(await promiseForInfuraIsBlocked).toBeTruthy(); + await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); + await expect(promiseForInfuraIsBlocked).toBeFulfilled(); }, ); }); @@ -3048,17 +3731,14 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, @@ -3091,7 +3771,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -3124,24 +3804,17 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { + providerConfig: buildProviderConfig({ type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x111', - }, + }), networkConfigurations: { testNetworkConfiguration: { id: 'testNetworkConfiguration', rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), ticker: 'TEST', }, }, - networkDetails: { - EIPS: {}, - other: 'details', - }, }, infuraProjectId: 'some-infura-project-id', }, @@ -3175,7 +3848,7 @@ describe('NetworkController', () => { mockCreateNetworkClient() .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x222', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[0]) @@ -3214,20 +3887,80 @@ describe('NetworkController', () => { } describe(`if the previous provider configuration had a type of "rpc"`, () => { + it('emits networkWillChange', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + }), + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setProviderType(NETWORK_TYPES.GOERLI); + + const networkWillChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkWillChange', + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkWillChange).toBeFulfilled(); + }, + ); + }); + + it('emits networkDidChange', async () => { + await withController( + { + state: { + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + }), + }, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.setProviderType(NETWORK_TYPES.GOERLI); + + const networkDidChange = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:networkDidChange', + operation: () => { + // Intentionally not awaited because we're capturing an event + // emitted partway through the operation + controller.rollbackToPreviousProvider(); + }, + }); + + await expect(networkDidChange).toBeFulfilled(); + }, + ); + }); + it('overwrites the the current provider configuration with the previous provider configuration', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), nickname: 'network', ticker: 'TEST', rpcPrefs: { blockExplorerUrl: 'https://test-block-explorer.com', }, - }, + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3246,7 +3979,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); @@ -3254,94 +3987,41 @@ describe('NetworkController', () => { expect(controller.store.getState().providerConfig).toStrictEqual({ type: 'goerli', rpcUrl: undefined, - chainId: '0x5', + chainId: toHex(5), ticker: 'GoerliETH', nickname: undefined, rpcPrefs: { blockExplorerUrl: 'https://goerli.etherscan.io', }, + id: undefined, }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }); + await controller.rollbackToPreviousProvider(); + expect(controller.store.getState().providerConfig).toStrictEqual( + buildProviderConfig({ + type: 'rpc', + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(1337), + nickname: 'network', + ticker: 'TEST', + rpcPrefs: { + blockExplorerUrl: 'https://test-block-explorer.com', + }, + }), + ); }, ); }); - it('emits networkWillChange', async () => { + it('resets the network state to "unknown" before updating the provider', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(networkWillChange).toBeTruthy(); - }, - }); - }, - ); - }); - - it('resets the network state to "unknown" before emitting networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3376,48 +4056,43 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); await controller.setProviderType('goerli'); expect(controller.store.getState().networkStatus).toBe('available'); - await waitForLookupNetworkToComplete({ + await waitForStateChanges({ controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); + propertyPath: ['networkStatus'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect(controller.store.getState().networkStatus).toBe( + 'unknown', + ); }, }); }, ); }); - it('clears EIP-1559 support for the network from state before emitting networkDidChange', async () => { + it('clears EIP-1559 support for the network from state before updating the provider', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3448,7 +4123,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); @@ -3459,29 +4134,22 @@ describe('NetworkController', () => { }, }); - await waitForLookupNetworkToComplete({ + await waitForStateChanges({ controller, - operation: async () => { - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, + propertyPath: ['networkDetails'], + // We only care about the first state change, because it + // happens before networkDidChange + count: 1, + operation: () => { + // Intentionally not awaited because we want to check state + // while this operation is in-progress + controller.rollbackToPreviousProvider(); + }, + beforeResolving: () => { + expect( + controller.store.getState().networkDetails, + ).toStrictEqual({ + EIPS: {}, }); }, }); @@ -3493,11 +4161,11 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3528,18 +4196,13 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); await controller.setProviderType('goerli'); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); const { provider } = controller.getProviderAndBlockTracker(); assert(provider, 'Provider is somehow unset'); @@ -3560,11 +4223,11 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3583,7 +4246,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); @@ -3591,12 +4254,7 @@ describe('NetworkController', () => { const { provider: providerBefore } = controller.getProviderAndBlockTracker(); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); const { provider: providerAfter } = controller.getProviderAndBlockTracker(); @@ -3605,66 +4263,15 @@ describe('NetworkController', () => { ); }); - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NETWORK_TYPES.GOERLI, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(networkDidChange).toBeTruthy(); - }, - }); - }, - ); - }); - it('emits infuraIsUnblocked', async () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3683,26 +4290,21 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); await controller.setProviderType('goerli'); - await waitForLookupNetworkToComplete({ - controller, + const promiseForInfuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', operation: async () => { - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - expect(infuraIsUnblocked).toBeTruthy(); + await controller.rollbackToPreviousProvider(); }, }); + + await expect(promiseForInfuraIsUnblocked).toBeFulfilled(); }, ); }); @@ -3711,11 +4313,11 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3757,7 +4359,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); @@ -3776,15 +4378,11 @@ describe('NetworkController', () => { await withController( { state: { - providerConfig: { - type: 'rpc', + providerConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, + chainId: toHex(1337), + }), }, infuraProjectId: 'some-infura-project-id', }, @@ -3824,7 +4422,7 @@ describe('NetworkController', () => { .mockReturnValue(fakeNetworkClients[0]) .calledWith({ rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: toHex(1337), type: NetworkClientType.Custom, }) .mockReturnValue(fakeNetworkClients[1]); @@ -3835,12 +4433,7 @@ describe('NetworkController', () => { }, }); - await waitForLookupNetworkToComplete({ - controller, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); + await controller.rollbackToPreviousProvider(); expect(controller.store.getState().networkDetails).toStrictEqual({ EIPS: { 1559: true, @@ -3853,13 +4446,92 @@ describe('NetworkController', () => { }); describe('upsertNetworkConfiguration', () => { + it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { + uuidV4Mock.mockImplementationOnce(() => 'network-configuration-id-1'); + + await withController(async ({ controller }) => { + const rpcUrlNetwork = { + chainId: toHex(9999), + rpcUrl: 'https://test-rpc.com', + ticker: 'RPC', + }; + + expect(controller.store.getState().networkConfigurations).toStrictEqual( + {}, + ); + + await controller.upsertNetworkConfiguration(rpcUrlNetwork, { + referrer: 'https://test-dapp.com', + source: 'dapp', + }); + + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + ...rpcUrlNetwork, + nickname: undefined, + rpcPrefs: undefined, + id: 'network-configuration-id-1', + }, + ]), + ); + }); + }); + + it('update a network configuration when the configuration being added has an rpcURL that matches an existing configuration', async () => { + await withController( + { + state: { + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://rpc-url.com', + ticker: 'old_rpc_ticker', + nickname: 'old_rpc_nickname', + rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, + chainId: toHex(1), + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + async ({ controller }) => { + await controller.upsertNetworkConfiguration( + { + rpcUrl: 'https://rpc-url.com', + ticker: 'new_rpc_ticker', + nickname: 'new_rpc_nickname', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: toHex(1), + }, + { referrer: 'https://test-dapp.com', source: 'dapp' }, + ); + expect( + Object.values(controller.store.getState().networkConfigurations), + ).toStrictEqual( + expect.arrayContaining([ + { + rpcUrl: 'https://rpc-url.com', + nickname: 'new_rpc_nickname', + ticker: 'new_rpc_ticker', + rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, + chainId: toHex(1), + id: 'testNetworkConfigurationId', + }, + ]), + ); + }, + ); + }); + it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { const invalidChainId = '1'; await withController(async ({ controller }) => { - await expect(() => + await expect(async () => controller.upsertNetworkConfiguration( { - /* @ts-expect-error We are intentionally passing bad input. */ + // @ts-expect-error Intentionally invalid chainId: invalidChainId, nickname: 'RPC', rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, @@ -3868,7 +4540,7 @@ describe('NetworkController', () => { }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ), ).rejects.toThrow( @@ -3881,7 +4553,7 @@ describe('NetworkController', () => { it('throws if the given chain ID is greater than the maximum allowed ID', async () => { await withController(async ({ controller }) => { - await expect(() => + await expect(async () => controller.upsertNetworkConfiguration( { chainId: '0xFFFFFFFFFFFFFFFF', @@ -3892,7 +4564,7 @@ describe('NetworkController', () => { }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ), ).rejects.toThrow( @@ -3909,14 +4581,14 @@ describe('NetworkController', () => { controller.upsertNetworkConfiguration( /* @ts-expect-error We are intentionally passing bad input. */ { - chainId: '0x9999', + chainId: toHex(9999), nickname: 'RPC', rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, ticker: 'RPC', }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ), ).rejects.toThrow( @@ -3929,10 +4601,10 @@ describe('NetworkController', () => { it('throws if rpcUrl passed is not a valid Url', async () => { await withController(async ({ controller }) => { - await expect(() => + await expect(async () => controller.upsertNetworkConfiguration( { - chainId: '0x9999', + chainId: toHex(9999), nickname: 'RPC', rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, ticker: 'RPC', @@ -3940,7 +4612,7 @@ describe('NetworkController', () => { }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ), ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); @@ -3949,18 +4621,18 @@ describe('NetworkController', () => { it('throws if the no (or a falsy) ticker is passed', async () => { await withController(async ({ controller }) => { - await expect(() => + await expect(async () => controller.upsertNetworkConfiguration( - /* @ts-expect-error We are intentionally passing bad input. */ + // @ts-expect-error - we want to test the case where no ticker is present. { - chainId: '0x5', + chainId: toHex(5), nickname: 'RPC', rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, rpcUrl: 'https://mock-rpc-url', }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ), ).rejects.toThrow( @@ -3973,22 +4645,63 @@ describe('NetworkController', () => { it('throws if an options object is not passed as a second argument', async () => { await withController(async ({ controller }) => { - await expect(() => - /* @ts-expect-error We are intentionally passing bad input. */ + await expect(async () => + // @ts-expect-error - we want to test the case where no second arg is passed. controller.upsertNetworkConfiguration({ - chainId: '0x5', + chainId: toHex(5), nickname: 'RPC', rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, rpcUrl: 'https://mock-rpc-url', }), - ).rejects.toThrow( - new Error( - "Cannot read properties of undefined (reading 'setActive')", - ), - ); + ).rejects.toThrow('Cannot read properties of undefined'); }); }); + it('throws if referrer and source arguments are not passed', async () => { + uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); + const trackEventSpy = jest.fn(); + await withController( + { + state: { + providerConfig: { + type: NETWORK_TYPES.RPC, + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: toHex(111), + ticker: 'TEST', + id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, + }, + }, + }, + trackMetaMetricsEvent: trackEventSpy, + }, + async ({ controller }) => { + const newNetworkConfiguration = { + rpcUrl: 'https://new-chain-rpc-url', + chainId: toHex(222), + ticker: 'NEW', + nickname: 'new-chain', + rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, + }; + + await expect(async () => + // @ts-expect-error - we want to test the case where the options object is empty. + controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), + ).rejects.toThrow( + 'referrer and source are required arguments for adding or updating a network configuration', + ); + }, + ); + }); + it('should add the given network if all required properties are present but nither rpcPrefs nor nickname properties are passed', async () => { uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); await withController( @@ -3999,14 +4712,14 @@ describe('NetworkController', () => { }, async ({ controller }) => { const rpcUrlNetwork = { - chainId: '0x1' as const, + chainId: toHex(1), rpcUrl: 'https://test-rpc-url', ticker: 'test_ticker', }; await controller.upsertNetworkConfiguration(rpcUrlNetwork, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect( @@ -4035,7 +4748,7 @@ describe('NetworkController', () => { }, async ({ controller }) => { const rpcUrlNetwork = { - chainId: '0x1' as const, + chainId: toHex(1), rpcUrl: 'https://test-rpc-url', ticker: 'test_ticker', invalidKey: 'new-chain', @@ -4044,7 +4757,7 @@ describe('NetworkController', () => { await controller.upsertNetworkConfiguration(rpcUrlNetwork, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect( @@ -4052,7 +4765,7 @@ describe('NetworkController', () => { ).toStrictEqual( expect.arrayContaining([ { - chainId: '0x1', + chainId: toHex(1), rpcUrl: 'https://test-rpc-url', ticker: 'test_ticker', nickname: undefined, @@ -4076,7 +4789,7 @@ describe('NetworkController', () => { ticker: 'ticker', nickname: 'nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, }, @@ -4084,7 +4797,7 @@ describe('NetworkController', () => { }, async ({ controller }) => { const rpcUrlNetwork = { - chainId: '0x1' as const, + chainId: toHex(1), nickname: 'RPC', rpcPrefs: undefined, rpcUrl: 'https://test-rpc-url-2', @@ -4093,7 +4806,7 @@ describe('NetworkController', () => { await controller.upsertNetworkConfiguration(rpcUrlNetwork, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect( @@ -4105,7 +4818,7 @@ describe('NetworkController', () => { ticker: 'ticker', nickname: 'nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, @@ -4125,7 +4838,7 @@ describe('NetworkController', () => { ticker: 'old_rpc_ticker', nickname: 'old_rpc_chainName', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, }, @@ -4138,11 +4851,11 @@ describe('NetworkController', () => { ticker: 'new_rpc_ticker', nickname: 'new_rpc_chainName', rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1' as const, + chainId: toHex(1), }; await controller.upsertNetworkConfiguration(updatedConfiguration, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect( Object.values(controller.store.getState().networkConfigurations), @@ -4152,7 +4865,7 @@ describe('NetworkController', () => { nickname: 'new_rpc_chainName', ticker: 'new_rpc_ticker', rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, ]); @@ -4170,7 +4883,7 @@ describe('NetworkController', () => { ticker: 'ticker', nickname: 'nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, networkConfigurationId2: { @@ -4178,7 +4891,7 @@ describe('NetworkController', () => { ticker: 'ticker-2', nickname: 'nickname-2', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x9999', + chainId: toHex(9999), id: 'networkConfigurationId2', }, }, @@ -4191,11 +4904,11 @@ describe('NetworkController', () => { ticker: 'new-ticker', nickname: 'new-nickname', rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), }, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }, ); @@ -4207,7 +4920,7 @@ describe('NetworkController', () => { ticker: 'new-ticker', nickname: 'new-nickname', rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: '0x1', + chainId: toHex(1), id: 'networkConfigurationId', }, { @@ -4215,7 +4928,7 @@ describe('NetworkController', () => { ticker: 'ticker-2', nickname: 'nickname-2', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x9999', + chainId: toHex(9999), id: 'networkConfigurationId2', }, ]); @@ -4225,40 +4938,43 @@ describe('NetworkController', () => { it('should add the given network and not set it to active if the setActive option is not passed (or a falsy value is passed)', async () => { uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const originalProviderConfig = { + const originalProvider = { type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest' as const, + chainId: toHex(111), ticker: 'TEST', + id: 'testNetworkConfigurationId', }; await withController( { state: { - providerConfig: originalProviderConfig, + providerConfig: originalProvider, networkConfigurations: { testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, }, }, }, async ({ controller }) => { const rpcUrlNetwork = { - chainId: '0x1' as const, + chainId: toHex(222), rpcUrl: 'https://test-rpc-url', ticker: 'test_ticker', }; await controller.upsertNetworkConfiguration(rpcUrlNetwork, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect(controller.store.getState().providerConfig).toStrictEqual( - originalProviderConfig, + originalProvider, ); }, ); @@ -4270,17 +4986,22 @@ describe('NetworkController', () => { { state: { providerConfig: { - type: 'rpc', + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', + id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, networkConfigurations: { testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, }, }, @@ -4288,25 +5009,27 @@ describe('NetworkController', () => { async ({ controller }) => { const fakeProvider = buildFakeProvider(); const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); const rpcUrlNetwork = { - chainId: '0x1' as const, rpcUrl: 'https://test-rpc-url', + chainId: toHex(222), ticker: 'test_ticker', }; await controller.upsertNetworkConfiguration(rpcUrlNetwork, { setActive: true, referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect(controller.store.getState().providerConfig).toStrictEqual({ - ...rpcUrlNetwork, + type: 'rpc', + rpcUrl: 'https://test-rpc-url', + chainId: toHex(222), + ticker: 'test_ticker', + id: 'networkConfigurationId', nickname: undefined, rpcPrefs: undefined, - type: 'rpc', - id: 'networkConfigurationId', }); }, ); @@ -4319,17 +5042,22 @@ describe('NetworkController', () => { { state: { providerConfig: { - type: 'rpc', + type: NETWORK_TYPES.RPC, rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', + id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, networkConfigurations: { testNetworkConfigurationId: { rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, }, }, @@ -4338,7 +5066,7 @@ describe('NetworkController', () => { async ({ controller }) => { const newNetworkConfiguration = { rpcUrl: 'https://new-chain-rpc-url', - chainId: '0x9999' as const, + chainId: toHex(222), ticker: 'NEW', nickname: 'new-chain', rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, @@ -4346,7 +5074,7 @@ describe('NetworkController', () => { await controller.upsertNetworkConfiguration(newNetworkConfiguration, { referrer: 'https://test-dapp.com', - source: MetaMetricsNetworkEventSource.Dapp, + source: 'dapp', }); expect( @@ -4354,9 +5082,11 @@ describe('NetworkController', () => { ).toStrictEqual([ { rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', + chainId: toHex(111), ticker: 'TEST', id: 'testNetworkConfigurationId', + nickname: undefined, + rpcPrefs: undefined, }, { ...newNetworkConfiguration, @@ -4370,7 +5100,7 @@ describe('NetworkController', () => { url: 'https://test-dapp.com', }, properties: { - chain_id: '0x9999', + chain_id: toHex(222), symbol: 'NEW', source: 'dapp', }, @@ -4378,71 +5108,28 @@ describe('NetworkController', () => { }, ); }); - - it('throws if referrer and source arguments are not passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: '0x9999' as const, - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; - - await expect(() => - /* @ts-expect-error We are intentionally passing bad input. */ - controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), - ).rejects.toThrow( - 'referrer and source are required arguments for adding or updating a network configuration', - ); - }, - ); - }); }); describe('removeNetworkConfigurations', () => { - it('removes a network configuration', async () => { - const networkConfigurationId = 'testNetworkConfigurationId'; + it('remove a network configuration', async () => { + const testNetworkConfigurationId = 'testNetworkConfigurationId'; await withController( { state: { networkConfigurations: { - [networkConfigurationId]: { - id: networkConfigurationId, - rpcUrl: 'https://test-rpc-url', + [testNetworkConfigurationId]: { + rpcUrl: 'https://rpc-url.com', ticker: 'old_rpc_ticker', - nickname: 'old_rpc_chainName', + nickname: 'old_rpc_nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1337), + id: testNetworkConfigurationId, }, }, }, }, async ({ controller }) => { - controller.removeNetworkConfiguration(networkConfigurationId); - + controller.removeNetworkConfiguration(testNetworkConfigurationId); expect( controller.store.getState().networkConfigurations, ).toStrictEqual({}); @@ -4462,7 +5149,7 @@ describe('NetworkController', () => { ticker: 'old_rpc_ticker', nickname: 'old_rpc_nickname', rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: '0x1', + chainId: toHex(1337), id: testNetworkConfigurationId, }, }, @@ -4746,9 +5433,7 @@ function refreshNetworkTests({ }); expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, - }, + EIPS: {}, }); }, ); @@ -6082,7 +6767,10 @@ function lookupNetworkTests({ * @returns The controller messenger. */ function buildMessenger() { - return new ControllerMessenger(); + return new ControllerMessenger< + NetworkControllerAction, + NetworkControllerEvent + >(); } /** @@ -6094,6 +6782,10 @@ function buildMessenger() { function buildNetworkControllerMessenger(messenger = buildMessenger()) { return messenger.getRestricted({ name: 'NetworkController', + allowedActions: [ + 'NetworkController:getProviderConfig', + 'NetworkController:getEthQuery', + ], allowedEvents: [ 'NetworkController:networkDidChange', 'NetworkController:networkWillChange', @@ -6110,7 +6802,10 @@ type WithControllerCallback = ({ controller, }: { controller: NetworkController; - messenger: ControllerMessenger; + messenger: ControllerMessenger< + NetworkControllerAction, + NetworkControllerEvent + >; }) => Promise | ReturnValue; /** @@ -6134,22 +6829,21 @@ type WithControllerArgs = * * @param args - Either a function, or an options bag + a function. The options * bag is equivalent to the options that NetworkController takes (although - * `messenger` is filled in if not given); the function will be called with the - * built controller. + * `messenger` and `infuraProjectId` are filled in if not given); the function + * will be called with the built controller. * @returns Whatever the callback returns. */ async function withController( ...args: WithControllerArgs ): Promise { - const [givenNetworkControllerOptions, fn] = - args.length === 2 ? args : [{}, args[0]]; + const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; const messenger = buildMessenger(); const restrictedMessenger = buildNetworkControllerMessenger(messenger); const controller = new NetworkController({ - infuraProjectId: DEFAULT_INFURA_PROJECT_ID, messenger: restrictedMessenger, trackMetaMetricsEvent: jest.fn(), - ...givenNetworkControllerOptions, + infuraProjectId: 'infura-project-id', + ...rest, }); try { return await fn({ controller, messenger }); @@ -6285,60 +6979,6 @@ async function setFakeProvider( } } -/** - * For each kind of way that the provider can be set, `lookupNetwork` is always - * called. This can cause difficulty when testing the behavior of - * `lookupNetwork` itself, as extra requests then have to be mocked. - * This function takes a function that presumably sets the provider, - * stubbing `lookupNetwork` before the function and releasing the stub - * afterward. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.operation - The function that presumably involves - * `lookupNetwork`. - */ -async function withoutCallingLookupNetwork({ - controller, - operation, -}: { - controller: NetworkController; - operation: () => void | Promise; -}) { - const spy = jest - .spyOn(controller, 'lookupNetwork') - .mockResolvedValue(undefined); - await operation(); - spy.mockRestore(); -} - -/** - * For each kind of way that the provider can be set, `getEIP1559Compatibility` - * is always called. This can cause difficulty when testing the behavior of - * `getEIP1559Compatibility` itself, as extra requests then have to be - * mocked. This function takes a function that presumably sets the provider, - * stubbing `getEIP1559Compatibility` before the function and releasing the stub - * afterward. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.operation - The function that presumably involves - * `getEIP1559Compatibility`. - */ -async function withoutCallingGetEIP1559Compatibility({ - controller, - operation, -}: { - controller: NetworkController; - operation: () => void | Promise; -}) { - const spy = jest - .spyOn(controller, 'getEIP1559Compatibility') - .mockResolvedValue(false); - await operation(); - spy.mockRestore(); -} - /** * Waits for changes to the primary observable store of a controller to occur * before proceeding. May be called with a function, in which case waiting will @@ -6383,7 +7023,7 @@ async function waitForStateChanges({ }, }: { controller: NetworkController; - propertyPath: string[]; + propertyPath?: string[]; count?: number | null; duration?: number; operation?: () => void | Promise; @@ -6463,11 +7103,16 @@ async function waitForStateChanges({ }; eventListener = (newState) => { - const isInteresting = isStateChangeInteresting( - newState, - allStates.length > 0 ? allStates[allStates.length - 1] : initialState, - propertyPath, - ); + const isInteresting = + propertyPath === undefined + ? true + : isStateChangeInteresting( + newState, + allStates.length > 0 + ? allStates[allStates.length - 1] + : initialState, + propertyPath, + ); allStates.push({ ...newState }); @@ -6612,46 +7257,6 @@ async function waitForPublishedEvents({ return await promiseForEventPayloads; } -/** - * `lookupNetwork` is a method in NetworkController which is called internally - * by a few methods. `lookupNetwork` is asynchronous as it makes network - * requests under the hood, but unfortunately, the method is not awaited after - * being called. Hence, if it is called during a test, even if the network - * requests are initiated within the test, they may complete after that test - * ends. This is a problem because it may cause Nock mocks set up in a later - * test to get used up prematurely, causing failures. - * - * To fix this, we need to wait for `lookupNetwork` to fully finish before - * continuing. Since the latest thing that happens in `lookupNetwork` is to - * update EIP-1559 compatibility in state, we can wait for the `networkDetails` - * state to get updated specifically. Unfortunately, we don't know how many - * times this will happen, so this function does incur some time when it's used. - * To speed up tests, you can pass `numberOfNetworkDetailsChanges`. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.numberOfNetworkDetailsChanges - The number of times that - * `networkDetails` is expected to be updated. - * @param args.operation - The function that presumably involves - * `lookupNetwork`. - */ -async function waitForLookupNetworkToComplete({ - controller, - numberOfNetworkDetailsChanges = null, - operation, -}: { - controller: NetworkController; - numberOfNetworkDetailsChanges?: number | null; - operation: () => void | Promise; -}) { - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation, - count: numberOfNetworkDetailsChanges, - }); -} - /** * Returns whether two places in different state objects have different values. * diff --git a/app/scripts/controllers/network/network-controller.ts b/app/scripts/controllers/network/network-controller.ts index 2f4fd9763..a4417a8e2 100644 --- a/app/scripts/controllers/network/network-controller.ts +++ b/app/scripts/controllers/network/network-controller.ts @@ -130,12 +130,26 @@ export type NetworkControllerEvent = | NetworkControllerInfuraIsBlockedEvent | NetworkControllerInfuraIsUnblockedEvent; +export type NetworkControllerGetProviderConfigAction = { + type: `NetworkController:getProviderConfig`; + handler: () => ProviderConfiguration; +}; + +export type NetworkControllerGetEthQueryAction = { + type: `NetworkController:getEthQuery`; + handler: () => EthQuery | undefined; +}; + +export type NetworkControllerAction = + | NetworkControllerGetProviderConfigAction + | NetworkControllerGetEthQueryAction; + /** * The messenger that the NetworkController uses to publish events. */ export type NetworkControllerMessenger = RestrictedControllerMessenger< typeof name, - never, + NetworkControllerAction, NetworkControllerEvent, string, string @@ -174,6 +188,10 @@ export type ProviderConfiguration = { rpcPrefs?: { blockExplorerUrl?: string; }; + /** + * The ID of the network configuration used to build this provider config. + */ + id?: NetworkConfigurationId; }; /** @@ -350,9 +368,7 @@ function buildDefaultNetworkStatusState(): NetworkStatus { */ function buildDefaultNetworkDetailsState(): NetworkDetails { return { - EIPS: { - 1559: undefined, - }, + EIPS: {}, }; } @@ -434,6 +450,8 @@ export class NetworkController extends EventEmitter { #blockTrackerProxy: SwappableProxy | null; + #ethQuery: EthQuery | undefined; + #infuraProjectId: NetworkControllerOptions['infuraProjectId']; #trackMetaMetricsEvent: NetworkControllerOptions['trackMetaMetricsEvent']; @@ -477,6 +495,13 @@ export class NetworkController extends EventEmitter { } this.#infuraProjectId = infuraProjectId; this.#trackMetaMetricsEvent = trackMetaMetricsEvent; + + this.#messenger.registerActionHandler(`${name}:getProviderConfig`, () => { + return this.store.getState().providerConfig; + }); + this.#messenger.registerActionHandler(`${name}:getEthQuery`, () => { + return this.#ethQuery; + }); } /** @@ -560,7 +585,7 @@ export class NetworkController extends EventEmitter { * blocking requests, or if the network is not Infura-supported. */ async lookupNetwork(): Promise { - const { chainId, type } = this.store.getState().providerConfig; + const { type } = this.store.getState().providerConfig; const { provider } = this.getProviderAndBlockTracker(); let networkChanged = false; let networkId: NetworkIdState = null; @@ -574,16 +599,6 @@ export class NetworkController extends EventEmitter { return; } - if (!chainId) { - log.warn( - 'NetworkController - lookupNetwork aborted due to missing chainId', - ); - this.#resetNetworkId(); - this.#resetNetworkStatus(); - this.#resetNetworkDetails(); - return; - } - const isInfura = isInfuraProviderType(type); const listener = () => { @@ -727,6 +742,7 @@ export class NetworkController extends EventEmitter { ticker: 'ticker' in network ? network.ticker : 'ETH', nickname: undefined, rpcPrefs: { blockExplorerUrl: network.blockExplorerUrl }, + id: undefined, }); } @@ -864,11 +880,12 @@ export class NetworkController extends EventEmitter { * the new network. */ async #switchNetwork(providerConfig: ProviderConfiguration) { + const { type, rpcUrl, chainId } = providerConfig; this.#messenger.publish('NetworkController:networkWillChange'); this.#resetNetworkId(); this.#resetNetworkStatus(); this.#resetNetworkDetails(); - this.#configureProvider(providerConfig); + this.#configureProvider({ type, rpcUrl, chainId }); this.#messenger.publish('NetworkController:networkDidChange'); await this.lookupNetwork(); } @@ -878,8 +895,7 @@ export class NetworkController extends EventEmitter { * block tracker) to talk to a network. * * @param args - The arguments. - * @param args.type - The shortname of an Infura-supported network (see - * {@link NETWORK_TYPES}). + * @param args.type - The provider type. * @param args.rpcUrl - The URL of the RPC endpoint that represents the * network. Only used for non-Infura networks. * @param args.chainId - The chain ID of the network (as per EIP-155). Only @@ -887,21 +903,43 @@ export class NetworkController extends EventEmitter { * any Infura-supported network). * @throws if the `type` if not a known Infura-supported network. */ - #configureProvider({ type, rpcUrl, chainId }: ProviderConfiguration): void { + #configureProvider({ + type, + rpcUrl, + chainId, + }: { + type: ProviderType; + rpcUrl: string | undefined; + chainId: Hex | undefined; + }): void { const isInfura = isInfuraProviderType(type); if (isInfura) { - // infura type-based endpoints this.#configureInfuraProvider({ type, infuraProjectId: this.#infuraProjectId, }); - } else if (type === NETWORK_TYPES.RPC && rpcUrl) { - // url-based rpc endpoints + } else if (type === NETWORK_TYPES.RPC) { + if (chainId === undefined) { + throw new Error('chainId must be provided for custom RPC endpoints'); + } + if (rpcUrl === undefined) { + throw new Error('rpcUrl must be provided for custom RPC endpoints'); + } this.#configureStandardProvider(rpcUrl, chainId); } else { - throw new Error( - `NetworkController - #configureProvider - unknown type "${type}"`, - ); + throw new Error(`Unrecognized network type: '${type}'`); + } + } + + /** + * Creates a new instance of EthQuery that wraps the current provider and + * saves it for future usage. + */ + #registerProvider() { + const { provider } = this.getProviderAndBlockTracker(); + + if (provider) { + this.#ethQuery = new EthQuery(provider); } } @@ -928,7 +966,7 @@ export class NetworkController extends EventEmitter { infuraProjectId, type: NetworkClientType.Infura, }); - this.#setProviderAndBlockTracker({ provider, blockTracker }); + this.#updateProvider(provider, blockTracker); } /** @@ -945,7 +983,27 @@ export class NetworkController extends EventEmitter { rpcUrl, type: NetworkClientType.Custom, }); - this.#setProviderAndBlockTracker({ provider, blockTracker }); + this.#updateProvider(provider, blockTracker); + } + + /** + * Given a provider and a block tracker, updates any proxies pointing to + * these objects that have been previously set, or initializes any proxies + * that have not been previously set, then creates an instance of EthQuery + * that wraps the provider. + * + * @param provider - The provider. + * @param blockTracker - The block tracker. + */ + #updateProvider( + provider: SafeEventEmitterProvider, + blockTracker: PollingBlockTracker, + ) { + this.#setProviderAndBlockTracker({ + provider, + blockTracker, + }); + this.#registerProvider(); } /** diff --git a/app/scripts/controllers/permissions/snaps/snap-permissions.js b/app/scripts/controllers/permissions/snaps/snap-permissions.js index 92d4bc52a..cfada9365 100644 --- a/app/scripts/controllers/permissions/snaps/snap-permissions.js +++ b/app/scripts/controllers/permissions/snaps/snap-permissions.js @@ -8,15 +8,17 @@ import { ExcludedSnapPermissions, } from '../../../../../shared/constants/permissions'; +// TODO: Use the exported versions of these functions from the snaps monorepo after stable release. + /** * @returns {Record>} All endowment permission * specifications. */ export const buildSnapEndowmentSpecifications = () => Object.values(endowmentPermissionBuilders).reduce( - (allSpecifications, { targetKey, specificationBuilder }) => { - if (!Object.keys(ExcludedSnapEndowments).includes(targetKey)) { - allSpecifications[targetKey] = specificationBuilder(); + (allSpecifications, { targetName, specificationBuilder }) => { + if (!Object.keys(ExcludedSnapEndowments).includes(targetName)) { + allSpecifications[targetName] = specificationBuilder(); } return allSpecifications; }, @@ -29,9 +31,9 @@ export const buildSnapEndowmentSpecifications = () => */ export const buildSnapRestrictedMethodSpecifications = (hooks) => Object.values(restrictedMethodPermissionBuilders).reduce( - (specifications, { targetKey, specificationBuilder, methodHooks }) => { - if (!Object.keys(ExcludedSnapPermissions).includes(targetKey)) { - specifications[targetKey] = specificationBuilder({ + (specifications, { targetName, specificationBuilder, methodHooks }) => { + if (!Object.keys(ExcludedSnapPermissions).includes(targetName)) { + specifications[targetName] = specificationBuilder({ methodHooks: selectHooks(hooks, methodHooks), }); } diff --git a/app/scripts/controllers/permissions/snaps/snap-permissions.test.js b/app/scripts/controllers/permissions/snaps/snap-permissions.test.js index 137f72f0b..334e12df5 100644 --- a/app/scripts/controllers/permissions/snaps/snap-permissions.test.js +++ b/app/scripts/controllers/permissions/snaps/snap-permissions.test.js @@ -8,6 +8,14 @@ import { buildSnapRestrictedMethodSpecifications, } from './snap-permissions'; +// Temporarily replace the snaps packages with the Flask versions. +jest.mock('@metamask/snaps-controllers', () => + jest.requireActual('@metamask/snaps-controllers-flask'), +); +jest.mock('@metamask/rpc-methods', () => + jest.requireActual('@metamask/rpc-methods-flask'), +); + describe('buildSnapRestrictedMethodSpecifications', () => { it('creates valid permission specification objects', () => { const hooks = { @@ -29,7 +37,7 @@ describe('buildSnapRestrictedMethodSpecifications', () => { Object.values(specifications).forEach((specification) => { expect(specification).toMatchObject({ - targetKey: expect.stringMatching(/^(snap_|wallet_)/u), + targetName: expect.stringMatching(/^(snap_|wallet_)/u), methodImplementation: expect.any(Function), }); }); @@ -43,8 +51,8 @@ describe('buildSnapEndowmentSpecifications', () => { ).toStrictEqual( Object.keys(EndowmentPermissions) .filter( - (targetKey) => - !Object.keys(ExcludedSnapEndowments).includes(targetKey), + (targetName) => + !Object.keys(ExcludedSnapEndowments).includes(targetName), ) .sort(), ); diff --git a/app/scripts/controllers/permissions/specifications.js b/app/scripts/controllers/permissions/specifications.js index b6f23d4be..42f685bfb 100644 --- a/app/scripts/controllers/permissions/specifications.js +++ b/app/scripts/controllers/permissions/specifications.js @@ -21,7 +21,7 @@ import { * The "keys" of all of permissions recognized by the PermissionController. * Permission keys and names have distinct meanings in the permission system. */ -const PermissionKeys = Object.freeze({ +const PermissionNames = Object.freeze({ ...RestrictedMethods, }); @@ -61,9 +61,7 @@ export const getCaveatSpecifications = ({ getIdentities }) => { decorator: (method, caveat) => { return async (args) => { const result = await method(args); - return result - .filter((account) => caveat.value.includes(account)) - .slice(0, 1); + return result.filter((account) => caveat.value.includes(account)); }; }, @@ -101,22 +99,22 @@ export const getPermissionSpecifications = ({ captureKeyringTypesWithMissingIdentities, }) => { return { - [PermissionKeys.eth_accounts]: { + [PermissionNames.eth_accounts]: { permissionType: PermissionType.RestrictedMethod, - targetKey: PermissionKeys.eth_accounts, + targetName: PermissionNames.eth_accounts, allowedCaveats: [CaveatTypes.restrictReturnedAccounts], factory: (permissionOptions, requestData) => { if (Array.isArray(permissionOptions.caveats)) { throw new Error( - `${PermissionKeys.eth_accounts} error: Received unexpected caveats. Any permitted caveats will be added automatically.`, + `${PermissionNames.eth_accounts} error: Received unexpected caveats. Any permitted caveats will be added automatically.`, ); } // This value will be further validated as part of the caveat. if (!requestData.approvedAccounts) { throw new Error( - `${PermissionKeys.eth_accounts} error: No approved accounts specified.`, + `${PermissionNames.eth_accounts} error: No approved accounts specified.`, ); } @@ -169,7 +167,7 @@ export const getPermissionSpecifications = ({ caveats[0].type !== CaveatTypes.restrictReturnedAccounts ) { throw new Error( - `${PermissionKeys.eth_accounts} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictReturnedAccounts}".`, + `${PermissionNames.eth_accounts} error: Invalid caveats. There must be a single caveat of type "${CaveatTypes.restrictReturnedAccounts}".`, ); } }, @@ -189,7 +187,7 @@ export const getPermissionSpecifications = ({ function validateCaveatAccounts(accounts, getIdentities) { if (!Array.isArray(accounts) || accounts.length === 0) { throw new Error( - `${PermissionKeys.eth_accounts} error: Expected non-empty array of Ethereum addresses.`, + `${PermissionNames.eth_accounts} error: Expected non-empty array of Ethereum addresses.`, ); } @@ -197,13 +195,13 @@ function validateCaveatAccounts(accounts, getIdentities) { accounts.forEach((address) => { if (!address || typeof address !== 'string') { throw new Error( - `${PermissionKeys.eth_accounts} error: Expected an array of Ethereum addresses. Received: "${address}".`, + `${PermissionNames.eth_accounts} error: Expected an array of Ethereum addresses. Received: "${address}".`, ); } if (!identities[address]) { throw new Error( - `${PermissionKeys.eth_accounts} error: Received unrecognized address: "${address}".`, + `${PermissionNames.eth_accounts} error: Received unrecognized address: "${address}".`, ); } }); diff --git a/app/scripts/controllers/permissions/specifications.test.js b/app/scripts/controllers/permissions/specifications.test.js index 0f3fb7a73..f716ddaba 100644 --- a/app/scripts/controllers/permissions/specifications.test.js +++ b/app/scripts/controllers/permissions/specifications.test.js @@ -46,7 +46,7 @@ describe('PermissionController specifications', () => { describe('restrictReturnedAccounts', () => { describe('decorator', () => { - it('returns the first array member included in the caveat value', async () => { + it('only returns array members included in the caveat value', async () => { const getIdentities = jest.fn(); const { decorator } = getCaveatSpecifications({ getIdentities })[ CaveatTypes.restrictReturnedAccounts @@ -55,10 +55,10 @@ describe('PermissionController specifications', () => { const method = async () => ['0x1', '0x2', '0x3']; const caveat = { type: CaveatTypes.restrictReturnedAccounts, - value: ['0x1', '0x2'], + value: ['0x1', '0x3'], }; const decorated = decorator(method, caveat); - expect(await decorated()).toStrictEqual(['0x1']); + expect(await decorated()).toStrictEqual(['0x1', '0x3']); }); it('returns an empty array if no array members are included in the caveat value', async () => { @@ -144,7 +144,7 @@ describe('PermissionController specifications', () => { const permissionSpecifications = getPermissionSpecifications({}); expect(Object.keys(permissionSpecifications)).toHaveLength(1); expect( - permissionSpecifications[RestrictedMethods.eth_accounts].targetKey, + permissionSpecifications[RestrictedMethods.eth_accounts].targetName, ).toStrictEqual(RestrictedMethods.eth_accounts); }); diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index ccd2d89eb..e2bf8025b 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -72,10 +72,6 @@ export default class PreferencesController { ...opts.initState, }; - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - initState.useTokenDetection = Boolean(process.env.TOKEN_DETECTION_V2); - ///: END:ONLY_INCLUDE_IN - this.network = opts.network; this._onInfuraIsBlocked = opts.onInfuraIsBlocked; this._onInfuraIsUnblocked = opts.onInfuraIsUnblocked; @@ -84,7 +80,7 @@ export default class PreferencesController { this.tokenListController = opts.tokenListController; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.handleMmiPortfolio = opts.handleMmiPortfolio; + this.handleMmiDashboardData = opts.handleMmiDashboardData; if (!process.env.IN_TEST) { this.mmiConfigurationStore = opts.mmiConfigurationStore.getState(); @@ -538,7 +534,7 @@ export default class PreferencesController { async prepareMmiPortfolio() { if (!process.env.IN_TEST) { try { - const mmiDashboardData = await this.handleMmiPortfolio(); + const mmiDashboardData = await this.handleMmiDashboardData(); const cookieSetUrls = this.mmiConfigurationStore.mmiConfiguration?.portfolio?.cookieSetUrls; setDashboardCookie(mmiDashboardData, cookieSetUrls); diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index b44e66a21..505b696d7 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -104,23 +104,34 @@ const initialState = { }; export default class SwapsController { - constructor({ - getBufferedGasLimit, - networkController, - provider, - getProviderConfig, - getTokenRatesState, - fetchTradesInfo = defaultFetchTradesInfo, - getCurrentChainId, - getEIP1559GasFeeEstimates, - onNetworkStateChange, - }) { + constructor( + { + getBufferedGasLimit, + networkController, + provider, + getProviderConfig, + getTokenRatesState, + fetchTradesInfo = defaultFetchTradesInfo, + getCurrentChainId, + getEIP1559GasFeeEstimates, + onNetworkStateChange, + }, + state, + ) { this.store = new ObservableStore({ - swapsState: { ...initialState.swapsState }, + swapsState: { + ...initialState.swapsState, + swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags || {}, + }, }); this.resetState = () => { - this.store.updateState({ swapsState: { ...initialState.swapsState } }); + this.store.updateState({ + swapsState: { + ...initialState.swapsState, + swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags, + }, + }); }; this._fetchTradesInfo = fetchTradesInfo; @@ -662,6 +673,7 @@ export default class SwapsController { swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime, swapsQuotePrefetchingRefreshTime: swapsState.swapsQuotePrefetchingRefreshTime, + swapsFeatureFlags: swapsState.swapsFeatureFlags, }, }); clearTimeout(this.pollingTimeout); diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index cd962e566..93d3ab382 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -2,7 +2,7 @@ import EventEmitter from '@metamask/safe-event-emitter'; import { ObservableStore } from '@metamask/obs-store'; import { bufferToHex, keccak, toBuffer, isHexString } from 'ethereumjs-util'; import EthQuery from 'ethjs-query'; -import { ethErrors } from 'eth-rpc-errors'; +import { errorCodes, ethErrors } from 'eth-rpc-errors'; import { Common, Hardfork } from '@ethereumjs/common'; import { TransactionFactory } from '@ethereumjs/tx'; import { ApprovalType } from '@metamask/controller-utils'; @@ -202,7 +202,7 @@ export default class TransactionController extends EventEmitter { const approved = this.txStateManager.getApprovedTransactions(); return [...pending, ...approved]; }, - approveTransaction: this.approveTransaction.bind(this), + approveTransaction: this._approveTransaction.bind(this), getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }); @@ -277,7 +277,11 @@ export default class TransactionController extends EventEmitter { // type will be one of our default network names or 'rpc'. the default // network names are sufficient configuration, simply pass the name as the // chain argument in the constructor. - if (type !== NETWORK_TYPES.RPC && type !== NETWORK_TYPES.SEPOLIA) { + if ( + type !== NETWORK_TYPES.RPC && + type !== NETWORK_TYPES.SEPOLIA && + type !== NETWORK_TYPES.LINEA_GOERLI + ) { return new Common({ chain: type, hardfork, @@ -347,7 +351,7 @@ export default class TransactionController extends EventEmitter { `MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`, ); - const initialTxMeta = await this.addUnapprovedTransaction( + const { txMeta: initialTxMeta, isExisting } = await this._createTransaction( opts.method, txParams, opts.origin, @@ -356,58 +360,59 @@ export default class TransactionController extends EventEmitter { opts.id, ); - // listen for tx completion (success, fail) - return new Promise((resolve, reject) => { - this.txStateManager.once( - `${initialTxMeta.id}:finished`, - (finishedTxMeta) => { - switch (finishedTxMeta.status) { - case TransactionStatus.submitted: - return resolve(finishedTxMeta.hash); - case TransactionStatus.rejected: - return reject( - cleanErrorStack( - ethErrors.provider.userRejectedRequest( - 'MetaMask Tx Signature: User denied transaction signature.', - ), - ), - ); - case TransactionStatus.failed: - return reject( - cleanErrorStack( - ethErrors.rpc.internal(finishedTxMeta.err.message), - ), - ); - default: - return reject( - cleanErrorStack( - ethErrors.rpc.internal( - `MetaMask Tx Signature: Unknown problem: ${JSON.stringify( - finishedTxMeta.txParams, - )}`, - ), - ), - ); - } - }, - ); - }); + const txId = initialTxMeta.id; + const isCompleted = this._isTransactionCompleted(initialTxMeta); + + const finishedPromise = isCompleted + ? Promise.resolve(initialTxMeta) + : this._waitForTransactionFinished(txId); + + if (!isExisting && !isCompleted) { + try { + await this._requestTransactionApproval(initialTxMeta); + } catch (error) { + // Errors generated from final status using finished event + } + } + + const finalTxMeta = await finishedPromise; + const finalStatus = finalTxMeta?.status; + + switch (finalStatus) { + case TransactionStatus.submitted: + return finalTxMeta.hash; + case TransactionStatus.rejected: + throw cleanErrorStack( + ethErrors.provider.userRejectedRequest( + 'MetaMask Tx Signature: User denied transaction signature.', + ), + ); + case TransactionStatus.failed: + throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message)); + default: + throw cleanErrorStack( + ethErrors.rpc.internal( + `MetaMask Tx Signature: Unknown problem: ${JSON.stringify( + finalTxMeta?.txParams, + )}`, + ), + ); + } } /** * Creates approvals for all unapproved transactions in the txStateManager. - * - * @returns {Promise} */ - async initApprovals() { + initApprovals() { const unapprovedTxs = this.txStateManager.getUnapprovedTxList(); - return Promise.all( - Object.values(unapprovedTxs).map((txMeta) => - this._requestApproval(txMeta, { - shouldShowRequest: false, - }), - ), - ); + + Object.values(unapprovedTxs).forEach((txMeta) => { + this._requestTransactionApproval(txMeta, { + shouldShowRequest: false, + }).catch((error) => { + log.error('Error during persisted transaction approval', error); + }); + }); } // ==================================================================================================================================================== @@ -606,81 +611,6 @@ export default class TransactionController extends EventEmitter { return this._getTransaction(txId); } - /** - * updates a swap approval transaction with provided metadata and source token symbol - * if the transaction state is unapproved. - * - * @param {string} txId - * @param {object} swapApprovalTransaction - holds the metadata and token symbol - * @param {string} swapApprovalTransaction.type - * @param {string} swapApprovalTransaction.sourceTokenSymbol - * @returns {TransactionMeta} the txMeta of the updated transaction - */ - updateSwapApprovalTransaction(txId, { type, sourceTokenSymbol }) { - this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapApprovalTransaction'); - - let swapApprovalTransaction = { type, sourceTokenSymbol }; - // only update what is defined - swapApprovalTransaction = pickBy(swapApprovalTransaction); - - const note = `Update Swap Approval Transaction for ${txId}`; - this._updateTransaction(txId, swapApprovalTransaction, note); - return this._getTransaction(txId); - } - - /** - * updates a swap transaction with provided metadata and source token symbol - * if the transaction state is unapproved. - * - * @param {string} txId - * @param {object} swapTransaction - holds the metadata - * @param {string} swapTransaction.sourceTokenSymbol - * @param {string} swapTransaction.destinationTokenSymbol - * @param {string} swapTransaction.type - * @param {string} swapTransaction.destinationTokenDecimals - * @param {string} swapTransaction.destinationTokenAddress - * @param {string} swapTransaction.swapMetaData - * @param {string} swapTransaction.swapTokenValue - * @param {string} swapTransaction.estimatedBaseFee - * @param {string} swapTransaction.approvalTxId - * @returns {TransactionMeta} the txMeta of the updated transaction - */ - updateSwapTransaction( - txId, - { - sourceTokenSymbol, - destinationTokenSymbol, - type, - destinationTokenDecimals, - destinationTokenAddress, - swapMetaData, - swapTokenValue, - estimatedBaseFee, - approvalTxId, - }, - ) { - this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapTransaction'); - - let swapTransaction = { - sourceTokenSymbol, - destinationTokenSymbol, - type, - destinationTokenDecimals, - destinationTokenAddress, - swapMetaData, - swapTokenValue, - estimatedBaseFee, - approvalTxId, - }; - - // only update what is defined - swapTransaction = pickBy(swapTransaction); - - const note = `Update Swap Transaction for ${txId}`; - this._updateTransaction(txId, swapTransaction, note); - return this._getTransaction(txId); - } - /** * updates a transaction's user settings only if the transaction state is unapproved * @@ -789,7 +719,7 @@ export default class TransactionController extends EventEmitter { * @param transactionType * @param sendFlowHistory * @param actionId - * @returns {txMeta} + * @param options */ async addUnapprovedTransaction( txMethodType, @@ -798,98 +728,30 @@ export default class TransactionController extends EventEmitter { transactionType, sendFlowHistory = [], actionId, + options, ) { - if ( - transactionType !== undefined && - !VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType) - ) { - throw new Error( - `TransactionController - invalid transactionType value: ${transactionType}`, - ); - } - - // If a transaction is found with the same actionId, do not create a new speed-up transaction. - if (actionId) { - let existingTxMeta = - this.txStateManager.getTransactionWithActionId(actionId); - if (existingTxMeta) { - this.emit('newUnapprovedTx', existingTxMeta); - existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta); - this._requestApproval(existingTxMeta); - return existingTxMeta; - } - } - - // validate - const normalizedTxParams = txUtils.normalizeTxParams(txParams); - const eip1559Compatibility = await this.getEIP1559Compatibility(); - - txUtils.validateTxParams(normalizedTxParams, eip1559Compatibility); - - /** - * `generateTxMeta` adds the default txMeta properties to the passed object. - * These include the tx's `id`. As we use the id for determining order of - * txes in the tx-state-manager, it is necessary to call the asynchronous - * method `determineTransactionType` after `generateTxMeta`. - */ - let txMeta = this.txStateManager.generateTxMeta({ - txParams: normalizedTxParams, + const { txMeta, isExisting } = await this._createTransaction( + txMethodType, + txParams, origin, + transactionType, sendFlowHistory, - }); - - // Add actionId to txMeta to check if same actionId is seen again - // IF request to create transaction with same actionId is submitted again, new transaction will not be added for it. - if (actionId) { - txMeta.actionId = actionId; - } - - if (origin === ORIGIN_METAMASK) { - // Assert the from address is the selected address - if (normalizedTxParams.from !== this.getSelectedAddress()) { - throw ethErrors.rpc.internal({ - message: `Internally initiated transaction is using invalid account.`, - data: { - origin, - fromAddress: normalizedTxParams.from, - selectedAddress: this.getSelectedAddress(), - }, - }); - } - } else { - // Assert that the origin has permissions to initiate transactions from - // the specified address - const permittedAddresses = await this.getPermittedAccounts(origin); - if (!permittedAddresses.includes(normalizedTxParams.from)) { - throw ethErrors.provider.unauthorized({ data: { origin } }); - } - } - - const { type } = await determineTransactionType( - normalizedTxParams, - this.query, + actionId, + options, ); - txMeta.type = transactionType || type; + if (isExisting) { + const isCompleted = this._isTransactionCompleted(txMeta); - // ensure value - txMeta.txParams.value = txMeta.txParams.value - ? addHexPrefix(txMeta.txParams.value) - : '0x0'; - - if (txMethodType && this.securityProviderRequest) { - const securityProviderResponse = await this.securityProviderRequest( - txMeta, - txMethodType, - ); - - txMeta.securityProviderResponse = securityProviderResponse; + return isCompleted + ? txMeta + : await this._waitForTransactionFinished(txMeta.id); } - this.addTransaction(txMeta); - this.emit('newUnapprovedTx', txMeta); - - txMeta = await this.addTransactionGasDefaults(txMeta); - this._requestApproval(txMeta); + if (options?.requireApproval === false) { + await this._updateAndApproveTransaction(txMeta, actionId); + } else { + await this._requestTransactionApproval(txMeta, { actionId }); + } return txMeta; } @@ -1260,7 +1122,7 @@ export default class TransactionController extends EventEmitter { } this.addTransaction(newTxMeta); - await this.approveTransaction(newTxMeta.id, actionId, { + await this._approveTransaction(newTxMeta.id, actionId, { hasApprovalRequest: false, }); return newTxMeta; @@ -1320,9 +1182,7 @@ export default class TransactionController extends EventEmitter { } this.addTransaction(newTxMeta); - await this.approveTransaction(newTxMeta.id, actionId, { - hasApprovalRequest: false, - }); + await this._approveTransaction(newTxMeta.id, actionId); return newTxMeta; } @@ -1338,113 +1198,6 @@ export default class TransactionController extends EventEmitter { ); } - /** - * updates and approves the transaction - * - * @param {object} txMeta - * @param {string} actionId - */ - async updateAndApproveTransaction(txMeta, actionId) { - this.txStateManager.updateTransaction( - txMeta, - 'confTx: user approved transaction', - ); - await this.approveTransaction(txMeta.id, actionId); - } - - /** - * sets the tx status to approved - * auto fills the nonce - * signs the transaction - * publishes the transaction - * if any of these steps fails the tx status will be set to failed - * - * @param {number} txId - the tx's Id - * @param {string} actionId - actionId passed from UI - * @param opts - options object - * @param opts.hasApprovalRequest - whether the transaction has an approval request - */ - async approveTransaction(txId, actionId, { hasApprovalRequest = true } = {}) { - // TODO: Move this safety out of this function. - // Since this transaction is async, - // we need to keep track of what is currently being signed, - // So that we do not increment nonce + resubmit something - // that is already being incremented & signed. - const txMeta = this.txStateManager.getTransaction(txId); - - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - // MMI does not broadcast transactions, as that is the responsibility of the custodian - if (txMeta.custodyStatus) { - this.inProcessOfSigning.delete(txId); - await this.signTransaction(txId); - return; - } - ///: END:ONLY_INCLUDE_IN - - if (this.inProcessOfSigning.has(txId)) { - return; - } - this.inProcessOfSigning.add(txId); - let nonceLock; - try { - // approve - this.txStateManager.setTxStatusApproved(txId); - if (hasApprovalRequest) { - this._acceptApproval(txMeta); - } - // get next nonce - const fromAddress = txMeta.txParams.from; - // wait for a nonce - let { customNonceValue } = txMeta; - customNonceValue = Number(customNonceValue); - nonceLock = await this.nonceTracker.getNonceLock(fromAddress); - // add nonce to txParams - // if txMeta has previousGasParams then it is a retry at same nonce with - // higher gas settings and therefor the nonce should not be recalculated - const nonce = txMeta.previousGasParams - ? txMeta.txParams.nonce - : nonceLock.nextNonce; - const customOrNonce = - customNonceValue === 0 ? customNonceValue : customNonceValue || nonce; - - txMeta.txParams.nonce = addHexPrefix(customOrNonce.toString(16)); - // add nonce debugging information to txMeta - txMeta.nonceDetails = nonceLock.nonceDetails; - if (customNonceValue) { - txMeta.nonceDetails.customNonceValue = customNonceValue; - } - this.txStateManager.updateTransaction( - txMeta, - 'transactions#approveTransaction', - ); - // sign transaction - const rawTx = await this.signTransaction(txId); - await this.publishTransaction(txId, rawTx, actionId); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.approved, - actionId, - ); - // must set transaction to submitted/failed before releasing lock - nonceLock.releaseLock(); - } catch (err) { - // this is try-catch wrapped so that we can guarantee that the nonceLock is released - try { - this._failTransaction(txId, err, actionId); - } catch (err2) { - log.error(err2); - } - // must set transaction to submitted/failed before releasing lock - if (nonceLock) { - nonceLock.releaseLock(); - } - // continue with error chain - throw err; - } finally { - this.inProcessOfSigning.delete(txId); - } - } - async approveTransactionsWithSameNonce(listOfTxParams = []) { if (listOfTxParams.length === 0) { return ''; @@ -1779,24 +1532,6 @@ export default class TransactionController extends EventEmitter { } } - /** - * Convenience method for the ui thats sets the transaction to rejected - * - * @param {number} txId - the tx's Id - * @param {string} actionId - actionId passed from UI - * @returns {Promise} - */ - async cancelTransaction(txId, actionId) { - const txMeta = this.txStateManager.getTransaction(txId); - this.txStateManager.setTxStatusRejected(txId); - this._rejectApproval(txMeta); - this._trackTransactionMetricsEvent( - txMeta, - TransactionMetaMetricsEvent.rejected, - actionId, - ); - } - /** * Sets the txHas on the txMeta * @@ -1835,6 +1570,368 @@ export default class TransactionController extends EventEmitter { // // PRIVATE METHODS // + + _isTransactionCompleted(txMeta) { + return [ + TransactionStatus.submitted, + TransactionStatus.rejected, + TransactionStatus.failed, + TransactionStatus.dropped, + TransactionStatus.confirmed, + ].includes(txMeta.status); + } + + async _waitForTransactionFinished(txId) { + return new Promise((resolve) => { + this.txStateManager.once(`${txId}:finished`, (txMeta) => { + resolve(txMeta); + }); + }); + } + + async _createTransaction( + txMethodType, + txParams, + origin, + transactionType, + sendFlowHistory = [], + actionId, + options, + ) { + if ( + transactionType !== undefined && + !VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType) + ) { + throw new Error( + `TransactionController - invalid transactionType value: ${transactionType}`, + ); + } + + // If a transaction is found with the same actionId, do not create a new speed-up transaction. + if (actionId) { + let existingTxMeta = + this.txStateManager.getTransactionWithActionId(actionId); + if (existingTxMeta) { + existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta); + return { txMeta: existingTxMeta, isExisting: true }; + } + } + + // validate + const normalizedTxParams = txUtils.normalizeTxParams(txParams); + const eip1559Compatibility = await this.getEIP1559Compatibility(); + + txUtils.validateTxParams(normalizedTxParams, eip1559Compatibility); + + /** + * `generateTxMeta` adds the default txMeta properties to the passed object. + * These include the tx's `id`. As we use the id for determining order of + * txes in the tx-state-manager, it is necessary to call the asynchronous + * method `determineTransactionType` after `generateTxMeta`. + */ + let txMeta = this.txStateManager.generateTxMeta({ + txParams: normalizedTxParams, + origin, + sendFlowHistory, + }); + + // Add actionId to txMeta to check if same actionId is seen again + // IF request to create transaction with same actionId is submitted again, new transaction will not be added for it. + if (actionId) { + txMeta.actionId = actionId; + } + + if (origin === ORIGIN_METAMASK) { + // Assert the from address is the selected address + if (normalizedTxParams.from !== this.getSelectedAddress()) { + throw ethErrors.rpc.internal({ + message: `Internally initiated transaction is using invalid account.`, + data: { + origin, + fromAddress: normalizedTxParams.from, + selectedAddress: this.getSelectedAddress(), + }, + }); + } + } else { + // Assert that the origin has permissions to initiate transactions from + // the specified address + const permittedAddresses = await this.getPermittedAccounts(origin); + if (!permittedAddresses.includes(normalizedTxParams.from)) { + throw ethErrors.provider.unauthorized({ data: { origin } }); + } + } + + const { type } = await determineTransactionType( + normalizedTxParams, + this.query, + ); + txMeta.type = transactionType || type; + + // ensure value + txMeta.txParams.value = txMeta.txParams.value + ? addHexPrefix(txMeta.txParams.value) + : '0x0'; + + if (txMethodType && this.securityProviderRequest) { + const securityProviderResponse = await this.securityProviderRequest( + txMeta, + txMethodType, + ); + + txMeta.securityProviderResponse = securityProviderResponse; + } + + this.addTransaction(txMeta); + + txMeta = await this.addTransactionGasDefaults(txMeta); + + if ( + [TransactionType.swap, TransactionType.swapApproval].includes( + transactionType, + ) + ) { + txMeta = await this._createSwapsTransaction( + options?.swaps, + transactionType, + txMeta, + ); + } + + return { txMeta, isExisting: false }; + } + + async _createSwapsTransaction(swapOptions, transactionType, txMeta) { + // The simulationFails property is added if the estimateGas call fails. In cases + // when no swaps approval tx is required, this indicates that the swap will likely + // fail. There was an earlier estimateGas call made by the swaps controller, + // but it is possible that external conditions have change since then, and + // a previously succeeding estimate gas call could now fail. By checking for + // the `simulationFails` property here, we can reduce the number of swap + // transactions that get published to the blockchain only to fail and thereby + // waste the user's funds on gas. + if ( + transactionType === TransactionType.swap && + swapOptions?.hasApproveTx === false && + txMeta.simulationFails + ) { + await this._cancelTransaction(txMeta.id); + throw new Error('Simulation failed'); + } + + const swapsMeta = swapOptions?.meta; + + if (!swapsMeta) { + return txMeta; + } + + if (transactionType === TransactionType.swapApproval) { + this.emit('newSwapApproval', txMeta); + return this._updateSwapApprovalTransaction(txMeta.id, swapsMeta); + } + + if (transactionType === TransactionType.swap) { + this.emit('newSwap', txMeta); + return this._updateSwapTransaction(txMeta.id, swapsMeta); + } + + return txMeta; + } + + /** + * updates a swap approval transaction with provided metadata and source token symbol + * if the transaction state is unapproved. + * + * @param {string} txId + * @param {object} swapApprovalTransaction - holds the metadata and token symbol + * @param {string} swapApprovalTransaction.type + * @param {string} swapApprovalTransaction.sourceTokenSymbol + * @returns {TransactionMeta} the txMeta of the updated transaction + */ + _updateSwapApprovalTransaction(txId, { type, sourceTokenSymbol }) { + this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapApprovalTransaction'); + + let swapApprovalTransaction = { type, sourceTokenSymbol }; + // only update what is defined + swapApprovalTransaction = pickBy(swapApprovalTransaction); + + const note = `Update Swap Approval Transaction for ${txId}`; + this._updateTransaction(txId, swapApprovalTransaction, note); + return this._getTransaction(txId); + } + + /** + * updates a swap transaction with provided metadata and source token symbol + * if the transaction state is unapproved. + * + * @param {string} txId + * @param {object} swapTransaction - holds the metadata + * @param {string} swapTransaction.sourceTokenSymbol + * @param {string} swapTransaction.destinationTokenSymbol + * @param {string} swapTransaction.type + * @param {string} swapTransaction.destinationTokenDecimals + * @param {string} swapTransaction.destinationTokenAddress + * @param {string} swapTransaction.swapMetaData + * @param {string} swapTransaction.swapTokenValue + * @param {string} swapTransaction.estimatedBaseFee + * @param {string} swapTransaction.approvalTxId + * @returns {TransactionMeta} the txMeta of the updated transaction + */ + _updateSwapTransaction( + txId, + { + sourceTokenSymbol, + destinationTokenSymbol, + type, + destinationTokenDecimals, + destinationTokenAddress, + swapMetaData, + swapTokenValue, + estimatedBaseFee, + approvalTxId, + }, + ) { + this._throwErrorIfNotUnapprovedTx(txId, 'updateSwapTransaction'); + + let swapTransaction = { + sourceTokenSymbol, + destinationTokenSymbol, + type, + destinationTokenDecimals, + destinationTokenAddress, + swapMetaData, + swapTokenValue, + estimatedBaseFee, + approvalTxId, + }; + + // only update what is defined + swapTransaction = pickBy(swapTransaction); + + const note = `Update Swap Transaction for ${txId}`; + this._updateTransaction(txId, swapTransaction, note); + return this._getTransaction(txId); + } + + /** + * updates and approves the transaction + * + * @param {object} txMeta + * @param {string} actionId + */ + async _updateAndApproveTransaction(txMeta, actionId) { + this.txStateManager.updateTransaction( + txMeta, + 'confTx: user approved transaction', + ); + await this._approveTransaction(txMeta.id, actionId); + } + + /** + * sets the tx status to approved + * auto fills the nonce + * signs the transaction + * publishes the transaction + * if any of these steps fails the tx status will be set to failed + * + * @param {number} txId - the tx's Id + * @param {string} actionId - actionId passed from UI + */ + async _approveTransaction(txId, actionId) { + // TODO: Move this safety out of this function. + // Since this transaction is async, + // we need to keep track of what is currently being signed, + // So that we do not increment nonce + resubmit something + // that is already being incremented & signed. + const txMeta = this.txStateManager.getTransaction(txId); + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + // MMI does not broadcast transactions, as that is the responsibility of the custodian + if (txMeta.custodyStatus) { + this.inProcessOfSigning.delete(txId); + await this.signTransaction(txId); + return; + } + ///: END:ONLY_INCLUDE_IN + + if (this.inProcessOfSigning.has(txId)) { + return; + } + this.inProcessOfSigning.add(txId); + let nonceLock; + try { + // approve + this.txStateManager.setTxStatusApproved(txId); + // get next nonce + const fromAddress = txMeta.txParams.from; + // wait for a nonce + let { customNonceValue } = txMeta; + customNonceValue = Number(customNonceValue); + nonceLock = await this.nonceTracker.getNonceLock(fromAddress); + // add nonce to txParams + // if txMeta has previousGasParams then it is a retry at same nonce with + // higher gas settings and therefor the nonce should not be recalculated + const nonce = txMeta.previousGasParams + ? txMeta.txParams.nonce + : nonceLock.nextNonce; + const customOrNonce = + customNonceValue === 0 ? customNonceValue : customNonceValue || nonce; + + txMeta.txParams.nonce = addHexPrefix(customOrNonce.toString(16)); + // add nonce debugging information to txMeta + txMeta.nonceDetails = nonceLock.nonceDetails; + if (customNonceValue) { + txMeta.nonceDetails.customNonceValue = customNonceValue; + } + this.txStateManager.updateTransaction( + txMeta, + 'transactions#approveTransaction', + ); + // sign transaction + const rawTx = await this.signTransaction(txId); + await this.publishTransaction(txId, rawTx, actionId); + this._trackTransactionMetricsEvent( + txMeta, + TransactionMetaMetricsEvent.approved, + actionId, + ); + // must set transaction to submitted/failed before releasing lock + nonceLock.releaseLock(); + } catch (err) { + // this is try-catch wrapped so that we can guarantee that the nonceLock is released + try { + this._failTransaction(txId, err, actionId); + } catch (err2) { + log.error(err2); + } + // must set transaction to submitted/failed before releasing lock + if (nonceLock) { + nonceLock.releaseLock(); + } + // continue with error chain + throw err; + } finally { + this.inProcessOfSigning.delete(txId); + } + } + + /** + * Convenience method for the ui thats sets the transaction to rejected + * + * @param {number} txId - the tx's Id + * @param {string} actionId - actionId passed from UI + * @returns {Promise} + */ + async _cancelTransaction(txId, actionId) { + const txMeta = this.txStateManager.getTransaction(txId); + this.txStateManager.setTxStatusRejected(txId); + this._trackTransactionMetricsEvent( + txMeta, + TransactionMetaMetricsEvent.rejected, + actionId, + ); + } + /** maps methods for convenience*/ _mapMethods() { /** @returns {object} the state in transaction controller */ @@ -1923,7 +2020,7 @@ export default class TransactionController extends EventEmitter { // Line below will try to publish transaction which is in // APPROVED state at the time of controller bootup - this.approveTransaction(txMeta.id); + this._approveTransaction(txMeta.id); ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) } @@ -2662,56 +2759,70 @@ export default class TransactionController extends EventEmitter { ); } - async _requestApproval( + // Approvals + + async _requestTransactionApproval( txMeta, + { shouldShowRequest = true, actionId } = {}, + ) { + let txId, result; + + try { + txId = txMeta.id; + const { origin } = txMeta; + + const approvalResult = await this._requestApproval( + String(txId), + origin, + { txId }, + { + shouldShowRequest, + }, + ); + + result = approvalResult.resultCallbacks; + + const { value } = approvalResult; + const { txMeta: updatedTxMeta } = value; + + await this._updateAndApproveTransaction(updatedTxMeta, actionId); + + result?.success(); + } catch (error) { + const transaction = this.txStateManager.getTransaction(txId); + + if (transaction && !this._isTransactionCompleted(transaction)) { + if (error.code === errorCodes.provider.userRejectedRequest) { + await this._cancelTransaction(txId, actionId); + } else { + this._failTransaction(txId, error, actionId); + } + } + + result?.error(error); + + throw error; + } + } + + async _requestApproval( + id, + origin, + requestData, { shouldShowRequest } = { shouldShowRequest: true }, ) { - const id = this._getApprovalId(txMeta); - const { origin } = txMeta; const type = ApprovalType.Transaction; - const requestData = { txId: txMeta.id }; - return this.messagingSystem - .call( - 'ApprovalController:addRequest', - { - id, - origin, - type, - requestData, - }, - shouldShowRequest, - ) - .catch(() => { - // Intentionally ignored as promise not currently used - }); - } - - _acceptApproval(txMeta) { - const id = this._getApprovalId(txMeta); - - try { - this.messagingSystem.call('ApprovalController:acceptRequest', id); - } catch (error) { - log.error('Failed to accept transaction approval request', error); - } - } - - _rejectApproval(txMeta) { - const id = this._getApprovalId(txMeta); - - try { - this.messagingSystem.call( - 'ApprovalController:rejectRequest', + return this.messagingSystem.call( + 'ApprovalController:addRequest', + { id, - new Error('Rejected'), - ); - } catch (error) { - log.error('Failed to reject transaction approval request', error); - } - } - - _getApprovalId(txMeta) { - return String(txMeta.id); + origin, + type, + requestData, + expectsResult: true, + }, + shouldShowRequest, + ); } } diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index 02eb957da..8600f08dc 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -1,3 +1,4 @@ +/* eslint-disable prefer-promise-reject-errors */ import { strict as assert } from 'assert'; import EventEmitter from 'events'; import { toBuffer } from 'ethereumjs-util'; @@ -6,6 +7,7 @@ import { ObservableStore } from '@metamask/obs-store'; import { ApprovalType } from '@metamask/controller-utils'; import sinon from 'sinon'; +import { errorCodes, ethErrors } from 'eth-rpc-errors'; import { createTestProviderTools, getTestAccounts, @@ -24,7 +26,6 @@ import { TokenStandard, } from '../../../../shared/constants/transaction'; -import { SECOND } from '../../../../shared/constants/time'; import { GasEstimateTypes, GasRecommendations, @@ -33,6 +34,7 @@ import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { NetworkStatus } from '../../../../shared/constants/network'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; +import TxGasUtil from './tx-gas-utils'; import TransactionController from '.'; const noop = () => true; @@ -46,6 +48,10 @@ const actionId = 'DUMMY_ACTION_ID'; const VALID_ADDRESS = '0x0000000000000000000000000000000000000000'; const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001'; +async function flushPromises() { + await new Promise((resolve) => setImmediate(resolve)); +} + describe('Transaction Controller', function () { let txController, provider, @@ -54,7 +60,9 @@ describe('Transaction Controller', function () { fragmentExists, networkStatusStore, getCurrentChainId, - messengerMock; + messengerMock, + resultCallbacksMock, + updateSpy; beforeEach(function () { fragmentExists = false; @@ -63,6 +71,8 @@ describe('Transaction Controller', function () { eth_gasPrice: '0x0de0b6b3a7640000', // by default, all accounts are external accounts (not contracts) eth_getCode: '0x', + eth_sendRawTransaction: + '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8', }; provider = createTestProviderTools({ scaffold: providerResultStub, @@ -78,7 +88,15 @@ describe('Transaction Controller', function () { blockTrackerStub.getLatestBlock = noop; getCurrentChainId = sinon.stub().callsFake(() => currentChainId); - messengerMock = { call: sinon.stub().returns(Promise.resolve()) }; + + resultCallbacksMock = { + success: sinon.spy(), + error: sinon.spy(), + }; + + messengerMock = { + call: sinon.stub(), + }; txController = new TransactionController({ provider, @@ -113,10 +131,24 @@ describe('Transaction Controller', function () { securityProviderRequest: () => undefined, messenger: messengerMock, }); + txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }); + + updateSpy = sinon.spy(txController.txStateManager, 'updateTransaction'); + + messengerMock.call.callsFake((_) => + Promise.resolve({ + value: { txMeta: getLastTxMeta() }, + resultCallbacks: resultCallbacksMock, + }), + ); }); + function getLastTxMeta() { + return updateSpy.lastCall.args[0]; + } + describe('#getState', function () { it('should return a state object with the right keys and data types', function () { const exposedState = txController.getState(); @@ -297,7 +329,8 @@ describe('Transaction Controller', function () { }); describe('#newUnapprovedTransaction', function () { - let stub, txMeta, txParams; + let txMeta, txParams, getPermittedAccounts, signStub; + beforeEach(function () { txParams = { from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', @@ -311,46 +344,119 @@ describe('Transaction Controller', function () { history: [{}], }; txController.txStateManager._addTransactionsToState([txMeta]); - stub = sinon - .stub(txController, 'addUnapprovedTransaction') - .callsFake(() => { - txController.emit('newUnapprovedTx', txMeta); - return Promise.resolve( - txController.txStateManager.addTransaction(txMeta), - ); - }); + getPermittedAccounts = sinon + .stub(txController, 'getPermittedAccounts') + .returns([txParams.from]); }); afterEach(function () { txController.txStateManager._addTransactionsToState([]); - stub.restore(); + getPermittedAccounts.restore(); + signStub?.restore(); }); it('should resolve when finished and status is submitted and resolve with the hash', async function () { - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - setTimeout(() => { - txController.setTxHash(txMetaFromEmit.id, '0x0'); - txController.txStateManager.setTxStatusSubmitted(txMetaFromEmit.id); - }); - }); - const hash = await txController.newUnapprovedTransaction(txParams); assert.ok(hash, 'newUnapprovedTransaction needs to return the hash'); }); it('should reject when finished and status is rejected', async function () { - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - setTimeout(() => { - txController.txStateManager.setTxStatusRejected(txMetaFromEmit.id); - }); + messengerMock.call.returns( + Promise.reject({ code: errorCodes.provider.userRejectedRequest }), + ); + + await assert.rejects(txController.newUnapprovedTransaction(txParams), { + message: 'MetaMask Tx Signature: User denied transaction signature.', + }); + }); + + it('rejects when finished and status is failed', async function () { + const signError = new Error('TestSigningError'); + + signStub = sinon.stub(txController, 'signEthTx').throws(signError); + + await assert.rejects(txController.newUnapprovedTransaction(txParams), { + message: signError.message, + }); + }); + + it('creates an approval request', async function () { + await txController.newUnapprovedTransaction(txParams); + + const txId = getLastTxMeta().id; + + assert.equal(messengerMock.call.callCount, 1); + assert.deepEqual(messengerMock.call.getCall(0).args, [ + 'ApprovalController:addRequest', + { + id: String(txId), + origin: undefined, + requestData: { txId }, + type: ApprovalType.Transaction, + expectsResult: true, + }, + true, // Show popup + ]); + }); + + describe('if transaction with same actionId exists', function () { + it('does not create an additional approval request', async function () { + await txController.newUnapprovedTransaction(txParams, { id: '12345' }); + await txController.newUnapprovedTransaction(txParams, { id: '12345' }); + + const txId = getLastTxMeta().id; + + assert.equal(messengerMock.call.callCount, 1); + assert.deepEqual(messengerMock.call.getCall(0).args, [ + 'ApprovalController:addRequest', + { + id: String(txId), + origin: undefined, + requestData: { txId }, + type: ApprovalType.Transaction, + expectsResult: true, + }, + true, // Show popup + ]); }); - await assert.rejects( - () => txController.newUnapprovedTransaction(txParams), - { - message: 'MetaMask Tx Signature: User denied transaction signature.', - }, - ); + it('does not resolve until transaction approved', async function () { + let firstTransactionResolve; + let firstTransactionCompleted = false; + let secondTransactionCompleted = false; + + messengerMock.call.returns( + new Promise((resolve) => { + firstTransactionResolve = resolve; + }), + ); + + txController + .newUnapprovedTransaction(txParams, { id: '12345' }) + .then(() => { + firstTransactionCompleted = true; + }); + + await flushPromises(); + + txController + .newUnapprovedTransaction(txParams, { id: '12345' }) + .then(() => { + secondTransactionCompleted = true; + }); + + await flushPromises(); + + assert.equal(firstTransactionCompleted, false); + assert.equal(secondTransactionCompleted, false); + + firstTransactionResolve({ value: { txMeta: getLastTxMeta() } }); + + await flushPromises(); + + assert.equal(secondTransactionCompleted, true); + assert.equal(secondTransactionCompleted, true); + }); }); }); @@ -460,20 +566,6 @@ describe('Transaction Controller', function () { assert.equal(transactionCount1 + 1, transactionCount2); }); - it('should emit newUnapprovedTx event and pass txMeta as the first argument', function (done) { - providerResultStub.eth_gasPrice = '4a817c800'; - txController.once('newUnapprovedTx', (txMetaFromEmit) => { - assert.ok(txMetaFromEmit, 'txMeta is falsy'); - done(); - }); - txController - .addUnapprovedTransaction(undefined, { - from: selectedAddress, - to: recipientAddress, - }) - .catch(done); - }); - it("should fail if the from address isn't the selected address", async function () { await assert.rejects(() => txController.addUnapprovedTransaction({ @@ -512,12 +604,13 @@ describe('Transaction Controller', function () { origin: ORIGIN_METAMASK, requestData: { txId: txMeta.id }, type: ApprovalType.Transaction, + expectsResult: true, }, true, // Show popup ]); }); - it('should still create an approval request when called twice with same actionId', async function () { + it('updates meta if transaction type is swap approval', async function () { await txController.addUnapprovedTransaction( undefined, { @@ -525,34 +618,677 @@ describe('Transaction Controller', function () { to: recipientAddress, }, ORIGIN_METAMASK, - undefined, + TransactionType.swapApproval, undefined, '12345', + { swaps: { meta: { type: 'swapApproval', sourceTokenSymbol: 'XBN' } } }, ); - const secondTxMeta = await txController.addUnapprovedTransaction( + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.type, 'swapApproval'); + assert.equal(transaction.sourceTokenSymbol, 'XBN'); + }); + + it('updates meta if transaction type is swap', async function () { + await txController.addUnapprovedTransaction( undefined, { from: selectedAddress, to: recipientAddress, }, - undefined, - undefined, + ORIGIN_METAMASK, + TransactionType.swap, undefined, '12345', + { + swaps: { + meta: { + sourceTokenSymbol: 'BTCX', + destinationTokenSymbol: 'ETH', + type: 'swapped', + destinationTokenDecimals: 8, + destinationTokenAddress: VALID_ADDRESS_TWO, + swapTokenValue: '0x0077', + }, + }, + }, ); - assert.equal(messengerMock.call.callCount, 2); - assert.deepEqual(messengerMock.call.getCall(1).args, [ - 'ApprovalController:addRequest', - { - id: String(secondTxMeta.id), - origin: ORIGIN_METAMASK, - requestData: { txId: secondTxMeta.id }, - type: ApprovalType.Transaction, - }, - true, // Show popup - ]); + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.sourceTokenSymbol, 'BTCX'); + assert.equal(transaction.destinationTokenSymbol, 'ETH'); + assert.equal(transaction.type, 'swapped'); + assert.equal(transaction.destinationTokenDecimals, 8); + assert.equal(transaction.destinationTokenAddress, VALID_ADDRESS_TWO); + assert.equal(transaction.swapTokenValue, '0x0077'); + }); + + describe('if swaps trade with no approval transaction and simulation fails', function () { + let analyzeGasUsageOriginal; + + beforeEach(function () { + analyzeGasUsageOriginal = TxGasUtil.prototype.analyzeGasUsage; + + sinon.stub(TxGasUtil.prototype, 'analyzeGasUsage').returns({ + simulationFails: true, + }); + }); + + afterEach(function () { + // Sinon restore didn't work + TxGasUtil.prototype.analyzeGasUsage = analyzeGasUsageOriginal; + }); + + it('throws error', async function () { + await assert.rejects( + txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + TransactionType.swap, + undefined, + '12345', + { + swaps: { + hasApproveTx: false, + }, + }, + ), + new Error('Simulation failed'), + ); + }); + + it('cancels transaction', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + TransactionType.swap, + undefined, + '12345', + { + swaps: { + hasApproveTx: false, + }, + }, + ); + } catch (error) { + // Expected error + } + + assert.equal(listener.callCount, 1, listener.args); + + const [txId, status] = listener.args[0]; + + assert.equal(status, TransactionStatus.rejected); + assert.equal(txId, getLastTxMeta().id); + }); + }); + + describe('if transaction with same actionId exists', function () { + it('does not create an additional approval request', async function () { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + + const secondTxMeta = await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + undefined, + undefined, + undefined, + '12345', + ); + + assert.equal(messengerMock.call.callCount, 1); + assert.deepEqual(messengerMock.call.getCall(0).args, [ + 'ApprovalController:addRequest', + { + id: String(secondTxMeta.id), + origin: ORIGIN_METAMASK, + requestData: { txId: secondTxMeta.id }, + type: ApprovalType.Transaction, + expectsResult: true, + }, + true, // Show popup + ]); + }); + + it('does not resolve until transaction approved', async function () { + let firstTransactionResolve; + let firstTransactionCompleted = false; + let secondTransactionCompleted = false; + + messengerMock.call.returns( + new Promise((resolve) => { + firstTransactionResolve = resolve; + }), + ); + + txController + .addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ) + .then(() => { + firstTransactionCompleted = true; + }); + + await flushPromises(); + + txController + .addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + undefined, + undefined, + undefined, + '12345', + ) + .then(() => { + secondTransactionCompleted = true; + }); + + await flushPromises(); + + assert.equal(firstTransactionCompleted, false); + assert.equal(secondTransactionCompleted, false); + + firstTransactionResolve({ value: { txMeta: getLastTxMeta() } }); + + await flushPromises(); + + assert.equal(secondTransactionCompleted, true); + assert.equal(secondTransactionCompleted, true); + }); + }); + + describe('on approval', function () { + it('changes status to submitted', async function () { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.status, TransactionStatus.submitted); + }); + + it('emits approved, signed, and submitted status events', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + + const txId = getLastTxMeta().id; + + assert.equal(listener.callCount, 3); + assert.equal(listener.args[0][0], txId); + assert.equal(listener.args[0][1], TransactionStatus.approved); + assert.equal(listener.args[1][0], txId); + assert.equal(listener.args[1][1], TransactionStatus.signed); + assert.equal(listener.args[2][0], txId); + assert.equal(listener.args[2][1], TransactionStatus.submitted); + }); + + it('reports success to approval request acceptor', async function () { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + + assert.equal(resultCallbacksMock.success.callCount, 1); + }); + + it('does not overwrite set values', async function () { + const originalValue = '0x01'; + const wrongValue = '0x05'; + + providerResultStub.eth_gasPrice = wrongValue; + providerResultStub.eth_estimateGas = '0x5209'; + + const signStub = sinon + .stub(txController, 'signTransaction') + .callsFake(() => Promise.resolve()); + + const pubStub = sinon + .stub(txController, 'publishTransaction') + .callsFake(() => { + const txId = getLastTxMeta().id; + txController.setTxHash(txId, originalValue); + txController.txStateManager.setTxStatusSubmitted(txId); + }); + + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + nonce: originalValue, + gas: originalValue, + gasPrice: originalValue, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + + const txId = getLastTxMeta().id; + const result = txController.txStateManager.getTransaction(txId); + const params = result.txParams; + + assert.equal(params.gas, originalValue, 'gas unmodified'); + assert.equal(params.gasPrice, originalValue, 'gas price unmodified'); + assert.equal(result.hash, originalValue); + assert.equal( + result.status, + TransactionStatus.submitted, + 'should have reached the submitted status.', + ); + + signStub.restore(); + pubStub.restore(); + }); + }); + + describe('on cancel', function () { + beforeEach(async function () { + messengerMock.call.returns( + Promise.reject({ code: errorCodes.provider.userRejectedRequest }), + ); + }); + + it('throws error', async function () { + await assert.rejects( + txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ), + { code: ethErrors.provider.userRejectedRequest().code }, + ); + }); + + it('emits rejected status event', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch (error) { + // Expected error + } + + assert.equal(listener.callCount, 1); + + const [txId, status] = listener.args[0]; + + assert.equal(status, TransactionStatus.rejected); + assert.equal(txId, getLastTxMeta().id); + }); + }); + + describe('on signing error', function () { + const signError = new Error('TestSignError'); + let signStub; + + beforeEach(async function () { + signStub = sinon.stub(txController, 'signEthTx').throws(signError); + }); + + afterEach(function () { + signStub.restore(); + }); + + it('changes status to failed', async function () { + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch { + // Expected error + } + + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.status, TransactionStatus.failed); + }); + + it('throws error', async function () { + await assert.rejects( + txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ), + signError, + ); + }); + + it('emits approved and failed status events', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch (error) { + // Expected error + } + + const txId = getLastTxMeta().id; + + assert.equal(listener.callCount, 2); + assert.equal(listener.args[0][0], txId); + assert.equal(listener.args[0][1], TransactionStatus.approved); + assert.equal(listener.args[1][0], txId); + assert.equal(listener.args[1][1], TransactionStatus.failed); + }); + + it('reports error to approval request acceptor', async function () { + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch { + // Expected error + } + + assert.equal(resultCallbacksMock.error.callCount, 1); + assert.strictEqual( + resultCallbacksMock.error.getCall(0).args[0].message, + signError.message, + ); + }); + }); + + describe('on publish error', function () { + const publishError = new Error('TestPublishError'); + let originalQuery; + + beforeEach(async function () { + originalQuery = txController.query; + + txController.query = { + sendRawTransaction: sinon.stub().throws(publishError), + }; + }); + + afterEach(function () { + txController.query = originalQuery; + }); + + it('changes status to failed', async function () { + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch { + // Expected error + } + + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.status, TransactionStatus.failed); + }); + + it('throws error', async function () { + await assert.rejects( + txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ), + publishError, + ); + }); + + it('emits approved, signed, and failed status events', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch (error) { + // Expected error + } + + const txId = getLastTxMeta().id; + + assert.equal(listener.callCount, 3); + assert.equal(listener.args[0][0], txId); + assert.equal(listener.args[0][1], TransactionStatus.approved); + assert.equal(listener.args[1][0], txId); + assert.equal(listener.args[1][1], TransactionStatus.signed); + assert.equal(listener.args[2][0], txId); + assert.equal(listener.args[2][1], TransactionStatus.failed); + }); + + it('reports error to approval request acceptor', async function () { + try { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + ); + } catch { + // Expected error + } + + assert.equal(resultCallbacksMock.error.callCount, 1); + assert.strictEqual( + resultCallbacksMock.error.getCall(0).args[0].message, + publishError.message, + ); + }); + }); + + describe('with require approval set to false', function () { + beforeEach(function () { + // Ensure that the default approval mock is not being used + messengerMock.call.callsFake(() => Promise.reject()); + }); + + it('changes status to submitted', async function () { + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + { requireApproval: false }, + ); + + const transaction = txController.getTransactions({ + searchCriteria: { id: getLastTxMeta().id }, + })[0]; + + assert.equal(transaction.status, TransactionStatus.submitted); + }); + + it('emits approved, signed, and submitted status events', async function () { + const listener = sinon.spy(); + + txController.on('tx:status-update', listener); + + await txController.addUnapprovedTransaction( + undefined, + { + from: selectedAddress, + to: recipientAddress, + }, + ORIGIN_METAMASK, + undefined, + undefined, + '12345', + { requireApproval: false }, + ); + + const txId = getLastTxMeta().id; + + assert.equal(listener.callCount, 3); + assert.equal(listener.args[0][0], txId); + assert.equal(listener.args[0][1], TransactionStatus.approved); + assert.equal(listener.args[1][0], txId); + assert.equal(listener.args[1][1], TransactionStatus.signed); + assert.equal(listener.args[2][0], txId); + assert.equal(listener.args[2][1], TransactionStatus.submitted); + }); }); }); @@ -595,7 +1331,6 @@ describe('Transaction Controller', function () { from: selectedAddress, to: recipientAddress, }); - await txController.approveTransaction(txMeta.id); const cancelTxMeta = await txController.createCancelTransaction( txMeta.id, {}, @@ -606,8 +1341,7 @@ describe('Transaction Controller', function () { cancelTxMeta.id, ); assert.deepEqual(cancelTxMeta, memTxMeta); - // One for the initial addUnapprovedTransaction, one for the approval - assert.equal(messengerMock.call.callCount, 2); + assert.equal(messengerMock.call.callCount, 1); }); it('should add only 1 cancel transaction when called twice with same actionId', async function () { @@ -615,7 +1349,6 @@ describe('Transaction Controller', function () { from: selectedAddress, to: recipientAddress, }); - await txController.approveTransaction(txMeta.id); await txController.createCancelTransaction( txMeta.id, {}, @@ -638,7 +1371,6 @@ describe('Transaction Controller', function () { from: selectedAddress, to: recipientAddress, }); - await txController.approveTransaction(txMeta.id); await txController.createCancelTransaction( txMeta.id, {}, @@ -1063,77 +1795,6 @@ describe('Transaction Controller', function () { }); }); - describe('#approveTransaction', function () { - let originalValue, txMeta, signStub, pubStub; - - beforeEach(function () { - originalValue = '0x01'; - txMeta = { - id: '1', - status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, - txParams: { - to: VALID_ADDRESS_TWO, - from: VALID_ADDRESS, - nonce: originalValue, - gas: originalValue, - gasPrice: originalValue, - }, - }; - // eslint-disable-next-line @babel/no-invalid-this - this.timeout(SECOND * 15); - const wrongValue = '0x05'; - - txController.addTransaction(txMeta); - providerResultStub.eth_gasPrice = wrongValue; - providerResultStub.eth_estimateGas = '0x5209'; - - signStub = sinon - .stub(txController, 'signTransaction') - .callsFake(() => Promise.resolve()); - - pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => { - txController.setTxHash('1', originalValue); - txController.txStateManager.setTxStatusSubmitted('1'); - }); - }); - - afterEach(function () { - signStub.restore(); - pubStub.restore(); - }); - - it('does not overwrite set values', async function () { - await txController.approveTransaction(txMeta.id); - const result = txController.txStateManager.getTransaction(txMeta.id); - const params = result.txParams; - - assert.equal(params.gas, originalValue, 'gas unmodified'); - assert.equal(params.gasPrice, originalValue, 'gas price unmodified'); - assert.equal(result.hash, originalValue); - assert.equal( - result.status, - TransactionStatus.submitted, - 'should have reached the submitted status.', - ); - }); - - it('should accept the approval request', async function () { - await txController.approveTransaction(txMeta.id); - - assert.equal(messengerMock.call.callCount, 1); - assert.deepEqual(messengerMock.call.getCall(0).args, [ - 'ApprovalController:acceptRequest', - txMeta.id, - ]); - }); - - it('should not throw if accepting approval request throws', async function () { - messengerMock.call.throws(); - await txController.approveTransaction(txMeta.id); - }); - }); - describe('#sign replay-protected tx', function () { it('prepares a tx with the chainId set', async function () { txController.addTransaction( @@ -1154,28 +1815,6 @@ describe('Transaction Controller', function () { }); }); - describe('#updateAndApproveTransaction', function () { - it('should update and approve transactions', async function () { - const txMeta = { - id: 1, - status: TransactionStatus.unapproved, - txParams: { - from: fromAccount.address, - to: '0x1678a085c290ebd122dc42cba69373b5953b831d', - gasPrice: '0x77359400', - gas: '0x7b0d', - nonce: '0x4b', - }, - metamaskNetworkId: currentNetworkId, - }; - txController.txStateManager.addTransaction(txMeta); - const approvalPromise = txController.updateAndApproveTransaction(txMeta); - const tx = txController.txStateManager.getTransaction(1); - assert.equal(tx.status, TransactionStatus.approved); - await approvalPromise; - }); - }); - describe('#getChainId', function () { it('returns the chain ID of the network when it is available', function () { networkStatusStore.putState(NetworkStatus.Available); @@ -1194,117 +1833,6 @@ describe('Transaction Controller', function () { }); }); - describe('#cancelTransaction', function () { - beforeEach(function () { - txController.txStateManager._addTransactionsToState([ - { - id: 0, - status: TransactionStatus.unapproved, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 1, - status: TransactionStatus.rejected, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 2, - status: TransactionStatus.approved, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 3, - status: TransactionStatus.signed, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 4, - status: TransactionStatus.submitted, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 5, - status: TransactionStatus.confirmed, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - { - id: 6, - status: TransactionStatus.failed, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, - metamaskNetworkId: currentNetworkId, - history: [{}], - }, - ]); - }); - - it('should emit a status change to rejected', function (done) { - txController.once('tx:status-update', (txId, status) => { - try { - assert.equal( - status, - TransactionStatus.rejected, - 'status should be rejected', - ); - assert.equal(txId, 0, 'id should e 0'); - done(); - } catch (e) { - done(e); - } - }); - - txController.cancelTransaction(0); - }); - - it('should reject the approval request', function () { - txController.cancelTransaction(0); - - assert.equal(messengerMock.call.callCount, 1); - assert.deepEqual(messengerMock.call.getCall(0).args, [ - 'ApprovalController:rejectRequest', - '0', - new Error('Rejected'), - ]); - }); - - it('should not throw if rejecting approval request throws', async function () { - messengerMock.call.throws(); - txController.cancelTransaction(0); - }); - }); - describe('#createSpeedUpTransaction', function () { let addTransactionSpy; let approveTransactionSpy; @@ -1320,7 +1848,7 @@ describe('Transaction Controller', function () { beforeEach(function () { addTransactionSpy = sinon.spy(txController, 'addTransaction'); - approveTransactionSpy = sinon.spy(txController, 'approveTransaction'); + approveTransactionSpy = sinon.spy(txController, '_approveTransaction'); const hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8'; @@ -1416,7 +1944,6 @@ describe('Transaction Controller', function () { from: selectedAddress, to: recipientAddress, }); - await txController.approveTransaction(txMeta.id); await txController.createSpeedUpTransaction( txMeta.id, {}, @@ -1439,7 +1966,6 @@ describe('Transaction Controller', function () { from: selectedAddress, to: recipientAddress, }); - await txController.approveTransaction(txMeta.id); await txController.createSpeedUpTransaction( txMeta.id, {}, @@ -1465,7 +1991,6 @@ describe('Transaction Controller', function () { to: recipientAddress, }, ); - await txController.approveTransaction(txMeta.id); await txController.createSpeedUpTransaction( txMeta.id, {}, @@ -2766,44 +3291,6 @@ describe('Transaction Controller', function () { assert.equal(result.decEstimatedBaseFee, '66'); }); - it('updates swap approval transaction', function () { - txController.updateSwapApprovalTransaction('1', { - type: 'swapApproval', - sourceTokenSymbol: 'XBN', - }); - - const result = txStateManager.getTransaction('1'); - assert.equal(result.type, 'swapApproval'); - assert.equal(result.sourceTokenSymbol, 'XBN'); - }); - - it('updates swap transaction', function () { - txController.updateSwapTransaction('1', { - sourceTokenSymbol: 'BTCX', - destinationTokenSymbol: 'ETH', - }); - - const result = txStateManager.getTransaction('1'); - assert.equal(result.sourceTokenSymbol, 'BTCX'); - assert.equal(result.destinationTokenSymbol, 'ETH'); - assert.equal(result.destinationTokenDecimals, 16); - assert.equal(result.destinationTokenAddress, VALID_ADDRESS); - assert.equal(result.swapTokenValue, '0x007'); - - txController.updateSwapTransaction('1', { - type: 'swapped', - destinationTokenDecimals: 8, - destinationTokenAddress: VALID_ADDRESS_TWO, - swapTokenValue: '0x0077', - }); - assert.equal(result.sourceTokenSymbol, 'BTCX'); - assert.equal(result.destinationTokenSymbol, 'ETH'); - assert.equal(result.type, 'swapped'); - assert.equal(result.destinationTokenDecimals, 8); - assert.equal(result.destinationTokenAddress, VALID_ADDRESS_TWO); - assert.equal(result.swapTokenValue, '0x0077'); - }); - it('updates transaction user settings', function () { txController.updateTransactionUserSettings('1', { userEditedGasLimit: '0x0088', @@ -2844,32 +3331,16 @@ describe('Transaction Controller', function () { }); it('does not update unknown parameters in update method', function () { - txController.updateSwapTransaction('1', { - type: 'swapped', - destinationTokenDecimals: 8, - destinationTokenAddress: VALID_ADDRESS_TWO, - swapTokenValue: '0x011', - gasPrice: '0x12', - }); - - let result = txStateManager.getTransaction('1'); - - assert.equal(result.type, 'swapped'); - assert.equal(result.destinationTokenDecimals, 8); - assert.equal(result.destinationTokenAddress, VALID_ADDRESS_TWO); - assert.equal(result.swapTokenValue, '0x011'); - assert.equal(result.txParams.gasPrice, '0x002'); // not updated even though it's passed in to update - txController.updateTransactionGasFees('1', { estimateUsed: '0x13', gasPrice: '0x14', destinationTokenAddress: VALID_ADDRESS, }); - result = txStateManager.getTransaction('1'); + const result = txStateManager.getTransaction('1'); assert.equal(result.estimateUsed, '0x13'); assert.equal(result.txParams.gasPrice, '0x14'); - assert.equal(result.destinationTokenAddress, VALID_ADDRESS_TWO); // not updated even though it's passed in to update + assert.equal(result.destinationTokenAddress, VALID_ADDRESS); // not updated even though it's passed in to update }); }); @@ -2996,34 +3467,34 @@ describe('Transaction Controller', function () { describe('initApprovals', function () { it('adds unapprovedTxs as approvals', async function () { const firstTxId = '1'; - txController.addTransaction( - { - id: firstTxId, - origin: ORIGIN_METAMASK, - status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, + const firstTxMeta = { + id: firstTxId, + origin: ORIGIN_METAMASK, + status: TransactionStatus.unapproved, + metamaskNetworkId: currentNetworkId, + txParams: { + to: VALID_ADDRESS, + from: VALID_ADDRESS_TWO, }, - noop, - ); + }; + const secondTxId = '2'; - txController.addTransaction( - { - id: secondTxId, - origin: ORIGIN_METAMASK, - status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, - txParams: { - to: VALID_ADDRESS, - from: VALID_ADDRESS_TWO, - }, + const secondTxMeta = { + id: secondTxId, + origin: ORIGIN_METAMASK, + status: TransactionStatus.unapproved, + metamaskNetworkId: currentNetworkId, + txParams: { + to: VALID_ADDRESS, + from: VALID_ADDRESS_TWO, }, - noop, - ); + }; + + txController.addTransaction(firstTxMeta); + txController.addTransaction(secondTxMeta); + await txController.initApprovals(); + assert.deepEqual(messengerMock.call.getCall(0).args, [ 'ApprovalController:addRequest', { @@ -3031,6 +3502,7 @@ describe('Transaction Controller', function () { origin: ORIGIN_METAMASK, requestData: { txId: firstTxId }, type: ApprovalType.Transaction, + expectsResult: true, }, false, ]); @@ -3041,9 +3513,43 @@ describe('Transaction Controller', function () { origin: ORIGIN_METAMASK, requestData: { txId: secondTxId }, type: ApprovalType.Transaction, + expectsResult: true, }, false, ]); }); }); + + describe('#updateTransactionSendFlowHistory', function () { + it('returns same result after two sequential calls with same history', async function () { + const txId = 1; + + const txMeta = { + id: txId, + origin: ORIGIN_METAMASK, + status: TransactionStatus.unapproved, + metamaskNetworkId: currentNetworkId, + txParams: { + to: VALID_ADDRESS, + from: VALID_ADDRESS_TWO, + }, + }; + + txController.addTransaction(txMeta); + + const transaction1 = txController.updateTransactionSendFlowHistory( + txId, + 2, + ['foo1', 'foo2'], + ); + + const transaction2 = txController.updateTransactionSendFlowHistory( + txId, + 2, + ['foo1', 'foo2'], + ); + + assert.deepEqual(transaction1, transaction2); + }); + }); }); diff --git a/app/scripts/lib/rpc-method-middleware/handlers/index.js b/app/scripts/lib/rpc-method-middleware/handlers/index.js index 9b0fc02fe..126da76f9 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/index.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/index.js @@ -7,6 +7,16 @@ import sendMetadata from './send-metadata'; import switchEthereumChain from './switch-ethereum-chain'; import watchAsset from './watch-asset'; +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import mmiSupported from './institutional/mmi-supported'; +import mmiAuthenticate from './institutional/mmi-authenticate'; +import mmiPortfolio from './institutional/mmi-portfolio'; +import mmiOpenSwaps from './institutional/mmi-open-swaps'; +import mmiCheckIfTokenIsPresent from './institutional/mmi-check-if-token-is-present'; +import mmiSetAccountAndNetwork from './institutional/mmi-set-account-and-network'; +import mmiOpenAddHardwareWallet from './institutional/mmi-open-add-hardware-wallet'; +///: END:ONLY_INCLUDE_IN + const handlers = [ addEthereumChain, ethAccounts, @@ -16,5 +26,14 @@ const handlers = [ sendMetadata, switchEthereumChain, watchAsset, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + mmiAuthenticate, + mmiSupported, + mmiPortfolio, + mmiOpenSwaps, + mmiCheckIfTokenIsPresent, + mmiSetAccountAndNetwork, + mmiOpenAddHardwareWallet, + ///: END:ONLY_INCLUDE_IN ]; export default handlers; diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-authenticate.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-authenticate.js new file mode 100644 index 000000000..48014a6d6 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-authenticate.js @@ -0,0 +1,43 @@ +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiAuthenticate = { + methodNames: [MESSAGE_TYPE.MMI_AUTHENTICATE, MESSAGE_TYPE.MMI_REAUTHENTICATE], + implementation: mmiAuthenticateHandler, + hookNames: { + handleMmiAuthenticate: true, + }, +}; +export default mmiAuthenticate; + +/** + * @typedef {object} MmiAuthenticateOptions + * @property {Function} handleWatchAssetRequest - The wallet_watchAsset method implementation. + */ + +/** + * @typedef {object} MmiAuthenticateParam + * @property {string} service - The service to which we are authenticating, e.g. 'codefi-compliance' + * @property {object} token - The token used to authenticate + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param {WatchAssetOptions} options + */ +async function mmiAuthenticateHandler( + req, + res, + _next, + end, + { handleMmiAuthenticate }, +) { + try { + res.result = await handleMmiAuthenticate(req); + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-check-if-token-is-present.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-check-if-token-is-present.js new file mode 100644 index 000000000..45b617bd9 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-check-if-token-is-present.js @@ -0,0 +1,46 @@ +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiAuthenticate = { + methodNames: [MESSAGE_TYPE.MMI_CHECK_IF_TOKEN_IS_PRESENT], + implementation: mmiCheckIfTokenIsPresentHandler, + hookNames: { + handleMmiCheckIfTokenIsPresent: true, + }, +}; +export default mmiAuthenticate; + +/** + * @typedef {object} MmiAuthenticateOptions + * @property {Function} handleMmiCheckIfTokenIsPresent - The metamaskinstitutional_checkIfTokenIsPresent method implementation. + */ + +/** + * @typedef {object} MmiCheckIfTokenIsPresentParam + * @property {string} service - The service to which we are authenticating, e.g. 'codefi-compliance' + * @property {object} environment - The environment in which we are authenticating, e.g. 'saturn-dev' + * @property {apiUrl} apiUrl - The API URL to which we are authenticating, e.g. 'https://saturn-custody.codefi.network/eth' + * @property {object} token - The token used to authenticate + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param options0 + * @param options0.handleMmiCheckIfTokenIsPresent + */ +async function mmiCheckIfTokenIsPresentHandler( + req, + res, + _next, + end, + { handleMmiCheckIfTokenIsPresent }, +) { + try { + res.result = await handleMmiCheckIfTokenIsPresent(req); + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js new file mode 100644 index 000000000..2518a326a --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-add-hardware-wallet.js @@ -0,0 +1,50 @@ +import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiOpenAddHardwareWallet = { + methodNames: [MESSAGE_TYPE.MMI_OPEN_ADD_HARDWARE_WALLET], + implementation: mmiOpenAddHardwareWalletHandler, + hookNames: { + handleMmiOpenAddHardwareWallet: true, + }, +}; +export default mmiOpenAddHardwareWallet; + +/** + * @typedef {object} MmiOpenAddHardwareWalletOptions + * @property {Function} handleMmiOpenAddHardwareWallet - The metmaskinsititutional_openAddHardwareWallet method implementation. + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param {WatchAssetOptions} options + */ +async function mmiOpenAddHardwareWalletHandler( + req, + res, + _next, + end, + { handleMmiOpenAddHardwareWallet }, +) { + try { + let validUrl = false; + // if (!RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].includes(req.origin)) { + RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { + // eslint-disable-next-line require-unicode-regexp + if (regexp.test(req.origin)) { + validUrl = true; + } + }); + // eslint-disable-next-line no-negated-condition + if (!validUrl) { + throw new Error('Unauthorized'); + } + res.result = await handleMmiOpenAddHardwareWallet(); + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js new file mode 100644 index 000000000..f0cdbd026 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-open-swaps.js @@ -0,0 +1,68 @@ +import { ethErrors } from 'eth-rpc-errors'; +import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiOpenSwaps = { + methodNames: [MESSAGE_TYPE.MMI_OPEN_SWAPS], + implementation: mmiOpenSwapsHandler, + hookNames: { + handleMmiOpenSwaps: true, + }, +}; +export default mmiOpenSwaps; + +/** + * @typedef {object} MmiOpenSwapsOptions + * @property {Function} handleMmiOpenSwaps - The metmaskinsititutional_open_swaps method implementation. + */ + +/** + * @typedef {object} MmiOpenSwapsParam + * @property {string} service - The service to which we are authenticating, e.g. 'codefi-compliance' + * @property {object} token - The token used to authenticate + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param {WatchAssetOptions} options + */ +async function mmiOpenSwapsHandler( + req, + res, + _next, + end, + { handleMmiOpenSwaps }, +) { + try { + let validUrl = false; + // if (!RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].includes(req.origin)) { + RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { + // eslint-disable-next-line require-unicode-regexp + if (regexp.test(req.origin)) { + validUrl = true; + } + }); + // eslint-disable-next-line no-negated-condition + if (!validUrl) { + throw new Error('Unauthorized'); + } + + if (!req.params?.[0] || typeof req.params[0] !== 'object') { + return end( + ethErrors.rpc.invalidParams({ + message: `Expected single, object parameter. Received:\n${JSON.stringify( + req.params, + )}`, + }), + ); + } + const { address, network } = req.params[0]; + res.result = await handleMmiOpenSwaps(req.origin, address, network); + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js new file mode 100644 index 000000000..e52599ee9 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-portfolio.js @@ -0,0 +1,56 @@ +import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiPortfolio = { + methodNames: [MESSAGE_TYPE.MMI_PORTFOLIO], + implementation: mmiPortfolioHandler, + hookNames: { + handleMmiDashboardData: true, + }, +}; +export default mmiPortfolio; + +/** + * @typedef {object} MmiPortfolioOptions + * @property {Function} handleMmiDashboardData - The metmaskinsititutional_portfolio method implementation. + */ + +/** + * @typedef {object} MmiPortfolioParam + * @property {string} service - The service to which we are authenticating, e.g. 'codefi-compliance' + * @property {object} token - The token used to authenticate + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param {WatchAssetOptions} options + */ +async function mmiPortfolioHandler( + req, + res, + _next, + end, + { handleMmiDashboardData }, +) { + try { + let validUrl = false; + RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { + // eslint-disable-next-line require-unicode-regexp + if (regexp.test(req.origin)) { + validUrl = true; + } + }); + // eslint-disable-next-line no-negated-condition + if (!validUrl) { + throw new Error('Unauthorized'); + } else { + res.result = await handleMmiDashboardData(req); + return end(); + } + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js new file mode 100644 index 000000000..db8806915 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-set-account-and-network.js @@ -0,0 +1,71 @@ +import { ethErrors } from 'eth-rpc-errors'; +import { RPC_ALLOWED_ORIGINS } from '@metamask-institutional/rpc-allowlist'; +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiSetAccountAndNetwork = { + methodNames: [MESSAGE_TYPE.MMI_SET_ACCOUNT_AND_NETWORK], + implementation: mmiSetAccountAndNetworkHandler, + hookNames: { + handleMmiSetAccountAndNetwork: true, + }, +}; +export default mmiSetAccountAndNetwork; + +/** + * @typedef {object} MmiSetAccountAndNetworkOptions + * @property {Function} handleMmiSetAccountAndNetwork - The metmaskinsititutional_set_account_and_network method implementation. + */ + +/** + * @typedef {object} MmiSetAccountAndNetworkParam + * @property {string} account - Account address + * @property {number} network - Chain Id + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + * @param {WatchAssetOptions} options + */ +async function mmiSetAccountAndNetworkHandler( + req, + res, + _next, + end, + { handleMmiSetAccountAndNetwork }, +) { + try { + let validUrl = false; + RPC_ALLOWED_ORIGINS[MESSAGE_TYPE.MMI_PORTFOLIO].forEach((regexp) => { + // eslint-disable-next-line require-unicode-regexp + if (regexp.test(req.origin)) { + validUrl = true; + } + }); + // eslint-disable-next-line no-negated-condition + if (!validUrl) { + throw new Error('Unauthorized'); + } + + if (!req.params?.[0] || typeof req.params[0] !== 'object') { + return end( + ethErrors.rpc.invalidParams({ + message: `Expected single, object parameter. Received:\n${JSON.stringify( + req.params, + )}`, + }), + ); + } + const { address, network } = req.params[0]; + res.result = await handleMmiSetAccountAndNetwork( + req.origin, + address, + network, + ); + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-supported.js b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-supported.js new file mode 100644 index 000000000..5aa987ed8 --- /dev/null +++ b/app/scripts/lib/rpc-method-middleware/handlers/institutional/mmi-supported.js @@ -0,0 +1,34 @@ +import { MESSAGE_TYPE } from '../../../../../../shared/constants/app'; + +const mmiSupported = { + methodNames: [MESSAGE_TYPE.MMI_SUPPORTED], + implementation: mmiSupportedHandler, + hookNames: {}, +}; +export default mmiSupported; + +/** + * @typedef {object} MmiAuthenticateOptions + * @property {Function} mmiSupportedHandler + * This method simply returns true if this is Metamask Institutional + */ + +/** + * @typedef {object} MmiSupportedParam + * @property {string} mmiSupported No parameters + */ + +/** + * @param {import('json-rpc-engine').JsonRpcRequest} _req - The JSON-RPC request object. + * @param {import('json-rpc-engine').JsonRpcResponse} res - The JSON-RPC response object. + * @param {Function} _next - The json-rpc-engine 'next' callback. + * @param {Function} end - The json-rpc-engine 'end' callback. + */ +async function mmiSupportedHandler(_req, res, _next, end) { + try { + res.result = true; + return end(); + } catch (error) { + return end(error); + } +} diff --git a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js index 6935ef96c..8954a15f5 100644 --- a/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js +++ b/app/scripts/lib/rpc-method-middleware/handlers/watch-asset.js @@ -1,4 +1,3 @@ -import { ethErrors } from 'eth-rpc-errors'; import { MESSAGE_TYPE } from '../../../../../shared/constants/app'; const watchAsset = { @@ -36,15 +35,14 @@ async function watchAssetHandler( { handleWatchAssetRequest }, ) { try { - const { options: asset, type } = req.params; - const handleWatchAssetResult = await handleWatchAssetRequest(asset, type); - await handleWatchAssetResult.result; + const { + params: { options: asset, type }, + origin, + } = req; + await handleWatchAssetRequest(asset, type, origin); res.result = true; return end(); } catch (error) { - if (error.message === 'User rejected to watch the asset.') { - return end(ethErrors.provider.userRejectedRequest()); - } return end(error); } } diff --git a/app/scripts/metamask-controller.actions.test.js b/app/scripts/metamask-controller.actions.test.js index b23351233..85dd5fe6d 100644 --- a/app/scripts/metamask-controller.actions.test.js +++ b/app/scripts/metamask-controller.actions.test.js @@ -5,7 +5,6 @@ import proxyquire from 'proxyquire'; import { ApprovalRequestNotFoundError } from '@metamask/approval-controller'; import { PermissionsRequestNotFoundError } from '@metamask/permission-controller'; import nock from 'nock'; -import { ORIGIN_METAMASK } from '../../shared/constants/app'; const Ganache = require('../../test/e2e/ganache'); @@ -43,11 +42,26 @@ const createLoggerMiddlewareMock = () => (req, res, next) => { next(); }; +// Temporarily replace the snaps packages with the Flask versions. +const proxyPermissions = proxyquire('./controllers/permissions', { + './snaps/snap-permissions': proxyquire( + './controllers/permissions/snaps/snap-permissions', + { + // eslint-disable-next-line node/global-require + '@metamask/snaps-controllers': require('@metamask/snaps-controllers-flask'), + // eslint-disable-next-line node/global-require + '@metamask/rpc-methods': require('@metamask/rpc-methods-flask'), + }, + ), +}); + const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'; const MetaMaskController = proxyquire('./metamask-controller', { './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, + // Temporarily replace the snaps packages with the Flask versions. + './controllers/permissions': proxyPermissions, }).default; describe('MetaMaskController', function () { @@ -222,35 +236,6 @@ describe('MetaMaskController', function () { }); }); - describe('#updateTransactionSendFlowHistory', function () { - it('two sequential calls with same history give same result', async function () { - const recipientAddress = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'; - - await metamaskController.createNewVaultAndKeychain('test@123'); - const accounts = await metamaskController.keyringController.getAccounts(); - const txMeta = await metamaskController.getApi().addUnapprovedTransaction( - undefined, - { - from: accounts[0], - to: recipientAddress, - }, - ORIGIN_METAMASK, - ); - - const [transaction1, transaction2] = await Promise.all([ - metamaskController - .getApi() - .updateTransactionSendFlowHistory(txMeta.id, 2, ['foo1', 'foo2']), - Promise.resolve(1).then(() => - metamaskController - .getApi() - .updateTransactionSendFlowHistory(txMeta.id, 2, ['foo1', 'foo2']), - ), - ]); - assert.deepEqual(transaction1, transaction2); - }); - }); - describe('#removePermissionsFor', function () { it('should not propagate PermissionsRequestNotFoundError', function () { const error = new PermissionsRequestNotFoundError('123'); @@ -327,7 +312,7 @@ describe('MetaMaskController', function () { }); describe('#resolvePendingApproval', function () { - it('should not propagate ApprovalRequestNotFoundError', function () { + it('should not propagate ApprovalRequestNotFoundError', async function () { const error = new ApprovalRequestNotFoundError('123'); metamaskController.approvalController = { accept: () => { @@ -335,7 +320,10 @@ describe('MetaMaskController', function () { }, }; // Line below will not throw error, in case it throws this test case will fail. - metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'); + await metamaskController.resolvePendingApproval( + 'DUMMY_ID', + 'DUMMY_VALUE', + ); }); it('should propagate Error other than ApprovalRequestNotFoundError', function () { @@ -345,9 +333,11 @@ describe('MetaMaskController', function () { throw error; }, }; - assert.throws(() => { - metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'); - }, error); + assert.rejects( + () => + metamaskController.resolvePendingApproval('DUMMY_ID', 'DUMMY_VALUE'), + error, + ); }); }); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 892f0ec4d..22a1da813 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -29,13 +29,13 @@ import { } from '@metamask/approval-controller'; import { ControllerMessenger } from '@metamask/base-controller'; import { - CurrencyRateController, - TokenListController, - TokensController, - TokenRatesController, - NftController, AssetsContractController, + CurrencyRateController, + NftController, NftDetectionController, + TokenListController, + TokenRatesController, + TokensController, } from '@metamask/assets-controllers'; import { PhishingController } from '@metamask/phishing-controller'; import { AnnouncementController } from '@metamask/announcement-controller'; @@ -63,8 +63,34 @@ import { } from '@metamask/snaps-controllers'; ///: END:ONLY_INCLUDE_IN +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import { + CUSTODIAN_TYPES, + MmiConfigurationController, +} from '@metamask-institutional/custody-keyring'; +import { InstitutionalFeaturesController } from '@metamask-institutional/institutional-features'; +import { CustodyController } from '@metamask-institutional/custody-controller'; +import { handleMmiPortfolio } from '@metamask-institutional/portfolio-dashboard'; +import { TransactionUpdateController } from '@metamask-institutional/transaction-update'; +///: END:ONLY_INCLUDE_IN import { SignatureController } from '@metamask/signature-controller'; -import { ApprovalType } from '@metamask/controller-utils'; + +///: BEGIN:ONLY_INCLUDE_IN(desktop) +// eslint-disable-next-line import/order +import { DesktopController } from '@metamask/desktop/dist/controllers/desktop'; +///: END:ONLY_INCLUDE_IN + +import { + ApprovalType, + ERC1155, + ERC20, + ERC721, +} from '@metamask/controller-utils'; + +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; +///: END:ONLY_INCLUDE_IN + import { AssetType, TransactionStatus, @@ -116,15 +142,16 @@ import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; import { getTokenValueParam } from '../../shared/lib/metamask-controller-utils'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; import { hexToDecimal } from '../../shared/modules/conversion.utils'; -///: BEGIN:ONLY_INCLUDE_IN(desktop) -// eslint-disable-next-line import/order -import { DesktopController } from '@metamask/desktop/dist/controllers/desktop'; -///: END:ONLY_INCLUDE_IN import { ACTION_QUEUE_METRICS_E2E_TEST } from '../../shared/constants/test-flags'; + import { onMessageReceived, checkForMultipleVersionsRunning, } from './detect-multiple-instances'; +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import MMIController from './controllers/mmi-controller'; +import { mmiKeyringBuilderFactory } from './mmi-keyring-builder-factory'; +///: END:ONLY_INCLUDE_IN import ComposableObservableStore from './lib/ComposableObservableStore'; import AccountTracker from './lib/account-tracker'; import createDupeReqFilterMiddleware from './lib/createDupeReqFilterMiddleware'; @@ -264,6 +291,13 @@ export default class MetamaskController extends EventEmitter { ], }); + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.mmiConfigurationController = new MmiConfigurationController({ + initState: initState.MmiConfigurationController, + mmiConfigurationServiceUrl: process.env.MMI_CONFIGURATION_SERVICE_URL, + }); + ///: END:ONLY_INCLUDE_IN + const networkControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NetworkController', allowedEvents: [ @@ -273,6 +307,7 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:infuraIsUnblocked', ], }); + this.networkController = new NetworkController({ messenger: networkControllerMessenger, state: initState.NetworkController, @@ -288,27 +323,20 @@ export default class MetamaskController extends EventEmitter { const tokenListMessenger = this.controllerMessenger.getRestricted({ name: 'TokenListController', + allowedEvents: [ + 'TokenListController:stateChange', + 'NetworkController:stateChange', + ], }); this.tokenListController = new TokenListController({ - chainId: hexToDecimal( - this.networkController.store.getState().providerConfig.chainId, - ), + chainId: this.networkController.store.getState().providerConfig.chainId, preventPollingOnNetworkRestart: initState.TokenListController ? initState.TokenListController.preventPollingOnNetworkRestart : true, - onNetworkStateChange: (cb) => { - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: hexToDecimal(networkState.providerConfig.chainId), - }, - }; - return cb(modifiedNetworkState); - }); - }, + onNetworkStateChange: this.networkController.store.subscribe.bind( + this.networkController.store, + ), messenger: tokenListMessenger, state: initState.TokenListController, }); @@ -326,36 +354,31 @@ export default class MetamaskController extends EventEmitter { ), tokenListController: this.tokenListController, provider: this.provider, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + handleMmiDashboardData: this.handleMmiDashboardData.bind(this), + mmiConfigurationStore: this.mmiConfigurationController.store, + ///: END:ONLY_INCLUDE_IN }); this.tokensController = new TokensController({ + chainId: this.networkController.store.getState().providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: (cb) => - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: this.networkController.store.subscribe.bind( + this.networkController.store, + ), config: { provider: this.provider }, state: initState.TokensController, messenger: this.controllerMessenger.getRestricted({ name: 'TokensController', - allowedActions: [ - `${this.approvalController.name}:addRequest`, - `${this.approvalController.name}:acceptRequest`, - `${this.approvalController.name}:rejectRequest`, - ], + allowedActions: [`${this.approvalController.name}:addRequest`], }), }); this.assetsContractController = new AssetsContractController( { + chainId: this.networkController.store.getState().providerConfig.chainId, onPreferencesStateChange: (listener) => this.preferencesController.store.subscribe(listener), // This handler is misnamed, and is a known issue that will be resolved @@ -371,14 +394,7 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:networkDidChange', () => { const networkState = this.networkController.store.getState(); - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: hexToDecimal(networkState.providerConfig.chainId), - }, - }; - return cb(modifiedNetworkState); + return cb(networkState); }, ), }, @@ -388,23 +404,21 @@ export default class MetamaskController extends EventEmitter { initState.AssetsContractController, ); + const nftControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'NftController', + allowedActions: [`${this.approvalController.name}:addRequest`], + }); this.nftController = new NftController( { + messenger: nftControllerMessenger, + chainId: this.networkController.store.getState().providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: (cb) => - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: hexToDecimal(networkState.providerConfig.chainId), - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: this.networkController.store.subscribe.bind( + this.networkController.store, + ), getERC721AssetName: this.assetsContractController.getERC721AssetName.bind( this.assetsContractController, @@ -450,21 +464,14 @@ export default class MetamaskController extends EventEmitter { this.nftController.setApiKey(process.env.OPENSEA_KEY); this.nftDetectionController = new NftDetectionController({ + chainId: this.networkController.store.getState().providerConfig.chainId, onNftsStateChange: (listener) => this.nftController.subscribe(listener), onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: (cb) => - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: hexToDecimal(networkState.providerConfig.chainId), - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: this.networkController.store.subscribe.bind( + this.networkController.store, + ), getOpenSeaApiKey: () => this.nftController.openSeaApiKey, getBalancesInSingleCall: this.assetsContractController.getBalancesInSingleCall.bind( @@ -516,10 +523,12 @@ export default class MetamaskController extends EventEmitter { this.networkController.getProviderAndBlockTracker().provider, // NOTE: This option is inaccurately named; it should be called // onNetworkDidChange - onNetworkStateChange: networkControllerMessenger.subscribe.bind( - networkControllerMessenger, - 'NetworkController:networkDidChange', - ), + onNetworkStateChange: (eventHandler) => { + networkControllerMessenger.subscribe( + 'NetworkController:networkDidChange', + () => eventHandler(this.networkController.store.getState()), + ); + }, getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( this.networkController, @@ -533,11 +542,8 @@ export default class MetamaskController extends EventEmitter { this.networkController.store.getState().providerConfig; return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET; }, - getChainId: () => { - return process.env.IN_TEST - ? CHAIN_IDS.MAINNET - : this.networkController.store.getState().providerConfig.chainId; - }, + getChainId: () => + this.networkController.store.getState().providerConfig.chainId, }); this.qrHardwareKeyring = new QRHardwareKeyring(); @@ -595,6 +601,7 @@ export default class MetamaskController extends EventEmitter { // token exchange rate tracker this.tokenRatesController = new TokenRatesController( { + chainId: this.networkController.store.getState().providerConfig.chainId, onTokensStateChange: (listener) => this.tokensController.subscribe(listener), onCurrencyRateStateChange: (listener) => @@ -602,17 +609,9 @@ export default class MetamaskController extends EventEmitter { `${this.currencyRateController.name}:stateChange`, listener, ), - onNetworkStateChange: (cb) => - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - chainId: hexToDecimal(networkState.providerConfig.chainId), - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: this.networkController.store.subscribe.bind( + this.networkController.store, + ), }, { disabled: @@ -725,9 +724,21 @@ export default class MetamaskController extends EventEmitter { keyringOverrides?.lattice || LatticeKeyring, QRHardwareKeyring, ]; + additionalKeyrings = additionalKeyringTypes.map((keyringType) => keyringBuilderFactory(keyringType), ); + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + for (const custodianType of Object.keys(CUSTODIAN_TYPES)) { + additionalKeyrings.push( + mmiKeyringBuilderFactory( + CUSTODIAN_TYPES[custodianType].keyringClass, + { mmiConfigurationController: this.mmiConfigurationController }, + ), + ); + } + ///: END:ONLY_INCLUDE_IN } this.keyringController = new KeyringController({ @@ -892,29 +903,33 @@ export default class MetamaskController extends EventEmitter { name: 'RateLimitController', }), implementations: { - showNativeNotification: (origin, message) => { - const subjectMetadataState = this.controllerMessenger.call( - 'SubjectMetadataController:getState', - ); + showNativeNotification: { + method: (origin, message) => { + const subjectMetadataState = this.controllerMessenger.call( + 'SubjectMetadataController:getState', + ); - const originMetadata = subjectMetadataState.subjectMetadata[origin]; + const originMetadata = subjectMetadataState.subjectMetadata[origin]; - this.platform - ._showNotification(originMetadata?.name ?? origin, message) - .catch((error) => { - log.error('Failed to create notification', error); - }); + this.platform + ._showNotification(originMetadata?.name ?? origin, message) + .catch((error) => { + log.error('Failed to create notification', error); + }); - return null; + return null; + }, }, - showInAppNotification: (origin, message) => { - this.controllerMessenger.call( - 'NotificationController:show', - origin, - message, - ); + showInAppNotification: { + method: (origin, message) => { + this.controllerMessenger.call( + 'NotificationController:show', + origin, + message, + ); - return null; + return null; + }, }, }, }); @@ -984,6 +999,22 @@ export default class MetamaskController extends EventEmitter { preferencesStore: this.preferencesController.store, }); + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.custodyController = new CustodyController({ + initState: initState.CustodyController, + }); + this.institutionalFeaturesController = new InstitutionalFeaturesController({ + initState: initState.InstitutionalFeaturesController, + showConfirmRequest: opts.showUserConfirmation, + }); + this.transactionUpdateController = new TransactionUpdateController({ + initState: initState.TransactionUpdateController, + getCustodyKeyring: this.getCustodyKeyringIfExists.bind(this), + mmiConfigurationController: this.mmiConfigurationController, + captureException, + }); + ///: END:ONLY_INCLUDE_IN + this.backupController = new BackupController({ preferencesController: this.preferencesController, addressBookController: this.addressBookController, @@ -1054,6 +1085,9 @@ export default class MetamaskController extends EventEmitter { getDeviceModel: this.getDeviceModel.bind(this), getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), securityProviderRequest: this.securityProviderRequest.bind(this), + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + transactionUpdateController: this.transactionUpdateController, + ///: END:ONLY_INCLUDE_IN messenger: this.controllerMessenger.getRestricted({ name: 'TransactionController', allowedActions: [ @@ -1064,6 +1098,28 @@ export default class MetamaskController extends EventEmitter { }), }); + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.mmiController = new MMIController({ + mmiConfigurationController: this.mmiConfigurationController, + keyringController: this.keyringController, + txController: this.txController, + securityProviderRequest: this.securityProviderRequest.bind(this), + preferencesController: this.preferencesController, + appStateController: this.appStateController, + transactionUpdateController: this.transactionUpdateController, + custodyController: this.custodyController, + institutionalFeaturesController: this.institutionalFeaturesController, + getState: this.getState.bind(this), + getPendingNonce: this.getPendingNonce.bind(this), + accountTracker: this.accountTracker, + metaMetricsController: this.metaMetricsController, + networkController: this.networkController, + permissionController: this.permissionController, + platform: this.platform, + extension: this.extension, + }); + ///: END:ONLY_INCLUDE_IN + this.txController.on(`tx:status-update`, async (txId, status) => { if ( status === TransactionStatus.confirmed || @@ -1112,9 +1168,8 @@ export default class MetamaskController extends EventEmitter { getTokenValueParam(transactionData); const { allNfts } = this.nftController.state; - const chainIdAsDecimal = hexToDecimal(chainId); // check if its a known NFT - const knownNft = allNfts?.[userAddress]?.[chainIdAsDecimal]?.find( + const knownNft = allNfts?.[userAddress]?.[chainId]?.find( ({ address, tokenId }) => isEqualCaseInsensitive(address, contractAddress) && tokenId === transactionDataTokenId, @@ -1125,7 +1180,7 @@ export default class MetamaskController extends EventEmitter { this.nftController.checkAndUpdateSingleNftOwnershipStatus( knownNft, false, - { userAddress, chainId: chainIdAsDecimal }, + { userAddress, chainId }, ); } } @@ -1232,22 +1287,28 @@ export default class MetamaskController extends EventEmitter { }, ); - this.swapsController = new SwapsController({ - getBufferedGasLimit: this.txController.txGasUtil.getBufferedGasLimit.bind( - this.txController.txGasUtil, - ), - networkController: this.networkController, - onNetworkStateChange: (listener) => - this.networkController.store.subscribe(listener), - provider: this.provider, - getProviderConfig: () => - this.networkController.store.getState().providerConfig, - getTokenRatesState: () => this.tokenRatesController.state, - getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, - getEIP1559GasFeeEstimates: - this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController), - }); + this.swapsController = new SwapsController( + { + getBufferedGasLimit: + this.txController.txGasUtil.getBufferedGasLimit.bind( + this.txController.txGasUtil, + ), + networkController: this.networkController, + onNetworkStateChange: (listener) => + this.networkController.store.subscribe(listener), + provider: this.provider, + getProviderConfig: () => + this.networkController.store.getState().providerConfig, + getTokenRatesState: () => this.tokenRatesController.state, + getCurrentChainId: () => + this.networkController.store.getState().providerConfig.chainId, + getEIP1559GasFeeEstimates: + this.gasFeeController.fetchGasFeeEstimates.bind( + this.gasFeeController, + ), + }, + initState.SwapsController, + ); this.smartTransactionsController = new SmartTransactionsController( { onNetworkStateChange: (cb) => { @@ -1279,6 +1340,14 @@ export default class MetamaskController extends EventEmitter { initState.SmartTransactionsController, ); + this.txController.on('newSwapApproval', (txMeta) => { + this.swapsController.setApproveTxId(txMeta.id); + }); + + this.txController.on('newSwap', (txMeta) => { + this.swapsController.setTradeTxId(txMeta.id); + }); + // ensure accountTracker updates balances after network change networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', @@ -1431,6 +1500,13 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(desktop) DesktopController: this.desktopController.store, ///: END:ONLY_INCLUDE_IN + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + CustodyController: this.custodyController.store, + InstitutionalFeaturesController: + this.institutionalFeaturesController.store, + MmiConfigurationController: this.mmiConfigurationController.store, + ///: END:ONLY_INCLUDE_IN ...resetOnRestartStore, }); @@ -1467,6 +1543,13 @@ export default class MetamaskController extends EventEmitter { ///: BEGIN:ONLY_INCLUDE_IN(desktop) DesktopController: this.desktopController.store, ///: END:ONLY_INCLUDE_IN + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + CustodyController: this.custodyController.store, + InstitutionalFeaturesController: + this.institutionalFeaturesController.store, + MmiConfigurationController: this.mmiConfigurationController.store, + ///: END:ONLY_INCLUDE_IN ...resetOnRestartStore, }, controllerMessenger: this.controllerMessenger, @@ -2025,10 +2108,6 @@ export default class MetamaskController extends EventEmitter { preferencesController, ), addToken: tokensController.addToken.bind(tokensController), - rejectWatchAsset: - tokensController.rejectWatchAsset.bind(tokensController), - acceptWatchAsset: - tokensController.acceptWatchAsset.bind(tokensController), updateTokenType: tokensController.updateTokenType.bind(tokensController), setAccountLabel: preferencesController.setAccountLabel.bind( preferencesController, @@ -2111,6 +2190,12 @@ export default class MetamaskController extends EventEmitter { ), setTermsOfUseLastAgreed: appStateController.setTermsOfUseLastAgreed.bind(appStateController), + ///: BEGIN:ONLY_INCLUDE_IN(snaps) + setSnapsInstallPrivacyWarningShownStatus: + appStateController.setSnapsInstallPrivacyWarningShownStatus.bind( + appStateController, + ), + ///: END:ONLY_INCLUDE_IN setOutdatedBrowserWarningLastShown: appStateController.setOutdatedBrowserWarningLastShown.bind( appStateController, @@ -2139,10 +2224,7 @@ export default class MetamaskController extends EventEmitter { exportAccount: this.exportAccount.bind(this), // txController - cancelTransaction: txController.cancelTransaction.bind(txController), updateTransaction: txController.updateTransaction.bind(txController), - updateAndApproveTransaction: - txController.updateAndApproveTransaction.bind(txController), approveTransactionsWithSameNonce: txController.approveTransactionsWithSameNonce.bind(txController), createCancelTransaction: this.createCancelTransaction.bind(this), @@ -2162,11 +2244,6 @@ export default class MetamaskController extends EventEmitter { updateTransactionSendFlowHistory: txController.updateTransactionSendFlowHistory.bind(txController), - updateSwapApprovalTransaction: - txController.updateSwapApprovalTransaction.bind(txController), - updateSwapTransaction: - txController.updateSwapTransaction.bind(txController), - updatePreviousGasParams: txController.updatePreviousGasParams.bind(txController), @@ -2236,6 +2313,72 @@ export default class MetamaskController extends EventEmitter { rejectPermissionsRequest: this.rejectPermissionsRequest, ...getPermissionBackgroundApiMethods(permissionController), + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + connectCustodyAddresses: + this.mmiController.connectCustodyAddresses.bind(this), + getCustodianAccounts: this.mmiController.getCustodianAccounts.bind(this), + getCustodianAccountsByAddress: + this.mmiController.getCustodianAccountsByAddress.bind(this), + getCustodianTransactionDeepLink: + this.mmiController.getCustodianTransactionDeepLink.bind(this), + getCustodianConfirmDeepLink: + this.mmiController.getCustodianConfirmDeepLink.bind(this), + getCustodianSignMessageDeepLink: + this.mmiController.getCustodianSignMessageDeepLink.bind(this), + getCustodianToken: this.mmiController.getCustodianToken.bind(this), + getCustodianJWTList: this.mmiController.getCustodianJWTList.bind(this), + setWaitForConfirmDeepLinkDialog: + this.custodyController.setWaitForConfirmDeepLinkDialog.bind( + this.custodyController, + ), + setCustodianConnectRequest: + this.custodyController.setCustodianConnectRequest.bind( + this.custodyController, + ), + getCustodianConnectRequest: + this.custodyController.getCustodianConnectRequest.bind( + this.custodyController, + ), + getAllCustodianAccountsWithToken: + this.mmiController.getAllCustodianAccountsWithToken.bind(this), + getMmiConfiguration: + this.mmiConfigurationController.getConfiguration.bind( + this.mmiConfigurationController, + ), + setComplianceAuthData: + this.institutionalFeaturesController.setComplianceAuthData.bind( + this.institutionalFeaturesController, + ), + deleteComplianceAuthData: + this.institutionalFeaturesController.deleteComplianceAuthData.bind( + this.institutionalFeaturesController, + ), + generateComplianceReport: + this.institutionalFeaturesController.generateComplianceReport.bind( + this.institutionalFeaturesController, + ), + syncReportsInProgress: + this.institutionalFeaturesController.syncReportsInProgress.bind( + this.institutionalFeaturesController, + ), + + removeConnectInstitutionalFeature: + this.institutionalFeaturesController.removeConnectInstitutionalFeature.bind( + this.institutionalFeaturesController, + ), + + getComplianceHistoricalReportsByAddress: + this.institutionalFeaturesController.getComplianceHistoricalReportsByAddress.bind( + this.institutionalFeaturesController, + ), + removeAddTokenConnectRequest: + this.institutionalFeaturesController.removeAddTokenConnectRequest.bind( + this.institutionalFeaturesController, + ), + setCustodianNewRefreshToken: + this.mmiController.setCustodianNewRefreshToken.bind(this), + ///: END:ONLY_INCLUDE_IN + ///: BEGIN:ONLY_INCLUDE_IN(snaps) // snaps removeSnapError: this.controllerMessenger.call.bind( @@ -2666,6 +2809,10 @@ export default class MetamaskController extends EventEmitter { async submitPassword(password) { await this.keyringController.submitPassword(password); + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.mmiController.onSubmitPassword(); + ///: END:ONLY_INCLUDE_IN + try { await this.blockTracker.checkForLatestBlock(); } catch (error) { @@ -2785,6 +2932,16 @@ export default class MetamaskController extends EventEmitter { return keyring.mnemonic; } + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + async getCustodyKeyringIfExists(address) { + const custodyType = this.custodyController.getCustodyTypeByAddress( + toChecksumHexAddress(address), + ); + const keyring = this.keyringController.getKeyringsByType(custodyType)[0]; + return keyring?.getAccountDetails(address) ? keyring : undefined; + } + ///: END:ONLY_INCLUDE_IN + // // Hardware // @@ -3173,6 +3330,10 @@ export default class MetamaskController extends EventEmitter { // Remove account from the account tracker controller this.accountTracker.removeAccount([address]); + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + this.custodyController.removeAccount(address); + ///: END:ONLY_INCLUDE_IN(build-mmi) + const keyring = await this.keyringController.getKeyringForAccount(address); // Remove account from the keyring await this.keyringController.removeAccount(address); @@ -3293,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 //============================================================================= @@ -3672,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, @@ -3731,6 +3902,21 @@ export default class MetamaskController extends EventEmitter { this.alertController.setWeb3ShimUsageRecorded.bind( this.alertController, ), + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + handleMmiAuthenticate: + this.institutionalFeaturesController.handleMmiAuthenticate.bind( + this.institutionalFeaturesController, + ), + handleMmiCheckIfTokenIsPresent: + this.mmiController.handleMmiCheckIfTokenIsPresent.bind(this), + handleMmiDashboardData: this.handleMmiDashboardData.bind(this), + handleMmiOpenSwaps: this.mmiController.handleMmiOpenSwaps.bind(this), + handleMmiSetAccountAndNetwork: + this.mmiController.setAccountAndNetwork.bind(this), + handleMmiOpenAddHardwareWallet: + this.mmiController.handleMmiOpenAddHardwareWallet.bind(this), + ///: END:ONLY_INCLUDE_IN }), ); @@ -3783,6 +3969,37 @@ export default class MetamaskController extends EventEmitter { return engine; } + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + /** + * This method is needed in preferences controller + * so it needs to be here and not in our controller because + * preferences controllers is initiated first + */ + async handleMmiDashboardData() { + await this.appStateController.getUnlockPromise(true); + const keyringAccounts = await this.keyringController.getAccounts(); + const { identities } = this.preferencesController.store.getState(); + const { metaMetricsId } = this.metaMetricsController.store.getState(); + const getAccountDetails = (address) => + this.custodyController.getAccountDetails(address); + const extensionId = this.extension.runtime.id; + const networks = [ + ...this.preferencesController.getRpcMethodPreferences(), + { chainId: CHAIN_IDS.MAINNET }, + { chainId: CHAIN_IDS.GOERLI }, + ]; + + return handleMmiPortfolio({ + keyringAccounts, + identities, + metaMetricsId, + networks, + getAccountDetails, + extensionId, + }); + } + ///: END:ONLY_INCLUDE_IN + /** * TODO:LegacyProvider: Delete * A method for providing our public config info over a stream. @@ -4270,9 +4487,9 @@ export default class MetamaskController extends EventEmitter { } }; - resolvePendingApproval = (id, value) => { + resolvePendingApproval = async (id, value, options) => { try { - this.approvalController.accept(id, value); + await this.approvalController.accept(id, value, options); } catch (exp) { if (!(exp instanceof ApprovalRequestNotFoundError)) { throw exp; diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index f5cf0ddaa..8f013c48c 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -31,7 +31,9 @@ const browserPolyfillMock = { getPlatformInfo: async () => 'mac', }, storage: { - session: {}, + session: { + set: () => undefined, + }, }, }; @@ -72,6 +74,19 @@ function MockEthContract() { }; } +// Temporarily replace the snaps packages with the Flask versions. +const proxyPermissions = proxyquire('./controllers/permissions', { + './snaps/snap-permissions': proxyquire( + './controllers/permissions/snaps/snap-permissions', + { + // eslint-disable-next-line node/global-require + '@metamask/snaps-controllers': require('@metamask/snaps-controllers-flask'), + // eslint-disable-next-line node/global-require + '@metamask/rpc-methods': require('@metamask/rpc-methods-flask'), + }, + ), +}); + // TODO, Feb 24, 2023: // ethjs-contract is being added to proxyquire, but we might want to discontinue proxyquire // this is for expediency as we resolve a bug for v10.26.0. The proper solution here would have @@ -80,10 +95,14 @@ function MockEthContract() { const MetaMaskController = proxyquire('./metamask-controller', { './lib/createLoggerMiddleware': { default: createLoggerMiddlewareMock }, 'ethjs-contract': MockEthContract, + // Temporarily replace the snaps packages with the Flask versions. + './controllers/permissions': proxyPermissions, }).default; const MetaMaskControllerMV3 = proxyquire('./metamask-controller', { '../../shared/modules/mv3.utils': { isManifestV3: true }, + // Temporarily replace the snaps packages with the Flask versions. + './controllers/permissions': proxyPermissions, }).default; const currentNetworkId = '5'; @@ -169,19 +188,13 @@ const firstTimeState = { }, }; +const noop = () => undefined; + describe('MetaMaskController', function () { - let metamaskController; - const sandbox = sinon.createSandbox(); - const noop = () => undefined; - - browserPolyfillMock.storage.session.set = sandbox.spy(); before(async function () { - globalThis.isFirstTimeProfileLoaded = true; await ganacheServer.start(); - sinon.spy(MetaMaskController.prototype, 'resetStates'); - sinon.spy(MetaMaskControllerMV3.prototype, 'resetStates'); }); beforeEach(function () { @@ -215,37 +228,7 @@ describe('MetaMaskController', function () { sendMessage: sandbox.stub().rejects(), }); - metamaskController = new MetaMaskController({ - showUserConfirmation: noop, - encryptor: { - encrypt(_, object) { - this.object = object; - return Promise.resolve('mock-encrypted'); - }, - decrypt() { - return Promise.resolve(this.object); - }, - }, - initState: cloneDeep(firstTimeState), - initLangCode: 'en_US', - platform: { - showTransactionNotification: () => undefined, - getVersion: () => 'foo', - }, - browser: browserPolyfillMock, - infuraProjectId: 'foo', - isFirstMetaMaskControllerSetup: true, - }); - - // add sinon method spies - sandbox.spy( - metamaskController.keyringController, - 'createNewVaultAndKeychain', - ); - sandbox.spy( - metamaskController.keyringController, - 'createNewVaultAndRestore', - ); + browserPolyfillMock.storage.session.set = sandbox.spy(); }); afterEach(function () { @@ -257,14 +240,1377 @@ describe('MetaMaskController', function () { await ganacheServer.quit(); }); - describe('should reset states on first time profile load', function () { - it('in mv2, it should reset state without attempting to call browser storage', function () { - assert.equal(metamaskController.resetStates.callCount, 1); - assert.equal(browserPolyfillMock.storage.session.set.callCount, 0); + describe('MetaMaskController Behaviour', function () { + let metamaskController; + + beforeEach(function () { + sandbox.spy(MetaMaskController.prototype, 'resetStates'); + + metamaskController = new MetaMaskController({ + showUserConfirmation: noop, + encryptor: { + encrypt(_, object) { + this.object = object; + return Promise.resolve('mock-encrypted'); + }, + decrypt() { + return Promise.resolve(this.object); + }, + }, + initState: cloneDeep(firstTimeState), + initLangCode: 'en_US', + platform: { + showTransactionNotification: () => undefined, + getVersion: () => 'foo', + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + isFirstMetaMaskControllerSetup: true, + }); + + // add sinon method spies + sandbox.spy( + metamaskController.keyringController, + 'createNewVaultAndKeychain', + ); + sandbox.spy( + metamaskController.keyringController, + 'createNewVaultAndRestore', + ); }); - it('in mv3, it should reset state', function () { - MetaMaskControllerMV3.prototype.resetStates.resetHistory(); + describe('should reset states on first time profile load', function () { + it('in mv2, it should reset state without attempting to call browser storage', function () { + assert.equal(metamaskController.resetStates.callCount, 1); + assert.equal(browserPolyfillMock.storage.session.set.callCount, 0); + }); + }); + + describe('#importAccountWithStrategy', function () { + const importPrivkey = + '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; + + beforeEach(async function () { + const password = 'a-fake-password'; + await metamaskController.createNewVaultAndRestore(password, TEST_SEED); + await metamaskController.importAccountWithStrategy('Private Key', [ + importPrivkey, + ]); + }); + + it('adds private key to keyrings in KeyringController', async function () { + const simpleKeyrings = + metamaskController.keyringController.getKeyringsByType( + KeyringType.imported, + ); + const pubAddressHexArr = await simpleKeyrings[0].getAccounts(); + const privKeyHex = await simpleKeyrings[0].exportAccount( + pubAddressHexArr[0], + ); + assert.equal(privKeyHex, importPrivkey); + assert.equal( + pubAddressHexArr[0], + '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + ); + }); + + it('adds 1 account', async function () { + const keyringAccounts = + await metamaskController.keyringController.getAccounts(); + assert.equal( + keyringAccounts[keyringAccounts.length - 1], + '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', + ); + }); + }); + + describe('submitPassword', function () { + it('removes any identities that do not correspond to known accounts.', async function () { + const password = 'password'; + await metamaskController.createNewVaultAndKeychain(password); + + const fakeAddress = '0xbad0'; + metamaskController.preferencesController.addAddresses([fakeAddress]); + await metamaskController.submitPassword(password); + + const identities = Object.keys( + metamaskController.preferencesController.store.getState().identities, + ); + const addresses = + await metamaskController.keyringController.getAccounts(); + + identities.forEach((identity) => { + assert.ok( + addresses.includes(identity), + `addresses should include all IDs: ${identity}`, + ); + }); + + addresses.forEach((address) => { + assert.ok( + identities.includes(address), + `identities should include all Addresses: ${address}`, + ); + }); + }); + }); + + describe('#createNewVaultAndKeychain', function () { + it('can only create new vault on keyringController once', async function () { + const selectStub = sandbox.stub( + metamaskController, + 'selectFirstIdentity', + ); + + const password = 'a-fake-password'; + + await metamaskController.createNewVaultAndKeychain(password); + await metamaskController.createNewVaultAndKeychain(password); + + assert( + metamaskController.keyringController.createNewVaultAndKeychain + .calledOnce, + ); + + selectStub.reset(); + }); + }); + + describe('#createNewVaultAndRestore', function () { + it('should be able to call newVaultAndRestore despite a mistake.', async function () { + const password = 'what-what-what'; + sandbox.stub(metamaskController, 'getBalance'); + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0'); + }); + + await metamaskController + .createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)) + .catch(() => null); + await metamaskController.createNewVaultAndRestore(password, TEST_SEED); + + assert( + metamaskController.keyringController.createNewVaultAndRestore + .calledTwice, + ); + }); + + it('should clear previous identities after vault restoration', async function () { + sandbox.stub(metamaskController, 'getBalance'); + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0'); + }); + + let startTime = Date.now(); + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED, + ); + let endTime = Date.now(); + + const firstVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ); + assert.ok( + firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime && + firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime, + `'${firstVaultIdentities[TEST_ADDRESS].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, + ); + delete firstVaultIdentities[TEST_ADDRESS].lastSelected; + assert.deepEqual(firstVaultIdentities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, + }); + + await metamaskController.preferencesController.setAccountLabel( + TEST_ADDRESS, + 'Account Foo', + ); + + const labelledFirstVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ); + delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected; + assert.deepEqual(labelledFirstVaultIdentities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, + }); + + startTime = Date.now(); + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED_ALT, + ); + endTime = Date.now(); + + const secondVaultIdentities = cloneDeep( + metamaskController.getState().identities, + ); + assert.ok( + secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime && + secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime, + `'${secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, + ); + delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected; + assert.deepEqual(secondVaultIdentities, { + [TEST_ADDRESS_ALT]: { + address: TEST_ADDRESS_ALT, + name: DEFAULT_LABEL, + }, + }); + }); + + it('should restore any consecutive accounts with balances without extra zero balance accounts', async function () { + sandbox.stub(metamaskController, 'getBalance'); + metamaskController.getBalance.withArgs(TEST_ADDRESS).callsFake(() => { + return Promise.resolve('0x14ced5122ce0a000'); + }); + metamaskController.getBalance.withArgs(TEST_ADDRESS_2).callsFake(() => { + return Promise.resolve('0x0'); + }); + metamaskController.getBalance.withArgs(TEST_ADDRESS_3).callsFake(() => { + return Promise.resolve('0x14ced5122ce0a000'); + }); + + const startTime = Date.now(); + await metamaskController.createNewVaultAndRestore( + 'foobar1337', + TEST_SEED, + ); + + const identities = cloneDeep(metamaskController.getState().identities); + assert.ok( + identities[TEST_ADDRESS].lastSelected >= startTime && + identities[TEST_ADDRESS].lastSelected <= Date.now(), + ); + delete identities[TEST_ADDRESS].lastSelected; + assert.deepEqual(identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, + }); + }); + }); + + describe('#getBalance', function () { + it('should return the balance known by accountTracker', async function () { + const accounts = {}; + const balance = '0x14ced5122ce0a000'; + accounts[TEST_ADDRESS] = { balance }; + + metamaskController.accountTracker.store.putState({ accounts }); + + const gotten = await metamaskController.getBalance(TEST_ADDRESS); + + assert.equal(balance, gotten); + }); + + it('should ask the network for a balance when not known by accountTracker', async function () { + const accounts = {}; + const balance = '0x14ced5122ce0a000'; + const ethQuery = new EthQuery(); + sinon.stub(ethQuery, 'getBalance').callsFake((_, callback) => { + callback(undefined, balance); + }); + + metamaskController.accountTracker.store.putState({ accounts }); + + const gotten = await metamaskController.getBalance( + TEST_ADDRESS, + ethQuery, + ); + + assert.equal(balance, gotten); + }); + }); + + describe('#getApi', function () { + it('getState', function () { + const getApi = metamaskController.getApi(); + const state = getApi.getState(); + assert.deepEqual(state, metamaskController.getState()); + }); + }); + + describe('#selectFirstIdentity', function () { + let identities, address; + + beforeEach(function () { + address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'; + identities = { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + address, + name: 'Account 1', + }, + '0xc42edfcc21ed14dda456aa0756c153f7985d8813': { + address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', + name: 'Account 2', + }, + }; + metamaskController.preferencesController.store.updateState({ + identities, + }); + metamaskController.selectFirstIdentity(); + }); + + it('changes preferences controller select address', function () { + const preferenceControllerState = + metamaskController.preferencesController.store.getState(); + assert.equal(preferenceControllerState.selectedAddress, address); + }); + + it('changes metamask controller selected address', function () { + const metamaskState = metamaskController.getState(); + assert.equal(metamaskState.selectedAddress, address); + }); + }); + + describe('connectHardware', function () { + it('should throw if it receives an unknown device name', async function () { + try { + await metamaskController.connectHardware( + 'Some random device name', + 0, + `m/44/0'/0'`, + ); + } catch (e) { + assert.equal( + e.message, + 'MetamaskController:getKeyringForDevice - Unknown device', + ); + } + }); + + it('should add the Trezor Hardware keyring', async function () { + sinon.spy(metamaskController.keyringController, 'addNewKeyring'); + await metamaskController + .connectHardware(HardwareDeviceNames.trezor, 0) + .catch(() => null); + const keyrings = + await metamaskController.keyringController.getKeyringsByType( + KeyringType.trezor, + ); + assert.deepEqual( + metamaskController.keyringController.addNewKeyring.getCall(0).args, + [KeyringType.trezor], + ); + assert.equal(keyrings.length, 1); + }); + + it('should add the Ledger Hardware keyring', async function () { + sinon.spy(metamaskController.keyringController, 'addNewKeyring'); + await metamaskController + .connectHardware(HardwareDeviceNames.ledger, 0) + .catch(() => null); + const keyrings = + await metamaskController.keyringController.getKeyringsByType( + KeyringType.ledger, + ); + assert.deepEqual( + metamaskController.keyringController.addNewKeyring.getCall(0).args, + [KeyringType.ledger], + ); + assert.equal(keyrings.length, 1); + }); + }); + + describe('getPrimaryKeyringMnemonic', function () { + it('should return a mnemonic as a Uint8Array', function () { + const mockMnemonic = + 'above mercy benefit hospital call oval domain student sphere interest argue shock'; + const mnemonicIndices = mockMnemonic + .split(' ') + .map((word) => englishWordlist.indexOf(word)); + const uint8ArrayMnemonic = new Uint8Array( + new Uint16Array(mnemonicIndices).buffer, + ); + + const mockHDKeyring = { + type: 'HD Key Tree', + mnemonic: uint8ArrayMnemonic, + }; + sinon + .stub(metamaskController.keyringController, 'getKeyringsByType') + .returns([mockHDKeyring]); + + const recoveredMnemonic = + metamaskController.getPrimaryKeyringMnemonic(); + + assert.equal(recoveredMnemonic, uint8ArrayMnemonic); + }); + }); + + describe('checkHardwareStatus', function () { + it('should throw if it receives an unknown device name', async function () { + try { + await metamaskController.checkHardwareStatus( + 'Some random device name', + `m/44/0'/0'`, + ); + } catch (e) { + assert.equal( + e.message, + 'MetamaskController:getKeyringForDevice - Unknown device', + ); + } + }); + + it('should be locked by default', async function () { + await metamaskController + .connectHardware(HardwareDeviceNames.trezor, 0) + .catch(() => null); + const status = await metamaskController.checkHardwareStatus( + HardwareDeviceNames.trezor, + ); + assert.equal(status, false); + }); + }); + + describe('forgetDevice', function () { + it('should throw if it receives an unknown device name', async function () { + try { + await metamaskController.forgetDevice('Some random device name'); + } catch (e) { + assert.equal( + e.message, + 'MetamaskController:getKeyringForDevice - Unknown device', + ); + } + }); + + it('should wipe all the keyring info', async function () { + await metamaskController + .connectHardware(HardwareDeviceNames.trezor, 0) + .catch(() => null); + await metamaskController.forgetDevice(HardwareDeviceNames.trezor); + const keyrings = + await metamaskController.keyringController.getKeyringsByType( + KeyringType.trezor, + ); + + assert.deepEqual(keyrings[0].accounts, []); + assert.deepEqual(keyrings[0].page, 0); + assert.deepEqual(keyrings[0].isUnlocked(), false); + }); + }); + + describe('unlockHardwareWalletAccount', function () { + let accountToUnlock; + let windowOpenStub; + let addNewAccountStub; + let getAccountsStub; + beforeEach(async function () { + accountToUnlock = 10; + windowOpenStub = sinon.stub(window, 'open'); + windowOpenStub.returns(noop); + + addNewAccountStub = sinon.stub( + metamaskController.keyringController, + 'addNewAccount', + ); + addNewAccountStub.returns({}); + + getAccountsStub = sinon.stub( + metamaskController.keyringController, + 'getAccounts', + ); + // Need to return different address to mock the behavior of + // adding a new account from the keyring + getAccountsStub.onCall(0).returns(Promise.resolve(['0x1'])); + getAccountsStub.onCall(1).returns(Promise.resolve(['0x2'])); + getAccountsStub.onCall(2).returns(Promise.resolve(['0x3'])); + getAccountsStub.onCall(3).returns(Promise.resolve(['0x4'])); + sinon.spy(metamaskController.preferencesController, 'setAddresses'); + sinon.spy( + metamaskController.preferencesController, + 'setSelectedAddress', + ); + sinon.spy(metamaskController.preferencesController, 'setAccountLabel'); + await metamaskController + .connectHardware(HardwareDeviceNames.trezor, 0, `m/44'/1'/0'/0`) + .catch(() => null); + await metamaskController.unlockHardwareWalletAccount( + accountToUnlock, + HardwareDeviceNames.trezor, + `m/44'/1'/0'/0`, + ); + }); + + afterEach(function () { + window.open.restore(); + metamaskController.keyringController.addNewAccount.restore(); + metamaskController.keyringController.getAccounts.restore(); + metamaskController.preferencesController.setAddresses.restore(); + metamaskController.preferencesController.setSelectedAddress.restore(); + metamaskController.preferencesController.setAccountLabel.restore(); + }); + + it('should set unlockedAccount in the keyring', async function () { + const keyrings = + await metamaskController.keyringController.getKeyringsByType( + KeyringType.trezor, + ); + assert.equal(keyrings[0].unlockedAccount, accountToUnlock); + }); + + it('should call keyringController.addNewAccount', async function () { + assert(metamaskController.keyringController.addNewAccount.calledOnce); + }); + + it('should call keyringController.getAccounts ', async function () { + assert(metamaskController.keyringController.getAccounts.called); + }); + + it('should call preferencesController.setAddresses', async function () { + assert( + metamaskController.preferencesController.setAddresses.calledOnce, + ); + }); + + it('should call preferencesController.setSelectedAddress', async function () { + assert( + metamaskController.preferencesController.setSelectedAddress + .calledOnce, + ); + }); + + it('should call preferencesController.setAccountLabel', async function () { + assert( + metamaskController.preferencesController.setAccountLabel.calledOnce, + ); + }); + }); + + describe('#addNewAccount', function () { + it('errors when an primary keyring is does not exist', async function () { + const addNewAccount = metamaskController.addNewAccount(); + + try { + await addNewAccount; + assert.fail('should throw'); + } catch (e) { + assert.equal(e.message, 'MetamaskController - No HD Key Tree found'); + } + }); + }); + + describe('#verifyseedPhrase', function () { + it('errors when no keying is provided', async function () { + try { + await metamaskController.verifySeedPhrase(); + } catch (error) { + assert.equal( + error.message, + 'MetamaskController - No HD Key Tree found', + ); + } + }); + + it('#addNewAccount', async function () { + await metamaskController.createNewVaultAndKeychain('password'); + await metamaskController.addNewAccount(1); + const getAccounts = + await metamaskController.keyringController.getAccounts(); + assert.equal(getAccounts.length, 2); + }); + }); + + describe('#resetAccount', function () { + it('wipes transactions from only the correct network id and with the selected address', async function () { + const selectedAddressStub = sinon.stub( + metamaskController.preferencesController, + 'getSelectedAddress', + ); + const getNetworkIdStub = sinon.stub( + metamaskController.txController.txStateManager, + 'getNetworkId', + ); + + selectedAddressStub.returns( + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + ); + getNetworkIdStub.returns(42); + + metamaskController.txController.txStateManager._addTransactionsToState([ + createTxMeta({ + id: 1, + status: TransactionStatus.unapproved, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, + }), + createTxMeta({ + id: 1, + status: TransactionStatus.unapproved, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, + }), + createTxMeta({ + id: 2, + status: TransactionStatus.rejected, + metamaskNetworkId: '32', + }), + createTxMeta({ + id: 3, + status: TransactionStatus.submitted, + metamaskNetworkId: currentNetworkId, + txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' }, + }), + ]); + + await metamaskController.resetAccount(); + assert.equal( + metamaskController.txController.txStateManager.getTransaction(1), + undefined, + ); + }); + }); + + describe('#removeAccount', function () { + let ret; + const addressToRemove = '0x1'; + let mockKeyring; + + beforeEach(async function () { + mockKeyring = { + getAccounts: sinon.stub().returns(Promise.resolve([])), + destroy: sinon.stub(), + }; + sinon.stub(metamaskController.preferencesController, 'removeAddress'); + sinon.stub(metamaskController.accountTracker, 'removeAccount'); + sinon.stub(metamaskController.keyringController, 'removeAccount'); + sinon.stub(metamaskController, 'removeAllAccountPermissions'); + sinon + .stub(metamaskController.keyringController, 'getKeyringForAccount') + .returns(Promise.resolve(mockKeyring)); + + ret = await metamaskController.removeAccount(addressToRemove); + }); + + afterEach(function () { + metamaskController.keyringController.removeAccount.restore(); + metamaskController.accountTracker.removeAccount.restore(); + metamaskController.preferencesController.removeAddress.restore(); + metamaskController.removeAllAccountPermissions.restore(); + + mockKeyring.getAccounts.resetHistory(); + mockKeyring.destroy.resetHistory(); + }); + + it('should call preferencesController.removeAddress', async function () { + assert( + metamaskController.preferencesController.removeAddress.calledWith( + addressToRemove, + ), + ); + }); + it('should call accountTracker.removeAccount', async function () { + assert( + metamaskController.accountTracker.removeAccount.calledWith([ + addressToRemove, + ]), + ); + }); + it('should call keyringController.removeAccount', async function () { + assert( + metamaskController.keyringController.removeAccount.calledWith( + addressToRemove, + ), + ); + }); + it('should call metamaskController.removeAllAccountPermissions', async function () { + assert( + metamaskController.removeAllAccountPermissions.calledWith( + addressToRemove, + ), + ); + }); + it('should return address', async function () { + assert.equal(ret, '0x1'); + }); + it('should call keyringController.getKeyringForAccount', async function () { + assert( + metamaskController.keyringController.getKeyringForAccount.calledWith( + addressToRemove, + ), + ); + }); + it('should call keyring.destroy', async function () { + assert(mockKeyring.destroy.calledOnce); + }); + }); + + describe('#setupUntrustedCommunication', function () { + const mockTxParams = { from: TEST_ADDRESS }; + + beforeEach(function () { + initializeMockMiddlewareLog(); + }); + + after(function () { + tearDownMockMiddlewareLog(); + }); + + it('sets up phishing stream for untrusted communication', async function () { + const phishingMessageSender = { + url: 'http://myethereumwalletntw.com', + tab: {}, + }; + + const { promise, resolve } = deferredPromise(); + const streamTest = createThoughStream((chunk, _, cb) => { + if (chunk.name !== 'phishing') { + cb(); + return; + } + assert.equal( + chunk.data.hostname, + new URL(phishingMessageSender.url).hostname, + ); + resolve(); + cb(); + }); + + metamaskController.setupUntrustedCommunication({ + connectionStream: streamTest, + sender: phishingMessageSender, + }); + await promise; + streamTest.end(); + }); + + it('adds a tabId and origin to requests', function (done) { + const messageSender = { + url: 'http://mycrypto.com', + tab: { id: 456 }, + }; + const streamTest = createThoughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunication({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + id: 1999133338649204, + jsonrpc: '2.0', + params: [{ ...mockTxParams }], + method: 'eth_sendTransaction', + }; + streamTest.write( + { + name: 'metamask-provider', + data: message, + }, + null, + () => { + setTimeout(() => { + assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { + ...message, + origin: 'http://mycrypto.com', + tabId: 456, + }); + done(); + }); + }, + ); + }); + + it('should add only origin to request if tabId not provided', function (done) { + const messageSender = { + url: 'http://mycrypto.com', + }; + const streamTest = createThoughStream((chunk, _, cb) => { + if (chunk.data && chunk.data.method) { + cb(null, chunk); + return; + } + cb(); + }); + + metamaskController.setupUntrustedCommunication({ + connectionStream: streamTest, + sender: messageSender, + }); + + const message = { + id: 1999133338649204, + jsonrpc: '2.0', + params: [{ ...mockTxParams }], + method: 'eth_sendTransaction', + }; + streamTest.write( + { + name: 'metamask-provider', + data: message, + }, + null, + () => { + setTimeout(() => { + assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { + ...message, + origin: 'http://mycrypto.com', + }); + done(); + }); + }, + ); + }); + }); + + describe('#setupTrustedCommunication', function () { + it('sets up controller JSON-RPC api for trusted communication', async function () { + const messageSender = { + url: 'http://mycrypto.com', + tab: {}, + }; + const { promise, resolve } = deferredPromise(); + const streamTest = createThoughStream((chunk, _, cb) => { + assert.equal(chunk.name, 'controller'); + resolve(); + cb(); + }); + + metamaskController.setupTrustedCommunication(streamTest, messageSender); + await promise; + streamTest.end(); + }); + }); + + describe('#markPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to true', function () { + metamaskController.markPasswordForgotten(noop); + const state = metamaskController.getState(); + assert.equal(state.forgottenPassword, true); + }); + }); + + describe('#unMarkPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to false', function () { + metamaskController.unMarkPasswordForgotten(noop); + const state = metamaskController.getState(); + assert.equal(state.forgottenPassword, false); + }); + }); + + describe('#_onKeyringControllerUpdate', function () { + it('should do nothing if there are no keyrings in state', async function () { + const syncAddresses = sinon.fake(); + const syncWithAddresses = sinon.fake(); + sandbox.replace(metamaskController, 'preferencesController', { + syncAddresses, + }); + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }); + + const oldState = metamaskController.getState(); + await metamaskController._onKeyringControllerUpdate({ keyrings: [] }); + + assert.ok(syncAddresses.notCalled); + assert.ok(syncWithAddresses.notCalled); + assert.deepEqual(metamaskController.getState(), oldState); + }); + + it('should sync addresses if there are keyrings in state', async function () { + const syncAddresses = sinon.fake(); + const syncWithAddresses = sinon.fake(); + sandbox.replace(metamaskController, 'preferencesController', { + syncAddresses, + }); + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }); + + const oldState = metamaskController.getState(); + await metamaskController._onKeyringControllerUpdate({ + keyrings: [ + { + accounts: ['0x1', '0x2'], + }, + ], + }); + + assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]); + assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]); + assert.deepEqual(metamaskController.getState(), oldState); + }); + + it('should NOT update selected address if already unlocked', async function () { + const syncAddresses = sinon.fake(); + const syncWithAddresses = sinon.fake(); + sandbox.replace(metamaskController, 'preferencesController', { + syncAddresses, + }); + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }); + + const oldState = metamaskController.getState(); + await metamaskController._onKeyringControllerUpdate({ + isUnlocked: true, + keyrings: [ + { + accounts: ['0x1', '0x2'], + }, + ], + }); + + assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]); + assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]); + assert.deepEqual(metamaskController.getState(), oldState); + }); + }); + + describe('markNotificationsAsRead', function () { + it('marks the notification as read', function () { + metamaskController.markNotificationsAsRead([NOTIFICATION_ID]); + const readNotification = + metamaskController.getState().notifications[NOTIFICATION_ID]; + assert.notEqual(readNotification.readDate, null); + }); + }); + + describe('dismissNotifications', function () { + it('deletes the notification from state', function () { + metamaskController.dismissNotifications([NOTIFICATION_ID]); + const state = metamaskController.getState().notifications; + assert.ok( + !Object.values(state).includes(NOTIFICATION_ID), + 'Object should not include the deleted notification', + ); + }); + }); + + describe('getTokenStandardAndDetails', function () { + it('gets token data from the token list if available, and with a balance retrieved by fetchTokenBalance', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + const tokenData = { + decimals: 18, + symbol: 'DAI', + }; + + metamaskController.tokenListController.update(() => { + return { + tokenList: { + '0x6b175474e89094c44da98b954eedeac495271d0f': tokenData, + }, + }; + }); + + metamaskController.provider = provider; + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0x6B175474E89094C44Da98b954EedeAC495271d0F', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + + assert.ok( + tokenDetails.standard === 'ERC20', + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === String(tokenData.decimals), + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === tokenData.symbol, + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === '3000000000000000000', + 'tokenDetails should include a balance', + ); + }); + + it('gets token data from tokens if available, and with a balance retrieved by fetchTokenBalance', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + const tokenData = { + decimals: 18, + symbol: 'DAI', + }; + + metamaskController.tokensController.update({ + tokens: [ + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + ...tokenData, + }, + ], + }); + + metamaskController.provider = provider; + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0x6B175474E89094C44Da98b954EedeAC495271d0F', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + + assert.ok( + tokenDetails.standard === 'ERC20', + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === String(tokenData.decimals), + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === tokenData.symbol, + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === '3000000000000000000', + 'tokenDetails should include a balance', + ); + }); + + it('gets token data from contract-metadata if available, and with a balance retrieved by fetchTokenBalance', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + metamaskController.provider = provider; + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0x6B175474E89094C44Da98b954EedeAC495271d0F', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + + assert.ok( + tokenDetails.standard === 'ERC20', + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === '18', + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === 'DAI', + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === '3000000000000000000', + 'tokenDetails should include a balance', + ); + }); + + it('gets token data from the blockchain, via the assetsContractController, if not available through other sources', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + const tokenData = { + standard: 'ERC20', + decimals: 18, + symbol: 'DAI', + balance: '333', + }; + + metamaskController.tokenListController.update(() => { + return { + tokenList: { + '0x6b175474e89094c44da98b954eedeac495271d0f': {}, + }, + }; + }); + + metamaskController.provider = provider; + + sandbox + .stub( + metamaskController.assetsContractController, + 'getTokenStandardAndDetails', + ) + .callsFake(() => { + return tokenData; + }); + + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0xNotInTokenList', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + assert.ok( + tokenDetails.standard === tokenData.standard.toUpperCase(), + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === String(tokenData.decimals), + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === tokenData.symbol, + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === tokenData.balance, + 'tokenDetails should include a balance', + ); + }); + + it('gets token data from the blockchain, via the assetsContractController, if it is in the token list but is an ERC721', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + const tokenData = { + standard: 'ERC721', + decimals: 18, + symbol: 'DAI', + balance: '333', + }; + + metamaskController.tokenListController.update(() => { + return { + tokenList: { + '0xaaa75474e89094c44da98b954eedeac495271d0f': tokenData, + }, + }; + }); + + metamaskController.provider = provider; + + sandbox + .stub( + metamaskController.assetsContractController, + 'getTokenStandardAndDetails', + ) + .callsFake(() => { + return tokenData; + }); + + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0xAAA75474e89094c44da98b954eedeac495271d0f', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + assert.ok( + tokenDetails.standard === tokenData.standard.toUpperCase(), + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === String(tokenData.decimals), + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === tokenData.symbol, + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === tokenData.balance, + 'tokenDetails should include a balance', + ); + }); + + it('gets token data from the blockchain, via the assetsContractController, if it is in the token list but is an ERC1155', async function () { + const providerResultStub = { + eth_getCode: '0x123', + eth_call: + '0x00000000000000000000000000000000000000000000000029a2241af62c0000', + }; + const { provider } = createTestProviderTools({ + scaffold: providerResultStub, + networkId: '5', + chainId: '5', + }); + + const tokenData = { + standard: 'ERC1155', + decimals: 18, + symbol: 'DAI', + balance: '333', + }; + + metamaskController.tokenListController.update(() => { + return { + tokenList: { + '0xaaa75474e89094c44da98b954eedeac495271d0f': tokenData, + }, + }; + }); + + metamaskController.provider = provider; + + sandbox + .stub( + metamaskController.assetsContractController, + 'getTokenStandardAndDetails', + ) + .callsFake(() => { + return tokenData; + }); + + const tokenDetails = + await metamaskController.getTokenStandardAndDetails( + '0xAAA75474e89094c44da98b954eedeac495271d0f', + '0xf0d172594caedee459b89ad44c94098e474571b6', + ); + assert.ok( + tokenDetails.standard === tokenData.standard.toUpperCase(), + 'tokenDetails should include token standard in upper case', + ); + assert.ok( + tokenDetails.decimals === String(tokenData.decimals), + 'tokenDetails should include token decimals as a string', + ); + assert.ok( + tokenDetails.symbol === tokenData.symbol, + 'tokenDetails should include token symbol', + ); + assert.ok( + tokenDetails.balance === tokenData.balance, + 'tokenDetails should include a balance', + ); + }); + + describe('findNetworkConfigurationBy', function () { + it('returns null if passed an object containing a valid networkConfiguration key but no matching value is found', function () { + assert.strictEqual( + metamaskController.findNetworkConfigurationBy({ + chainId: '0xnone', + }), + null, + ); + }); + it('returns null if passed an object containing an invalid networkConfiguration key', function () { + assert.strictEqual( + metamaskController.findNetworkConfigurationBy({ + invalidKey: '0xnone', + }), + null, + ); + }); + + it('returns matching networkConfiguration when passed a chainId that matches an existing configuration', function () { + assert.deepStrictEqual( + metamaskController.findNetworkConfigurationBy({ + chainId: MAINNET_CHAIN_ID, + }), + { + chainId: MAINNET_CHAIN_ID, + nickname: 'Alt Mainnet', + id: NETWORK_CONFIGURATION_ID_1, + rpcUrl: ALT_MAINNET_RPC_URL, + ticker: ETH, + type: NETWORK_TYPES.RPC, + }, + ); + }); + + it('returns matching networkConfiguration when passed a ticker that matches an existing configuration', function () { + assert.deepStrictEqual( + metamaskController.findNetworkConfigurationBy({ + ticker: MATIC, + }), + { + rpcUrl: POLYGON_RPC_URL, + type: NETWORK_TYPES.RPC, + chainId: POLYGON_CHAIN_ID, + ticker: MATIC, + nickname: 'Polygon', + id: NETWORK_CONFIGURATION_ID_2, + }, + ); + }); + + it('returns matching networkConfiguration when passed a nickname that matches an existing configuration', function () { + assert.deepStrictEqual( + metamaskController.findNetworkConfigurationBy({ + nickname: 'Alt Mainnet', + }), + { + chainId: MAINNET_CHAIN_ID, + nickname: 'Alt Mainnet', + id: NETWORK_CONFIGURATION_ID_1, + rpcUrl: ALT_MAINNET_RPC_URL, + ticker: ETH, + type: NETWORK_TYPES.RPC, + }, + ); + }); + + it('returns null if passed an object containing mismatched networkConfiguration key/value combination', function () { + assert.deepStrictEqual( + metamaskController.findNetworkConfigurationBy({ + nickname: MAINNET_CHAIN_ID, + }), + null, + ); + }); + + it('returns the first networkConfiguration added if passed an key/value combination for which there are multiple matching configurations', function () { + assert.deepStrictEqual( + metamaskController.findNetworkConfigurationBy({ + chainId: POLYGON_CHAIN_ID, + }), + { + rpcUrl: POLYGON_RPC_URL, + type: NETWORK_TYPES.RPC, + chainId: POLYGON_CHAIN_ID, + ticker: MATIC, + nickname: 'Polygon', + id: NETWORK_CONFIGURATION_ID_2, + }, + ); + }); + }); + }); + }); + + describe('MV3 Specific behaviour', function () { + before(async function () { + globalThis.isFirstTimeProfileLoaded = true; + }); + + beforeEach(async function () { + sandbox.spy(MetaMaskControllerMV3.prototype, 'resetStates'); + }); + + it('it should reset state', function () { + browserPolyfillMock.storage.session.set.resetHistory(); + const metamaskControllerMV3 = new MetaMaskControllerMV3({ showUserConfirmation: noop, encryptor: { @@ -297,8 +1643,8 @@ describe('MetaMaskController', function () { }); it('in mv3, it should not reset states if isFirstMetaMaskControllerSetup is false', function () { - MetaMaskControllerMV3.prototype.resetStates.resetHistory(); browserPolyfillMock.storage.session.set.resetHistory(); + const metamaskControllerMV3 = new MetaMaskControllerMV3({ showUserConfirmation: noop, encryptor: { @@ -324,1298 +1670,4 @@ describe('MetaMaskController', function () { assert.equal(browserPolyfillMock.storage.session.set.callCount, 0); }); }); - - describe('#importAccountWithStrategy', function () { - const importPrivkey = - '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; - - beforeEach(async function () { - const password = 'a-fake-password'; - await metamaskController.createNewVaultAndRestore(password, TEST_SEED); - await metamaskController.importAccountWithStrategy('Private Key', [ - importPrivkey, - ]); - }); - - it('adds private key to keyrings in KeyringController', async function () { - const simpleKeyrings = - metamaskController.keyringController.getKeyringsByType( - KeyringType.imported, - ); - const pubAddressHexArr = await simpleKeyrings[0].getAccounts(); - const privKeyHex = await simpleKeyrings[0].exportAccount( - pubAddressHexArr[0], - ); - assert.equal(privKeyHex, importPrivkey); - assert.equal( - pubAddressHexArr[0], - '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - ); - }); - - it('adds 1 account', async function () { - const keyringAccounts = - await metamaskController.keyringController.getAccounts(); - assert.equal( - keyringAccounts[keyringAccounts.length - 1], - '0xe18035bf8712672935fdb4e5e431b1a0183d2dfc', - ); - }); - }); - - describe('submitPassword', function () { - it('removes any identities that do not correspond to known accounts.', async function () { - const password = 'password'; - await metamaskController.createNewVaultAndKeychain(password); - - const fakeAddress = '0xbad0'; - metamaskController.preferencesController.addAddresses([fakeAddress]); - await metamaskController.submitPassword(password); - - const identities = Object.keys( - metamaskController.preferencesController.store.getState().identities, - ); - const addresses = - await metamaskController.keyringController.getAccounts(); - - identities.forEach((identity) => { - assert.ok( - addresses.includes(identity), - `addresses should include all IDs: ${identity}`, - ); - }); - - addresses.forEach((address) => { - assert.ok( - identities.includes(address), - `identities should include all Addresses: ${address}`, - ); - }); - }); - }); - - describe('#createNewVaultAndKeychain', function () { - it('can only create new vault on keyringController once', async function () { - const selectStub = sandbox.stub( - metamaskController, - 'selectFirstIdentity', - ); - - const password = 'a-fake-password'; - - await metamaskController.createNewVaultAndKeychain(password); - await metamaskController.createNewVaultAndKeychain(password); - - assert( - metamaskController.keyringController.createNewVaultAndKeychain - .calledOnce, - ); - - selectStub.reset(); - }); - }); - - describe('#createNewVaultAndRestore', function () { - it('should be able to call newVaultAndRestore despite a mistake.', async function () { - const password = 'what-what-what'; - sandbox.stub(metamaskController, 'getBalance'); - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0'); - }); - - await metamaskController - .createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)) - .catch(() => null); - await metamaskController.createNewVaultAndRestore(password, TEST_SEED); - - assert( - metamaskController.keyringController.createNewVaultAndRestore - .calledTwice, - ); - }); - - it('should clear previous identities after vault restoration', async function () { - sandbox.stub(metamaskController, 'getBalance'); - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0'); - }); - - let startTime = Date.now(); - await metamaskController.createNewVaultAndRestore( - 'foobar1337', - TEST_SEED, - ); - let endTime = Date.now(); - - const firstVaultIdentities = cloneDeep( - metamaskController.getState().identities, - ); - assert.ok( - firstVaultIdentities[TEST_ADDRESS].lastSelected >= startTime && - firstVaultIdentities[TEST_ADDRESS].lastSelected <= endTime, - `'${firstVaultIdentities[TEST_ADDRESS].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, - ); - delete firstVaultIdentities[TEST_ADDRESS].lastSelected; - assert.deepEqual(firstVaultIdentities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - }); - - await metamaskController.preferencesController.setAccountLabel( - TEST_ADDRESS, - 'Account Foo', - ); - - const labelledFirstVaultIdentities = cloneDeep( - metamaskController.getState().identities, - ); - delete labelledFirstVaultIdentities[TEST_ADDRESS].lastSelected; - assert.deepEqual(labelledFirstVaultIdentities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, - }); - - startTime = Date.now(); - await metamaskController.createNewVaultAndRestore( - 'foobar1337', - TEST_SEED_ALT, - ); - endTime = Date.now(); - - const secondVaultIdentities = cloneDeep( - metamaskController.getState().identities, - ); - assert.ok( - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected >= startTime && - secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected <= endTime, - `'${secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected}' expected to be between '${startTime}' and '${endTime}'`, - ); - delete secondVaultIdentities[TEST_ADDRESS_ALT].lastSelected; - assert.deepEqual(secondVaultIdentities, { - [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, - }); - }); - - it('should restore any consecutive accounts with balances without extra zero balance accounts', async function () { - sandbox.stub(metamaskController, 'getBalance'); - metamaskController.getBalance.withArgs(TEST_ADDRESS).callsFake(() => { - return Promise.resolve('0x14ced5122ce0a000'); - }); - metamaskController.getBalance.withArgs(TEST_ADDRESS_2).callsFake(() => { - return Promise.resolve('0x0'); - }); - metamaskController.getBalance.withArgs(TEST_ADDRESS_3).callsFake(() => { - return Promise.resolve('0x14ced5122ce0a000'); - }); - - const startTime = Date.now(); - await metamaskController.createNewVaultAndRestore( - 'foobar1337', - TEST_SEED, - ); - - const identities = cloneDeep(metamaskController.getState().identities); - assert.ok( - identities[TEST_ADDRESS].lastSelected >= startTime && - identities[TEST_ADDRESS].lastSelected <= Date.now(), - ); - delete identities[TEST_ADDRESS].lastSelected; - assert.deepEqual(identities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - }); - }); - }); - - describe('#getBalance', function () { - it('should return the balance known by accountTracker', async function () { - const accounts = {}; - const balance = '0x14ced5122ce0a000'; - accounts[TEST_ADDRESS] = { balance }; - - metamaskController.accountTracker.store.putState({ accounts }); - - const gotten = await metamaskController.getBalance(TEST_ADDRESS); - - assert.equal(balance, gotten); - }); - - it('should ask the network for a balance when not known by accountTracker', async function () { - const accounts = {}; - const balance = '0x14ced5122ce0a000'; - const ethQuery = new EthQuery(); - sinon.stub(ethQuery, 'getBalance').callsFake((_, callback) => { - callback(undefined, balance); - }); - - metamaskController.accountTracker.store.putState({ accounts }); - - const gotten = await metamaskController.getBalance( - TEST_ADDRESS, - ethQuery, - ); - - assert.equal(balance, gotten); - }); - }); - - describe('#getApi', function () { - it('getState', function () { - const getApi = metamaskController.getApi(); - const state = getApi.getState(); - assert.deepEqual(state, metamaskController.getState()); - }); - }); - - describe('#selectFirstIdentity', function () { - let identities, address; - - beforeEach(function () { - address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'; - identities = { - '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { - address, - name: 'Account 1', - }, - '0xc42edfcc21ed14dda456aa0756c153f7985d8813': { - address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', - name: 'Account 2', - }, - }; - metamaskController.preferencesController.store.updateState({ - identities, - }); - metamaskController.selectFirstIdentity(); - }); - - it('changes preferences controller select address', function () { - const preferenceControllerState = - metamaskController.preferencesController.store.getState(); - assert.equal(preferenceControllerState.selectedAddress, address); - }); - - it('changes metamask controller selected address', function () { - const metamaskState = metamaskController.getState(); - assert.equal(metamaskState.selectedAddress, address); - }); - }); - - describe('connectHardware', function () { - it('should throw if it receives an unknown device name', async function () { - try { - await metamaskController.connectHardware( - 'Some random device name', - 0, - `m/44/0'/0'`, - ); - } catch (e) { - assert.equal( - e.message, - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - } - }); - - it('should add the Trezor Hardware keyring', async function () { - sinon.spy(metamaskController.keyringController, 'addNewKeyring'); - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, - ); - assert.deepEqual( - metamaskController.keyringController.addNewKeyring.getCall(0).args, - [KeyringType.trezor], - ); - assert.equal(keyrings.length, 1); - }); - - it('should add the Ledger Hardware keyring', async function () { - sinon.spy(metamaskController.keyringController, 'addNewKeyring'); - await metamaskController - .connectHardware(HardwareDeviceNames.ledger, 0) - .catch(() => null); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.ledger, - ); - assert.deepEqual( - metamaskController.keyringController.addNewKeyring.getCall(0).args, - [KeyringType.ledger], - ); - assert.equal(keyrings.length, 1); - }); - }); - - describe('getPrimaryKeyringMnemonic', function () { - it('should return a mnemonic as a Uint8Array', function () { - const mockMnemonic = - 'above mercy benefit hospital call oval domain student sphere interest argue shock'; - const mnemonicIndices = mockMnemonic - .split(' ') - .map((word) => englishWordlist.indexOf(word)); - const uint8ArrayMnemonic = new Uint8Array( - new Uint16Array(mnemonicIndices).buffer, - ); - - const mockHDKeyring = { - type: 'HD Key Tree', - mnemonic: uint8ArrayMnemonic, - }; - sinon - .stub(metamaskController.keyringController, 'getKeyringsByType') - .returns([mockHDKeyring]); - - const recoveredMnemonic = metamaskController.getPrimaryKeyringMnemonic(); - - assert.equal(recoveredMnemonic, uint8ArrayMnemonic); - }); - }); - - describe('checkHardwareStatus', function () { - it('should throw if it receives an unknown device name', async function () { - try { - await metamaskController.checkHardwareStatus( - 'Some random device name', - `m/44/0'/0'`, - ); - } catch (e) { - assert.equal( - e.message, - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - } - }); - - it('should be locked by default', async function () { - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - const status = await metamaskController.checkHardwareStatus( - HardwareDeviceNames.trezor, - ); - assert.equal(status, false); - }); - }); - - describe('forgetDevice', function () { - it('should throw if it receives an unknown device name', async function () { - try { - await metamaskController.forgetDevice('Some random device name'); - } catch (e) { - assert.equal( - e.message, - 'MetamaskController:getKeyringForDevice - Unknown device', - ); - } - }); - - it('should wipe all the keyring info', async function () { - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0) - .catch(() => null); - await metamaskController.forgetDevice(HardwareDeviceNames.trezor); - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, - ); - - assert.deepEqual(keyrings[0].accounts, []); - assert.deepEqual(keyrings[0].page, 0); - assert.deepEqual(keyrings[0].isUnlocked(), false); - }); - }); - - describe('unlockHardwareWalletAccount', function () { - let accountToUnlock; - let windowOpenStub; - let addNewAccountStub; - let getAccountsStub; - beforeEach(async function () { - accountToUnlock = 10; - windowOpenStub = sinon.stub(window, 'open'); - windowOpenStub.returns(noop); - - addNewAccountStub = sinon.stub( - metamaskController.keyringController, - 'addNewAccount', - ); - addNewAccountStub.returns({}); - - getAccountsStub = sinon.stub( - metamaskController.keyringController, - 'getAccounts', - ); - // Need to return different address to mock the behavior of - // adding a new account from the keyring - getAccountsStub.onCall(0).returns(Promise.resolve(['0x1'])); - getAccountsStub.onCall(1).returns(Promise.resolve(['0x2'])); - getAccountsStub.onCall(2).returns(Promise.resolve(['0x3'])); - getAccountsStub.onCall(3).returns(Promise.resolve(['0x4'])); - sinon.spy(metamaskController.preferencesController, 'setAddresses'); - sinon.spy(metamaskController.preferencesController, 'setSelectedAddress'); - sinon.spy(metamaskController.preferencesController, 'setAccountLabel'); - await metamaskController - .connectHardware(HardwareDeviceNames.trezor, 0, `m/44'/1'/0'/0`) - .catch(() => null); - await metamaskController.unlockHardwareWalletAccount( - accountToUnlock, - HardwareDeviceNames.trezor, - `m/44'/1'/0'/0`, - ); - }); - - afterEach(function () { - window.open.restore(); - metamaskController.keyringController.addNewAccount.restore(); - metamaskController.keyringController.getAccounts.restore(); - metamaskController.preferencesController.setAddresses.restore(); - metamaskController.preferencesController.setSelectedAddress.restore(); - metamaskController.preferencesController.setAccountLabel.restore(); - }); - - it('should set unlockedAccount in the keyring', async function () { - const keyrings = - await metamaskController.keyringController.getKeyringsByType( - KeyringType.trezor, - ); - assert.equal(keyrings[0].unlockedAccount, accountToUnlock); - }); - - it('should call keyringController.addNewAccount', async function () { - assert(metamaskController.keyringController.addNewAccount.calledOnce); - }); - - it('should call keyringController.getAccounts ', async function () { - assert(metamaskController.keyringController.getAccounts.called); - }); - - it('should call preferencesController.setAddresses', async function () { - assert(metamaskController.preferencesController.setAddresses.calledOnce); - }); - - it('should call preferencesController.setSelectedAddress', async function () { - assert( - metamaskController.preferencesController.setSelectedAddress.calledOnce, - ); - }); - - it('should call preferencesController.setAccountLabel', async function () { - assert( - metamaskController.preferencesController.setAccountLabel.calledOnce, - ); - }); - }); - - describe('#addNewAccount', function () { - it('errors when an primary keyring is does not exist', async function () { - const addNewAccount = metamaskController.addNewAccount(); - - try { - await addNewAccount; - assert.fail('should throw'); - } catch (e) { - assert.equal(e.message, 'MetamaskController - No HD Key Tree found'); - } - }); - }); - - describe('#verifyseedPhrase', function () { - it('errors when no keying is provided', async function () { - try { - await metamaskController.verifySeedPhrase(); - } catch (error) { - assert.equal( - error.message, - 'MetamaskController - No HD Key Tree found', - ); - } - }); - - it('#addNewAccount', async function () { - await metamaskController.createNewVaultAndKeychain('password'); - await metamaskController.addNewAccount(1); - const getAccounts = - await metamaskController.keyringController.getAccounts(); - assert.equal(getAccounts.length, 2); - }); - }); - - describe('#resetAccount', function () { - it('wipes transactions from only the correct network id and with the selected address', async function () { - const selectedAddressStub = sinon.stub( - metamaskController.preferencesController, - 'getSelectedAddress', - ); - const getNetworkIdStub = sinon.stub( - metamaskController.txController.txStateManager, - 'getNetworkId', - ); - - selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'); - getNetworkIdStub.returns(42); - - metamaskController.txController.txStateManager._addTransactionsToState([ - createTxMeta({ - id: 1, - status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, - txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, - }), - createTxMeta({ - id: 1, - status: TransactionStatus.unapproved, - metamaskNetworkId: currentNetworkId, - txParams: { from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' }, - }), - createTxMeta({ - id: 2, - status: TransactionStatus.rejected, - metamaskNetworkId: '32', - }), - createTxMeta({ - id: 3, - status: TransactionStatus.submitted, - metamaskNetworkId: currentNetworkId, - txParams: { from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4' }, - }), - ]); - - await metamaskController.resetAccount(); - assert.equal( - metamaskController.txController.txStateManager.getTransaction(1), - undefined, - ); - }); - }); - - describe('#removeAccount', function () { - let ret; - const addressToRemove = '0x1'; - let mockKeyring; - - beforeEach(async function () { - mockKeyring = { - getAccounts: sinon.stub().returns(Promise.resolve([])), - destroy: sinon.stub(), - }; - sinon.stub(metamaskController.preferencesController, 'removeAddress'); - sinon.stub(metamaskController.accountTracker, 'removeAccount'); - sinon.stub(metamaskController.keyringController, 'removeAccount'); - sinon.stub(metamaskController, 'removeAllAccountPermissions'); - sinon - .stub(metamaskController.keyringController, 'getKeyringForAccount') - .returns(Promise.resolve(mockKeyring)); - - ret = await metamaskController.removeAccount(addressToRemove); - }); - - afterEach(function () { - metamaskController.keyringController.removeAccount.restore(); - metamaskController.accountTracker.removeAccount.restore(); - metamaskController.preferencesController.removeAddress.restore(); - metamaskController.removeAllAccountPermissions.restore(); - - mockKeyring.getAccounts.resetHistory(); - mockKeyring.destroy.resetHistory(); - }); - - it('should call preferencesController.removeAddress', async function () { - assert( - metamaskController.preferencesController.removeAddress.calledWith( - addressToRemove, - ), - ); - }); - it('should call accountTracker.removeAccount', async function () { - assert( - metamaskController.accountTracker.removeAccount.calledWith([ - addressToRemove, - ]), - ); - }); - it('should call keyringController.removeAccount', async function () { - assert( - metamaskController.keyringController.removeAccount.calledWith( - addressToRemove, - ), - ); - }); - it('should call metamaskController.removeAllAccountPermissions', async function () { - assert( - metamaskController.removeAllAccountPermissions.calledWith( - addressToRemove, - ), - ); - }); - it('should return address', async function () { - assert.equal(ret, '0x1'); - }); - it('should call keyringController.getKeyringForAccount', async function () { - assert( - metamaskController.keyringController.getKeyringForAccount.calledWith( - addressToRemove, - ), - ); - }); - it('should call keyring.destroy', async function () { - assert(mockKeyring.destroy.calledOnce); - }); - }); - - describe('#setupUntrustedCommunication', function () { - const mockTxParams = { from: TEST_ADDRESS }; - - beforeEach(function () { - initializeMockMiddlewareLog(); - }); - - after(function () { - tearDownMockMiddlewareLog(); - }); - - it('sets up phishing stream for untrusted communication', async function () { - const phishingMessageSender = { - url: 'http://myethereumwalletntw.com', - tab: {}, - }; - - const { promise, resolve } = deferredPromise(); - const streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.name !== 'phishing') { - cb(); - return; - } - assert.equal( - chunk.data.hostname, - new URL(phishingMessageSender.url).hostname, - ); - resolve(); - cb(); - }); - - metamaskController.setupUntrustedCommunication({ - connectionStream: streamTest, - sender: phishingMessageSender, - }); - await promise; - streamTest.end(); - }); - - it('adds a tabId and origin to requests', function (done) { - const messageSender = { - url: 'http://mycrypto.com', - tab: { id: 456 }, - }; - const streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.data && chunk.data.method) { - cb(null, chunk); - return; - } - cb(); - }); - - metamaskController.setupUntrustedCommunication({ - connectionStream: streamTest, - sender: messageSender, - }); - - const message = { - id: 1999133338649204, - jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', - }; - streamTest.write( - { - name: 'metamask-provider', - data: message, - }, - null, - () => { - setTimeout(() => { - assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { - ...message, - origin: 'http://mycrypto.com', - tabId: 456, - }); - done(); - }); - }, - ); - }); - - it('should add only origin to request if tabId not provided', function (done) { - const messageSender = { - url: 'http://mycrypto.com', - }; - const streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.data && chunk.data.method) { - cb(null, chunk); - return; - } - cb(); - }); - - metamaskController.setupUntrustedCommunication({ - connectionStream: streamTest, - sender: messageSender, - }); - - const message = { - id: 1999133338649204, - jsonrpc: '2.0', - params: [{ ...mockTxParams }], - method: 'eth_sendTransaction', - }; - streamTest.write( - { - name: 'metamask-provider', - data: message, - }, - null, - () => { - setTimeout(() => { - assert.deepStrictEqual(loggerMiddlewareMock.requests[0], { - ...message, - origin: 'http://mycrypto.com', - }); - done(); - }); - }, - ); - }); - }); - - describe('#setupTrustedCommunication', function () { - it('sets up controller JSON-RPC api for trusted communication', async function () { - const messageSender = { - url: 'http://mycrypto.com', - tab: {}, - }; - const { promise, resolve } = deferredPromise(); - const streamTest = createThoughStream((chunk, _, cb) => { - assert.equal(chunk.name, 'controller'); - resolve(); - cb(); - }); - - metamaskController.setupTrustedCommunication(streamTest, messageSender); - await promise; - streamTest.end(); - }); - }); - - describe('#markPasswordForgotten', function () { - it('adds and sets forgottenPassword to config data to true', function () { - metamaskController.markPasswordForgotten(noop); - const state = metamaskController.getState(); - assert.equal(state.forgottenPassword, true); - }); - }); - - describe('#unMarkPasswordForgotten', function () { - it('adds and sets forgottenPassword to config data to false', function () { - metamaskController.unMarkPasswordForgotten(noop); - const state = metamaskController.getState(); - assert.equal(state.forgottenPassword, false); - }); - }); - - describe('#_onKeyringControllerUpdate', function () { - it('should do nothing if there are no keyrings in state', async function () { - const syncAddresses = sinon.fake(); - const syncWithAddresses = sinon.fake(); - sandbox.replace(metamaskController, 'preferencesController', { - syncAddresses, - }); - sandbox.replace(metamaskController, 'accountTracker', { - syncWithAddresses, - }); - - const oldState = metamaskController.getState(); - await metamaskController._onKeyringControllerUpdate({ keyrings: [] }); - - assert.ok(syncAddresses.notCalled); - assert.ok(syncWithAddresses.notCalled); - assert.deepEqual(metamaskController.getState(), oldState); - }); - - it('should sync addresses if there are keyrings in state', async function () { - const syncAddresses = sinon.fake(); - const syncWithAddresses = sinon.fake(); - sandbox.replace(metamaskController, 'preferencesController', { - syncAddresses, - }); - sandbox.replace(metamaskController, 'accountTracker', { - syncWithAddresses, - }); - - const oldState = metamaskController.getState(); - await metamaskController._onKeyringControllerUpdate({ - keyrings: [ - { - accounts: ['0x1', '0x2'], - }, - ], - }); - - assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]); - assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]); - assert.deepEqual(metamaskController.getState(), oldState); - }); - - it('should NOT update selected address if already unlocked', async function () { - const syncAddresses = sinon.fake(); - const syncWithAddresses = sinon.fake(); - sandbox.replace(metamaskController, 'preferencesController', { - syncAddresses, - }); - sandbox.replace(metamaskController, 'accountTracker', { - syncWithAddresses, - }); - - const oldState = metamaskController.getState(); - await metamaskController._onKeyringControllerUpdate({ - isUnlocked: true, - keyrings: [ - { - accounts: ['0x1', '0x2'], - }, - ], - }); - - assert.deepEqual(syncAddresses.args, [[['0x1', '0x2']]]); - assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]); - assert.deepEqual(metamaskController.getState(), oldState); - }); - }); - - describe('markNotificationsAsRead', function () { - it('marks the notification as read', function () { - metamaskController.markNotificationsAsRead([NOTIFICATION_ID]); - const readNotification = - metamaskController.getState().notifications[NOTIFICATION_ID]; - assert.notEqual(readNotification.readDate, null); - }); - }); - - describe('dismissNotifications', function () { - it('deletes the notification from state', function () { - metamaskController.dismissNotifications([NOTIFICATION_ID]); - const state = metamaskController.getState().notifications; - assert.ok( - !Object.values(state).includes(NOTIFICATION_ID), - 'Object should not include the deleted notification', - ); - }); - }); - - describe('getTokenStandardAndDetails', function () { - it('gets token data from the token list if available, and with a balance retrieved by fetchTokenBalance', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - const tokenData = { - decimals: 18, - symbol: 'DAI', - }; - - metamaskController.tokenListController.update(() => { - return { - tokenList: { - '0x6b175474e89094c44da98b954eedeac495271d0f': tokenData, - }, - }; - }); - - metamaskController.provider = provider; - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0x6B175474E89094C44Da98b954EedeAC495271d0F', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - - assert.ok( - tokenDetails.standard === 'ERC20', - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === String(tokenData.decimals), - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === tokenData.symbol, - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === '3000000000000000000', - 'tokenDetails should include a balance', - ); - }); - - it('gets token data from tokens if available, and with a balance retrieved by fetchTokenBalance', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - const tokenData = { - decimals: 18, - symbol: 'DAI', - }; - - metamaskController.tokensController.update({ - tokens: [ - { - address: '0x6b175474e89094c44da98b954eedeac495271d0f', - ...tokenData, - }, - ], - }); - - metamaskController.provider = provider; - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0x6B175474E89094C44Da98b954EedeAC495271d0F', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - - assert.ok( - tokenDetails.standard === 'ERC20', - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === String(tokenData.decimals), - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === tokenData.symbol, - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === '3000000000000000000', - 'tokenDetails should include a balance', - ); - }); - - it('gets token data from contract-metadata if available, and with a balance retrieved by fetchTokenBalance', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - metamaskController.provider = provider; - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0x6B175474E89094C44Da98b954EedeAC495271d0F', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - - assert.ok( - tokenDetails.standard === 'ERC20', - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === '18', - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === 'DAI', - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === '3000000000000000000', - 'tokenDetails should include a balance', - ); - }); - - it('gets token data from the blockchain, via the assetsContractController, if not available through other sources', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - const tokenData = { - standard: 'ERC20', - decimals: 18, - symbol: 'DAI', - balance: '333', - }; - - metamaskController.tokenListController.update(() => { - return { - tokenList: { - '0x6b175474e89094c44da98b954eedeac495271d0f': {}, - }, - }; - }); - - metamaskController.provider = provider; - - sandbox - .stub( - metamaskController.assetsContractController, - 'getTokenStandardAndDetails', - ) - .callsFake(() => { - return tokenData; - }); - - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0xNotInTokenList', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - assert.ok( - tokenDetails.standard === tokenData.standard.toUpperCase(), - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === String(tokenData.decimals), - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === tokenData.symbol, - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === tokenData.balance, - 'tokenDetails should include a balance', - ); - }); - - it('gets token data from the blockchain, via the assetsContractController, if it is in the token list but is an ERC721', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - const tokenData = { - standard: 'ERC721', - decimals: 18, - symbol: 'DAI', - balance: '333', - }; - - metamaskController.tokenListController.update(() => { - return { - tokenList: { - '0xaaa75474e89094c44da98b954eedeac495271d0f': tokenData, - }, - }; - }); - - metamaskController.provider = provider; - - sandbox - .stub( - metamaskController.assetsContractController, - 'getTokenStandardAndDetails', - ) - .callsFake(() => { - return tokenData; - }); - - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0xAAA75474e89094c44da98b954eedeac495271d0f', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - assert.ok( - tokenDetails.standard === tokenData.standard.toUpperCase(), - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === String(tokenData.decimals), - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === tokenData.symbol, - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === tokenData.balance, - 'tokenDetails should include a balance', - ); - }); - - it('gets token data from the blockchain, via the assetsContractController, if it is in the token list but is an ERC1155', async function () { - const providerResultStub = { - eth_getCode: '0x123', - eth_call: - '0x00000000000000000000000000000000000000000000000029a2241af62c0000', - }; - const { provider } = createTestProviderTools({ - scaffold: providerResultStub, - networkId: '5', - chainId: '5', - }); - - const tokenData = { - standard: 'ERC1155', - decimals: 18, - symbol: 'DAI', - balance: '333', - }; - - metamaskController.tokenListController.update(() => { - return { - tokenList: { - '0xaaa75474e89094c44da98b954eedeac495271d0f': tokenData, - }, - }; - }); - - metamaskController.provider = provider; - - sandbox - .stub( - metamaskController.assetsContractController, - 'getTokenStandardAndDetails', - ) - .callsFake(() => { - return tokenData; - }); - - const tokenDetails = await metamaskController.getTokenStandardAndDetails( - '0xAAA75474e89094c44da98b954eedeac495271d0f', - '0xf0d172594caedee459b89ad44c94098e474571b6', - ); - assert.ok( - tokenDetails.standard === tokenData.standard.toUpperCase(), - 'tokenDetails should include token standard in upper case', - ); - assert.ok( - tokenDetails.decimals === String(tokenData.decimals), - 'tokenDetails should include token decimals as a string', - ); - assert.ok( - tokenDetails.symbol === tokenData.symbol, - 'tokenDetails should include token symbol', - ); - assert.ok( - tokenDetails.balance === tokenData.balance, - 'tokenDetails should include a balance', - ); - }); - - describe('findNetworkConfigurationBy', function () { - it('returns null if passed an object containing a valid networkConfiguration key but no matching value is found', function () { - assert.strictEqual( - metamaskController.findNetworkConfigurationBy({ - chainId: '0xnone', - }), - null, - ); - }); - it('returns null if passed an object containing an invalid networkConfiguration key', function () { - assert.strictEqual( - metamaskController.findNetworkConfigurationBy({ - invalidKey: '0xnone', - }), - null, - ); - }); - - it('returns matching networkConfiguration when passed a chainId that matches an existing configuration', function () { - assert.deepStrictEqual( - metamaskController.findNetworkConfigurationBy({ - chainId: MAINNET_CHAIN_ID, - }), - { - chainId: MAINNET_CHAIN_ID, - nickname: 'Alt Mainnet', - id: NETWORK_CONFIGURATION_ID_1, - rpcUrl: ALT_MAINNET_RPC_URL, - ticker: ETH, - type: NETWORK_TYPES.RPC, - }, - ); - }); - - it('returns matching networkConfiguration when passed a ticker that matches an existing configuration', function () { - assert.deepStrictEqual( - metamaskController.findNetworkConfigurationBy({ - ticker: MATIC, - }), - { - rpcUrl: POLYGON_RPC_URL, - type: NETWORK_TYPES.RPC, - chainId: POLYGON_CHAIN_ID, - ticker: MATIC, - nickname: 'Polygon', - id: NETWORK_CONFIGURATION_ID_2, - }, - ); - }); - - it('returns matching networkConfiguration when passed a nickname that matches an existing configuration', function () { - assert.deepStrictEqual( - metamaskController.findNetworkConfigurationBy({ - nickname: 'Alt Mainnet', - }), - { - chainId: MAINNET_CHAIN_ID, - nickname: 'Alt Mainnet', - id: NETWORK_CONFIGURATION_ID_1, - rpcUrl: ALT_MAINNET_RPC_URL, - ticker: ETH, - type: NETWORK_TYPES.RPC, - }, - ); - }); - - it('returns null if passed an object containing mismatched networkConfiguration key/value combination', function () { - assert.deepStrictEqual( - metamaskController.findNetworkConfigurationBy({ - nickname: MAINNET_CHAIN_ID, - }), - null, - ); - }); - - it('returns the first networkConfiguration added if passed an key/value combination for which there are multiple matching configurations', function () { - assert.deepStrictEqual( - metamaskController.findNetworkConfigurationBy({ - chainId: POLYGON_CHAIN_ID, - }), - { - rpcUrl: POLYGON_RPC_URL, - type: NETWORK_TYPES.RPC, - chainId: POLYGON_CHAIN_ID, - ticker: MATIC, - nickname: 'Polygon', - id: NETWORK_CONFIGURATION_ID_2, - }, - ); - }); - }); - }); }); diff --git a/app/scripts/migrations/087.test.js b/app/scripts/migrations/087.test.js new file mode 100644 index 000000000..1d631e926 --- /dev/null +++ b/app/scripts/migrations/087.test.js @@ -0,0 +1,89 @@ +import { migrate, version } from './087'; + +describe('migration #87', () => { + it('should update the version metadata', async () => { + const oldStorage = { + meta: { + version: 86, + }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.meta).toStrictEqual({ + version, + }); + }); + + it('should return state unaltered if there is no tokens controller state', async () => { + const oldData = { + other: 'data', + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should return state unaltered if there is no tokens controller suggested assets state', async () => { + const oldData = { + other: 'data', + TokensController: { + allDetectedTokens: {}, + allIgnoredTokens: {}, + allTokens: {}, + detectedTokens: [], + ignoredTokens: [], + tokens: [], + }, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('should remove the suggested assets state', async () => { + const oldData = { + other: 'data', + TokensController: { + allDetectedTokens: {}, + allIgnoredTokens: {}, + allTokens: {}, + detectedTokens: [], + ignoredTokens: [], + suggestedAssets: [], + tokens: [], + }, + }; + const oldStorage = { + meta: { + version: 86, + }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + expect(newStorage.data).toStrictEqual({ + other: 'data', + TokensController: { + allDetectedTokens: {}, + allIgnoredTokens: {}, + allTokens: {}, + detectedTokens: [], + ignoredTokens: [], + tokens: [], + }, + }); + }); +}); diff --git a/app/scripts/migrations/087.ts b/app/scripts/migrations/087.ts new file mode 100644 index 000000000..92093f967 --- /dev/null +++ b/app/scripts/migrations/087.ts @@ -0,0 +1,33 @@ +import { isObject } from '@metamask/utils'; +import { cloneDeep } from 'lodash'; + +export const version = 87; + +/** + * Remove the now-obsolete tokens controller `suggestedAssets` state. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate(originalVersionedData: { + meta: { version: number }; + data: Record; +}) { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + versionedData.data = transformState(versionedData.data); + return versionedData; +} + +function transformState(state: Record) { + if (!isObject(state.TokensController)) { + return state; + } + + delete state.TokensController.suggestedAssets; + + return state; +} diff --git a/app/scripts/migrations/088.test.ts b/app/scripts/migrations/088.test.ts new file mode 100644 index 000000000..2677cc3b3 --- /dev/null +++ b/app/scripts/migrations/088.test.ts @@ -0,0 +1,1094 @@ +import { migrate } from './088'; + +describe('migration #88', () => { + it('updates the version metadata', async () => { + const oldStorage = { + meta: { version: 87 }, + data: {}, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.meta).toStrictEqual({ version: 88 }); + }); + + it('returns the state unaltered if it has no NftController property', async () => { + const oldData = { + some: 'data', + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if the NftController object has no allNftContracts property', async () => { + const oldData = { + NftController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if NftController.allNftContracts is not an object', async () => { + const oldData = { + NftController: { + allNftContracts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if any value of the NftController.allNftContracts object is not an object itself', async () => { + const oldData = { + NftController: { + allNftContracts: { + '0x111': { + '123': 'foo', + }, + '0x222': 'bar', + }, + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites NftController.allNftContracts so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNftContracts: { + '0x111': { + '16': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + '32': [ + { + name: 'Contract 2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '64': [ + { + name: 'Contract 3', + address: '0xccc', + }, + ], + '128': [ + { + name: 'Contract 4', + address: '0xddd', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NftController: { + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'Contract 2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'Contract 3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'Contract 4', + address: '0xddd', + }, + ], + }, + }, + }, + }); + }); + + it('does not convert chain IDs in NftController.allNftContracts which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'Contract 2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'Contract 3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'Contract 4', + address: '0xddd', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NftController: { + allNftContracts: { + '0x111': { + '0x10': [ + { + name: 'Contract 1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'Contract 2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'Contract 3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'Contract 4', + address: '0xddd', + }, + ], + }, + }, + }, + }); + }); + + it('returns the state unaltered if the NftController object has no allNfts property', async () => { + const oldData = { + NftController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if NftController.allNfts is not an object', async () => { + const oldData = { + NftController: { + allNfts: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if any value of the NftController.allNfts object is not an object itself', async () => { + const oldData = { + NftController: { + allNfts: { + '0x111': { + '123': 'foo', + }, + '0x222': 'bar', + }, + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites NftController.allNfts so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNfts: { + '0x111': { + '16': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + '32': [ + { + name: 'NFT 2', + description: 'Description for NFT 2', + image: 'nft2.jpg', + standard: 'ERC721', + tokenId: '2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '64': [ + { + name: 'NFT 3', + description: 'Description for NFT 3', + image: 'nft3.jpg', + standard: 'ERC721', + tokenId: '3', + address: '0xccc', + }, + ], + '128': [ + { + name: 'NFT 4', + description: 'Description for NFT 4', + image: 'nft4.jpg', + standard: 'ERC721', + tokenId: '4', + address: '0xddd', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'NFT 2', + description: 'Description for NFT 2', + image: 'nft2.jpg', + standard: 'ERC721', + tokenId: '2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'NFT 3', + description: 'Description for NFT 3', + image: 'nft3.jpg', + standard: 'ERC721', + tokenId: '3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'NFT 4', + description: 'Description for NFT 4', + image: 'nft4.jpg', + standard: 'ERC721', + tokenId: '4', + address: '0xddd', + }, + ], + }, + }, + }, + }); + }); + + it('does not convert chain IDs in NftController.allNfts which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'NFT 2', + description: 'Description for NFT 2', + image: 'nft2.jpg', + standard: 'ERC721', + tokenId: '2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'NFT 3', + description: 'Description for NFT 3', + image: 'nft3.jpg', + standard: 'ERC721', + tokenId: '3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'NFT 4', + description: 'Description for NFT 4', + image: 'nft4.jpg', + standard: 'ERC721', + tokenId: '4', + address: '0xddd', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + NftController: { + allNfts: { + '0x111': { + '0x10': [ + { + name: 'NFT 1', + description: 'Description for NFT 1', + image: 'nft1.jpg', + standard: 'ERC721', + tokenId: '1', + address: '0xaaa', + }, + ], + '0x20': [ + { + name: 'NFT 2', + description: 'Description for NFT 2', + image: 'nft2.jpg', + standard: 'ERC721', + tokenId: '2', + address: '0xbbb', + }, + ], + }, + '0x222': { + '0x40': [ + { + name: 'NFT 3', + description: 'Description for NFT 3', + image: 'nft3.jpg', + standard: 'ERC721', + tokenId: '3', + address: '0xccc', + }, + ], + '0x80': [ + { + name: 'NFT 4', + description: 'Description for NFT 4', + image: 'nft4.jpg', + standard: 'ERC721', + tokenId: '4', + address: '0xddd', + }, + ], + }, + }, + }, + }); + }); + + it('returns the state unaltered if it has no TokenListController property', async () => { + const oldData = { + some: 'data', + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if the TokenListController object has no tokensChainsCache property', async () => { + const oldData = { + TokenListController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if TokenListController.tokensChainsCache is not an object', async () => { + const oldData = { + TokenListController: { + tokensChainsCache: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites TokenListController.tokensChainsCache so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokenListController: { + tokensChainsCache: { + '16': { + timestamp: 111111, + data: { + '0x111': { + address: '0x111', + symbol: 'TEST1', + decimals: 1, + occurrences: 1, + name: 'Token 1', + iconUrl: 'https://url/to/token1.png', + aggregators: [], + }, + }, + }, + '32': { + timestamp: 222222, + data: { + '0x222': { + address: '0x222', + symbol: 'TEST2', + decimals: 1, + occurrences: 1, + name: 'Token 2', + iconUrl: 'https://url/to/token2.png', + aggregators: [], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokenListController: { + tokensChainsCache: { + '0x10': { + timestamp: 111111, + data: { + '0x111': { + address: '0x111', + symbol: 'TEST1', + decimals: 1, + occurrences: 1, + name: 'Token 1', + iconUrl: 'https://url/to/token1.png', + aggregators: [], + }, + }, + }, + '0x20': { + timestamp: 222222, + data: { + '0x222': { + address: '0x222', + symbol: 'TEST2', + decimals: 1, + occurrences: 1, + name: 'Token 2', + iconUrl: 'https://url/to/token2.png', + aggregators: [], + }, + }, + }, + }, + }, + }); + }); + + it('does not convert chain IDs in TokenListController.tokensChainsCache which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokenListController: { + tokensChainsCache: { + '0x10': { + timestamp: 111111, + data: { + '0x111': { + address: '0x111', + symbol: 'TEST1', + decimals: 1, + occurrences: 1, + name: 'Token 1', + iconUrl: 'https://url/to/token1.png', + aggregators: [], + }, + }, + }, + '0x20': { + timestamp: 222222, + data: { + '0x222': { + address: '0x222', + symbol: 'TEST2', + decimals: 1, + occurrences: 1, + name: 'Token 2', + iconUrl: 'https://url/to/token2.png', + aggregators: [], + }, + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokenListController: { + tokensChainsCache: { + '0x10': { + timestamp: 111111, + data: { + '0x111': { + address: '0x111', + symbol: 'TEST1', + decimals: 1, + occurrences: 1, + name: 'Token 1', + iconUrl: 'https://url/to/token1.png', + aggregators: [], + }, + }, + }, + '0x20': { + timestamp: 222222, + data: { + '0x222': { + address: '0x222', + symbol: 'TEST2', + decimals: 1, + occurrences: 1, + name: 'Token 2', + iconUrl: 'https://url/to/token2.png', + aggregators: [], + }, + }, + }, + }, + }, + }); + }); + + it('returns the state unaltered if it has no TokensController property', async () => { + const oldData = { + some: 'data', + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if the TokensController object has no allTokens property', async () => { + const oldData = { + TokensController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if TokensController.allTokens is not an object', async () => { + const oldData = { + TokensController: { + allTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites TokensController.allTokens so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allTokens: { + '16': { + '0x111': [ + { + address: '0xaaa', + decimals: 1, + symbol: 'TEST1', + }, + ], + }, + '32': { + '0x222': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x10': { + '0x111': [ + { + address: '0xaaa', + decimals: 1, + symbol: 'TEST1', + }, + ], + }, + '0x20': { + '0x222': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + }, + }, + }); + }); + + it('does not convert chain IDs in TokensController.allTokens which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allTokens: { + '0x10': { + '0x111': [ + { + address: '0xaaa', + decimals: 1, + symbol: 'TEST1', + }, + ], + }, + '0x20': { + '0x222': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allTokens: { + '0x10': { + '0x111': [ + { + address: '0xaaa', + decimals: 1, + symbol: 'TEST1', + }, + ], + }, + '0x20': { + '0x222': [ + { + address: '0xbbb', + decimals: 1, + symbol: 'TEST2', + }, + ], + }, + }, + }, + }); + }); + + it('returns the state unaltered if the TokensController object has no allIgnoredTokens property', async () => { + const oldData = { + TokensController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if TokensController.allIgnoredTokens is not an object', async () => { + const oldData = { + TokensController: { + allIgnoredTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites TokensController.allIgnoredTokens so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allIgnoredTokens: { + '16': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '32': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allIgnoredTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }); + }); + + it('does not convert chain IDs in TokensController.allIgnoredTokens which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allIgnoredTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allIgnoredTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }); + }); + + it('returns the state unaltered if the TokensController object has no allDetectedTokens property', async () => { + const oldData = { + TokensController: { + some: 'data', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('returns the state unaltered if TokensController.allDetectedTokens is not an object', async () => { + const oldData = { + TokensController: { + allDetectedTokens: 'foo', + }, + }; + const oldStorage = { + meta: { version: 87 }, + data: oldData, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual(oldData); + }); + + it('rewrites TokensController.allDetectedTokens so that decimal chain IDs are converted to hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allDetectedTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allDetectedTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }); + }); + + it('does not convert chain IDs in TokensController.allDetectedTokens which are already hex strings', async () => { + const oldStorage = { + meta: { version: 87 }, + data: { + TokensController: { + allDetectedTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }, + }; + + const newStorage = await migrate(oldStorage); + + expect(newStorage.data).toStrictEqual({ + TokensController: { + allDetectedTokens: { + '0x10': { + '0x1': { + '0x111': ['0xaaa'], + }, + }, + '0x20': { + '0x2': { + '0x222': ['0xbbb'], + }, + }, + }, + }, + }); + }); +}); diff --git a/app/scripts/migrations/088.ts b/app/scripts/migrations/088.ts new file mode 100644 index 000000000..3233a3d1c --- /dev/null +++ b/app/scripts/migrations/088.ts @@ -0,0 +1,164 @@ +import { hasProperty, Hex, isObject, isStrictHexString } from '@metamask/utils'; +import { BN } from 'ethereumjs-util'; +import { cloneDeep, mapKeys } from 'lodash'; + +type VersionedData = { + meta: { version: number }; + data: Record; +}; + +export const version = 88; + +/** + * This migration does a few things: + * + * - Rebuilds `allNftContracts` and `allNfts` in NftController state to be keyed + * by a hex chain ID rather than a decimal chain ID. + * - Rebuilds `tokensChainsCache` in TokenListController to be keyed by a hex + * chain ID rather than a decimal chain ID. + * - Rebuilds `allTokens` and `allIgnoredTokens` in TokensController to be keyed + * by a hex chain ID rather than a decimal chain ID. + * + * @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist. + * @param originalVersionedData.meta - State metadata. + * @param originalVersionedData.meta.version - The current state version. + * @param originalVersionedData.data - The persisted MetaMask state, keyed by controller. + * @returns Updated versioned MetaMask extension state. + */ +export async function migrate( + originalVersionedData: VersionedData, +): Promise { + const versionedData = cloneDeep(originalVersionedData); + versionedData.meta.version = version; + migrateData(versionedData.data); + return versionedData; +} + +function migrateData(state: Record): void { + if (hasProperty(state, 'NftController') && isObject(state.NftController)) { + const nftControllerState = state.NftController; + + // Migrate NftController.allNftContracts + if ( + hasProperty(nftControllerState, 'allNftContracts') && + isObject(nftControllerState.allNftContracts) + ) { + const { allNftContracts } = nftControllerState; + + if ( + Object.keys(allNftContracts).every((address) => + isObject(allNftContracts[address]), + ) + ) { + Object.keys(allNftContracts).forEach((address) => { + const nftContractsByChainId = allNftContracts[address]; + + if (isObject(nftContractsByChainId)) { + allNftContracts[address] = mapKeys( + nftContractsByChainId, + (_, chainId: string) => toHex(chainId), + ); + } + }); + } + } + + // Migrate NftController.allNfts + if ( + hasProperty(nftControllerState, 'allNfts') && + isObject(nftControllerState.allNfts) + ) { + const { allNfts } = nftControllerState; + + if (Object.keys(allNfts).every((address) => isObject(allNfts[address]))) { + Object.keys(allNfts).forEach((address) => { + const nftsByChainId = allNfts[address]; + + if (isObject(nftsByChainId)) { + allNfts[address] = mapKeys(nftsByChainId, (_, chainId: string) => + toHex(chainId), + ); + } + }); + } + } + + state.NftController = nftControllerState; + } + + if ( + hasProperty(state, 'TokenListController') && + isObject(state.TokenListController) + ) { + const tokenListControllerState = state.TokenListController; + + // Migrate TokenListController.tokensChainsCache + if ( + hasProperty(tokenListControllerState, 'tokensChainsCache') && + isObject(tokenListControllerState.tokensChainsCache) + ) { + tokenListControllerState.tokensChainsCache = mapKeys( + tokenListControllerState.tokensChainsCache, + (_, chainId: string) => toHex(chainId), + ); + } + } + + if ( + hasProperty(state, 'TokensController') && + isObject(state.TokensController) + ) { + const tokensControllerState = state.TokensController; + + // Migrate TokensController.allTokens + if ( + hasProperty(tokensControllerState, 'allTokens') && + isObject(tokensControllerState.allTokens) + ) { + const { allTokens } = tokensControllerState; + + tokensControllerState.allTokens = mapKeys( + allTokens, + (_, chainId: string) => toHex(chainId), + ); + } + + // Migrate TokensController.allIgnoredTokens + if ( + hasProperty(tokensControllerState, 'allIgnoredTokens') && + isObject(tokensControllerState.allIgnoredTokens) + ) { + const { allIgnoredTokens } = tokensControllerState; + + tokensControllerState.allIgnoredTokens = mapKeys( + allIgnoredTokens, + (_, chainId: string) => toHex(chainId), + ); + } + + // Migrate TokensController.allDetectedTokens + if ( + hasProperty(tokensControllerState, 'allDetectedTokens') && + isObject(tokensControllerState.allDetectedTokens) + ) { + const { allDetectedTokens } = tokensControllerState; + + tokensControllerState.allDetectedTokens = mapKeys( + allDetectedTokens, + (_, chainId: string) => toHex(chainId), + ); + } + + state.TokensController = tokensControllerState; + } +} + +function toHex(value: number | string | BN): Hex { + if (typeof value === 'string' && isStrictHexString(value)) { + return value; + } + const hexString = BN.isBN(value) + ? value.toString(16) + : new BN(value.toString(), 10).toString(16); + return `0x${hexString}`; +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index acde4758b..813f5e799 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -90,6 +90,8 @@ import * as m083 from './083'; import * as m084 from './084'; import * as m085 from './085'; import * as m086 from './086'; +import * as m087 from './087'; +import * as m088 from './088'; const migrations = [ m002, @@ -177,6 +179,8 @@ const migrations = [ m084, m085, m086, + m087, + m088, ]; export default migrations; diff --git a/app/scripts/mmi-keyring-builder-factory.js b/app/scripts/mmi-keyring-builder-factory.js new file mode 100644 index 000000000..a532df071 --- /dev/null +++ b/app/scripts/mmi-keyring-builder-factory.js @@ -0,0 +1,17 @@ +/** + * Get builder function for MMI keyrings which require an additional `opts` + * parameter, used to pass MMI configuration. + * + * Returns a builder function for `Keyring` with a `type` property. + * + * @param {Keyring} Keyring - The Keyring class for the builder. + * @param {Keyring} opts - Optional parameters to be passed to the builder. + * @returns {Function} A builder function for the given Keyring. + */ +export function mmiKeyringBuilderFactory(Keyring, opts) { + const builder = () => new Keyring(opts); + + builder.type = Keyring.type; + + return builder; +} diff --git a/builds.yml b/builds.yml index 950e5249e..954ddd7f3 100644 --- a/builds.yml +++ b/builds.yml @@ -50,7 +50,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.1-flask.1/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -68,7 +68,7 @@ buildTypes: - SEGMENT_FLASK_WRITE_KEY - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.0-flask.1/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/0.16.1-flask.1/index.html - SUPPORT_LINK: https://metamask-flask.zendesk.com/hc - SUPPORT_REQUEST_LINK: https://metamask-flask.zendesk.com/hc/en-us/requests/new - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -86,6 +86,7 @@ buildTypes: - SEGMENT_WRITE_KEY_REF: SEGMENT_MMI_WRITE_KEY - SUPPORT_LINK: https://mmi-support.zendesk.com/hc/en-us - SUPPORT_REQUEST_LINK: https://mmi-support.zendesk.com/hc/en-us/requests/new + - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v1/configuration/default # For some reason, MMI uses this type of versioning # Leaving it on for backwards compatibility isPrerelease: true @@ -153,7 +154,6 @@ env: - SUPPORT_LINK: https://support.metamask.io - SUPPORT_REQUEST_LINK: https://metamask.zendesk.com/hc/en-us - SKIP_BACKGROUND_INITIALIZATION: false - - MULTICHAIN: false # TODO(ritave): Move ManifestV3 into a feature? - ENABLE_MV3: false diff --git a/codecov.yml b/codecov.yml index 6f7608e07..e2065d40a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -4,13 +4,14 @@ codecov: coverage: round: nearest status: - project: - global: - target: auto - threshold: 0% - base: auto - transforms: - target: 100% - threshold: 0% - paths: - - development/build/transforms/**/*.js + project: off + patch: off + # global: + # target: auto + # threshold: 0% + # base: auto + # transforms: + # target: 100% + # threshold: 0% + # paths: + # - development/build/transforms/**/*.js diff --git a/coverage-targets.js b/coverage-targets.js index c9b4ac370..631a8b6e7 100644 --- a/coverage-targets.js +++ b/coverage-targets.js @@ -6,10 +6,10 @@ // subset of files to check against these targets. module.exports = { global: { - lines: 71.15, - branches: 59.17, - statements: 70.6, - functions: 63.82, + lines: 70.85, + branches: 59.07, + statements: 70.3, + functions: 63.52, }, transforms: { branches: 100, diff --git a/development/build/config.js b/development/build/config.js index d8a339de2..0eee28ce3 100644 --- a/development/build/config.js +++ b/development/build/config.js @@ -11,6 +11,7 @@ const VARIABLES_REQUIRED_IN_PRODUCTION = { main: ['INFURA_PROD_PROJECT_ID', 'SEGMENT_PROD_WRITE_KEY', 'SENTRY_DSN'], beta: ['INFURA_BETA_PROJECT_ID', 'SEGMENT_BETA_WRITE_KEY', 'SENTRY_DSN'], flask: ['INFURA_FLASK_PROJECT_ID', 'SEGMENT_FLASK_WRITE_KEY', 'SENTRY_DSN'], + mmi: ['INFURA_MMI_PROJECT_ID', 'SEGMENT_MMI_WRITE_KEY', 'SENTRY_DSN'], }; async function fromIniFile(filepath) { diff --git a/development/generate-lavamoat-policies.js b/development/generate-lavamoat-policies.js index 16a669f2c..621d12c26 100755 --- a/development/generate-lavamoat-policies.js +++ b/development/generate-lavamoat-policies.js @@ -6,11 +6,6 @@ const { loadBuildTypesConfig } = require('./lib/build-type'); const buildTypesConfig = loadBuildTypesConfig(); -const stableBuildTypes = Object.keys(buildTypesConfig.buildTypes).filter( - // Skip generating policy for MMI until that build has stabilized - (buildType) => buildType !== 'mmi', -); - start().catch((error) => { console.error('Policy generation failed.', error); process.exitCode = 1; @@ -27,7 +22,7 @@ async function start() { .option('build-types', { alias: ['t'], choices: Object.keys(buildTypesConfig.buildTypes), - default: stableBuildTypes, + default: Object.keys(buildTypesConfig.buildTypes), demandOption: true, description: 'The build type(s) to generate policy files for.', }) diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json index 12fc75607..32daeee71 100644 --- a/development/states/navigate-txs.json +++ b/development/states/navigate-txs.json @@ -305,7 +305,7 @@ "mainnet": "ok", "goerli": "ok", "sepolia": "ok", - "lineatestnet": "ok" + "lineaGoerli": "ok" } }, "send": { diff --git a/development/ts-migration-dashboard/files-to-convert.json b/development/ts-migration-dashboard/files-to-convert.json index bf123f1e3..08d1a48e1 100644 --- a/development/ts-migration-dashboard/files-to-convert.json +++ b/development/ts-migration-dashboard/files-to-convert.json @@ -470,10 +470,6 @@ "ui/components/app/detected-token/detected-token-values/detected-token-values.test.js", "ui/components/app/detected-token/detected-token.js", "ui/components/app/detected-token/detected-token.test.js", - "ui/components/app/dropdowns/dropdown.js", - "ui/components/app/dropdowns/dropdown.test.js", - "ui/components/app/dropdowns/network-dropdown.js", - "ui/components/app/dropdowns/network-dropdown.test.js", "ui/components/app/edit-gas-display/edit-gas-display.component.js", "ui/components/app/edit-gas-display/edit-gas-display.stories.js", "ui/components/app/edit-gas-display/edit-gas-display.test.js", diff --git a/docs/components/account-menu.md b/docs/components/account-menu.md deleted file mode 100644 index b65a99360..000000000 --- a/docs/components/account-menu.md +++ /dev/null @@ -1,16 +0,0 @@ -# Account Menu - -The account menu is the popup menu which contains options such as: - - Logging out - - Switching accounts - - Creating a new account - - Importing an account - - Connecting a HW wallet - - Looking up info & help - - Adjusting settings - - It can be seen below where it has been outlined with a red box - - ![Screenshot of account menu](https://i.imgur.com/xpkfIuR.png) - - Above screenshot showing the menu bar in MetaMask 6.7.1 diff --git a/docs/confirmation-refactoring/confirmation-backend-architecture/README.md b/docs/confirmation-refactoring/confirmation-backend-architecture/README.md index 453482cbf..a3f30eb88 100644 --- a/docs/confirmation-refactoring/confirmation-backend-architecture/README.md +++ b/docs/confirmation-refactoring/confirmation-backend-architecture/README.md @@ -18,7 +18,7 @@ Current confirmation implementation in the background consists of following piec ## Areas of Code Cleanup: 1. Migrating to `@metamask/transaction-controller`. `TransactionController` in extension repo should eventually get replaced by core repo [TransactionController](https://github.com/MetaMask/core/tree/main/packages/transaction-controller). This controller is maintained by core team and also used in Metamask Mobile App. -2. Migrating to `@metamask/message-manager`. Message Managers in extension repo should be deprecated in favour of core repo [MessageManagers](https://github.com/MetaMask/core/tree/main/packages/message-manager). +2. Migrating to `@metamask/message-manager`. Message Managers in extension repo should be deprecated in favor of core repo [MessageManagers](https://github.com/MetaMask/core/tree/main/packages/message-manager). 3. Cleanup Code in `MetamaskController`. [Metamaskcontroller](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/metamask-controller.js) is where `TransactionController` and different `MessageManagers` are initialized. It is responsible for injecting required dependencies. Also, it is responsible for handling incoming DAPP requests and invoking appropriate methods in these background classes. Over the period of time lot of code that should have been part of `TransactionController` and `MessageManagers` has ended up in `MetamaskController`. We need to cleanup this code and move to the appropriate classes. - Code [here](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3097) to check if `eth_sign` is enabled in preferences and perform other validation on the incoming request should be part of [MessageManager](https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/message-manager.js) - Method to sign messages [signMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3158), [signPersonalMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3217), [signTypedMessage](https://github.com/MetaMask/metamask-extension/blob/bc19856d5d9ad1831e1722c84fe6161bed7a0a5a/app/scripts/metamask-controller.js#L3470) can be simplified by injecting `KeyringController` into `MessageManagers`. diff --git a/jest.config.js b/jest.config.js index 35c9f342c..db7b93bcd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,8 @@ module.exports = { '/app/scripts/lib/**/*.js', '/app/scripts/lib/createRPCMethodTrackingMiddleware.js', '/app/scripts/migrations/*.js', + '/app/scripts/migrations/*.ts', + '!/app/scripts/migrations/*.test.(js|ts)', '/app/scripts/platforms/*.js', '/shared/**/*.(js|ts|tsx)', '/ui/**/*.(js|ts|tsx)', @@ -37,6 +39,7 @@ module.exports = { testMatch: [ '/app/scripts/constants/error-utils.test.js', '/app/scripts/controllers/app-state.test.js', + '/app/scripts/controllers/mmi-controller.test.js', '/app/scripts/controllers/network/**/*.test.js', '/app/scripts/controllers/network/**/*.test.ts', '/app/scripts/controllers/permissions/**/*.test.js', @@ -46,7 +49,7 @@ module.exports = { '/app/scripts/lib/**/*.test.js', '/app/scripts/lib/**/*.test.ts', '/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js', - '/app/scripts/migrations/*.test.js', + '/app/scripts/migrations/*.test.(js|ts)', '/app/scripts/platforms/*.test.js', '/shared/**/*.test.(js|ts)', '/ui/**/*.test.(js|ts|tsx)', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 938ee7c44..c43c1c97b 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -142,7 +142,6 @@ "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@ethersproject/providers": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true } @@ -150,8 +149,6 @@ "@ethereumjs/tx>@chainsafe/ssz": { "packages": { "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, - "@ethereumjs/tx>@chainsafe/ssz>case": true, - "browserify": true, "browserify>buffer": true } }, @@ -160,7 +157,8 @@ "WeakRef": true }, "packages": { - "browserify": true + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/as-sha256": true, + "@metamask/key-tree>@noble/hashes": true } }, "@ethereumjs/tx>@ethereumjs/rlp": { @@ -175,50 +173,56 @@ "packages": { "@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, "browserify>buffer": true, "browserify>events": true, "browserify>insert-module-globals>is-buffer": true } }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "globals": { + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "browserify>util": true + } + }, "@ethereumjs/tx>ethereum-cryptography": { "globals": { "TextDecoder": true, "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true + "@metamask/key-tree>@noble/hashes": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": { - "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": { - "globals": { - "crypto": true - }, - "packages": { - "browserify>browser-resolve": true + "@metamask/key-tree>@noble/hashes": true } }, "@ethersproject/abi": { @@ -770,16 +774,17 @@ "URL": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, "console.info": true, "console.log": true, "setInterval": true, "setTimeout": true }, "packages": { + "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/assets-controllers>@metamask/rpc-errors": true, "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, @@ -790,7 +795,6 @@ "browserify>events": true, "eth-json-rpc-filters>async-mutex": true, "eth-query": true, - "eth-rpc-errors": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true @@ -814,6 +818,12 @@ "superstruct": true } }, + "@metamask/assets-controllers>@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/assets-controllers>abort-controller": { "globals": { "AbortController": true @@ -989,22 +999,74 @@ }, "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "@metamask/scure-bip39": true, "browserify>buffer": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": { + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, "ethereumjs-wallet>randombytes": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>obs-store": { "packages": { "@metamask/eth-token-tracker>safe-event-emitter": true, @@ -1040,25 +1102,17 @@ }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": { "packages": { - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1215,7 +1269,7 @@ "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true, "bn.js": true, "browserify>buffer": true, @@ -1223,6 +1277,21 @@ "eth-sig-util>tweetnacl-util": true } }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1547,6 +1616,7 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/utils": true, "deep-freeze-strict": true, "eth-rpc-errors": true, "immer": true, @@ -1563,12 +1633,34 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/base-controller": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1605,8 +1697,14 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39>@noble/hashes": true + } + }, + "@metamask/scure-bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@metamask/signature-controller": { @@ -1706,7 +1804,12 @@ }, "@metamask/subject-metadata-controller": { "packages": { - "@metamask/base-controller": true + "@metamask/subject-metadata-controller>@metamask/base-controller": true + } + }, + "@metamask/subject-metadata-controller>@metamask/base-controller": { + "packages": { + "immer": true } }, "@metamask/utils": { @@ -2484,6 +2587,16 @@ "ethjs-query>babel-runtime>core-js": true } }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>browserify-zlib>pako": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, "browserify>buffer": { "globals": { "console": true @@ -2650,6 +2763,12 @@ "browserify>has>function-bind": true } }, + "browserify>https-browserify": { + "packages": { + "browserify>stream-http": true, + "browserify>url": true + } + }, "browserify>os-browserify": { "globals": { "location": true, @@ -2679,6 +2798,41 @@ "readable-stream": true } }, + "browserify>stream-http": { + "globals": { + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, + "clearTimeout": true, + "fetch": true, + "location.protocol.search": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-http>builtin-status-codes": true, + "browserify>stream-http>readable-stream": true, + "browserify>url": true, + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "browserify>stream-http>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "browserify>string_decoder": { "packages": { "ethereumjs-wallet>safe-buffer": true @@ -2824,22 +2978,18 @@ "setTimeout": true }, "packages": { - "@metamask/safe-event-emitter": true, - "eth-block-tracker>@metamask/utils": true, + "@metamask/utils": true, + "eth-block-tracker>@metamask/safe-event-emitter": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true } }, - "eth-block-tracker>@metamask/utils": { + "eth-block-tracker>@metamask/safe-event-emitter": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "browserify>events": true } }, "eth-ens-namehash": { @@ -2900,16 +3050,59 @@ "setInterval": true }, "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "browserify>buffer": true, "browserify>crypto-browserify": true, "browserify>events": true, + "eth-lattice-keyring>@ethereumjs/tx": true, "eth-lattice-keyring>bn.js": true, "eth-lattice-keyring>gridplus-sdk": true, "eth-lattice-keyring>rlp": true } }, + "eth-lattice-keyring>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>bn.js": { "globals": { "Buffer": true @@ -2933,12 +3126,12 @@ "setTimeout": true }, "packages": { - "@ethereumjs/common": true, "@ethereumjs/common>crc-32": true, - "@ethereumjs/tx": true, "@ethersproject/abi": true, "bn.js": true, "browserify>buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -2955,6 +3148,65 @@ "lodash": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3084,22 +3336,14 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3138,21 +3382,13 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": true, "ethereumjs-abi>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ganache>secp256k1>elliptic": true } }, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-abi>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3337,22 +3573,14 @@ "ethereumjs-wallet>safe-buffer": true } }, - "ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-wallet>ethereumjs-util": { "packages": { "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>ethereum-cryptography": true, "ethereumjs-wallet>ethereumjs-util>ethjs-util": true, "ganache>secp256k1>elliptic": true } @@ -4136,34 +4364,6 @@ "react": true } }, - "react-transition-group": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "prop-types": true, - "react": true, - "react-dom": true, - "react-transition-group>chain-function": true, - "react-transition-group>dom-helpers": true, - "react-transition-group>warning": true - } - }, - "react-transition-group>dom-helpers": { - "globals": { - "document": true, - "setTimeout": true - }, - "packages": { - "@babel/runtime": true - } - }, - "react-transition-group>warning": { - "globals": { - "console": true - } - }, "readable-stream": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index cb10cb8fd..f80fbb328 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -142,7 +142,6 @@ "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@ethersproject/providers": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true } @@ -150,8 +149,6 @@ "@ethereumjs/tx>@chainsafe/ssz": { "packages": { "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, - "@ethereumjs/tx>@chainsafe/ssz>case": true, - "browserify": true, "browserify>buffer": true } }, @@ -160,7 +157,8 @@ "WeakRef": true }, "packages": { - "browserify": true + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/as-sha256": true, + "@metamask/key-tree>@noble/hashes": true } }, "@ethereumjs/tx>@ethereumjs/rlp": { @@ -175,50 +173,56 @@ "packages": { "@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, "browserify>buffer": true, "browserify>events": true, "browserify>insert-module-globals>is-buffer": true } }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "globals": { + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "browserify>util": true + } + }, "@ethereumjs/tx>ethereum-cryptography": { "globals": { "TextDecoder": true, "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true + "@metamask/key-tree>@noble/hashes": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": { - "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": { - "globals": { - "crypto": true - }, - "packages": { - "browserify>browser-resolve": true + "@metamask/key-tree>@noble/hashes": true } }, "@ethersproject/abi": { @@ -770,16 +774,17 @@ "URL": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, "console.info": true, "console.log": true, "setInterval": true, "setTimeout": true }, "packages": { + "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/assets-controllers>@metamask/rpc-errors": true, "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, @@ -790,7 +795,6 @@ "browserify>events": true, "eth-json-rpc-filters>async-mutex": true, "eth-query": true, - "eth-rpc-errors": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true @@ -814,6 +818,12 @@ "superstruct": true } }, + "@metamask/assets-controllers>@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/assets-controllers>abort-controller": { "globals": { "AbortController": true @@ -1060,22 +1070,74 @@ }, "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "@metamask/scure-bip39": true, "browserify>buffer": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": { + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, "ethereumjs-wallet>randombytes": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>obs-store": { "packages": { "@metamask/eth-token-tracker>safe-event-emitter": true, @@ -1111,25 +1173,17 @@ }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": { "packages": { - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1286,7 +1340,7 @@ "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true, "bn.js": true, "browserify>buffer": true, @@ -1294,6 +1348,21 @@ "eth-sig-util>tweetnacl-util": true } }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1593,8 +1662,8 @@ "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true } }, "@metamask/notification-controller>nanoid": { @@ -1625,6 +1694,7 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/utils": true, "deep-freeze-strict": true, "eth-rpc-errors": true, "immer": true, @@ -1641,12 +1711,34 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/base-controller": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1724,15 +1816,15 @@ "@metamask/permission-controller": true, "@metamask/rpc-methods-flask>@metamask/snaps-ui": true, "@metamask/rpc-methods-flask>@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, "@metamask/rpc-methods-flask>nanoid": true, - "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } }, "@metamask/rpc-methods-flask>@metamask/snaps-ui": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, "superstruct": true } }, @@ -1749,11 +1841,25 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/rpc-methods-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, "semver": true, "superstruct": true } @@ -1781,8 +1887,14 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39>@noble/hashes": true + } + }, + "@metamask/scure-bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@metamask/signature-controller": { @@ -1887,6 +1999,7 @@ "@metamask/providers>@metamask/object-multiplex": true, "@metamask/snaps-controllers-flask>@metamask/rpc-methods": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, "@metamask/snaps-controllers-flask>concat-stream": true, "@metamask/snaps-controllers-flask>nanoid": true, "@metamask/snaps-controllers>@xstate/fsm": true, @@ -1894,7 +2007,6 @@ "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, "json-rpc-middleware-stream": true, @@ -1908,8 +2020,8 @@ "@metamask/permission-controller": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, "@metamask/snaps-controllers-flask>nanoid": true, - "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } @@ -1927,18 +2039,32 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, "semver": true, "superstruct": true } }, "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": { "packages": { - "@metamask/utils": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2095,7 +2221,19 @@ }, "@metamask/snaps-ui-flask": { "packages": { - "@metamask/utils": true, + "@metamask/snaps-ui-flask>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-ui-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2112,19 +2250,59 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, "semver": true, "superstruct": true } }, + "@metamask/snaps-utils-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils-flask>is-svg": { + "packages": { + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/key-tree>@noble/secp256k1": true, - "@metamask/utils": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2152,7 +2330,12 @@ }, "@metamask/subject-metadata-controller": { "packages": { - "@metamask/base-controller": true + "@metamask/subject-metadata-controller>@metamask/base-controller": true + } + }, + "@metamask/subject-metadata-controller>@metamask/base-controller": { + "packages": { + "immer": true } }, "@metamask/utils": { @@ -2930,6 +3113,16 @@ "ethjs-query>babel-runtime>core-js": true } }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>browserify-zlib>pako": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, "browserify>buffer": { "globals": { "console": true @@ -3096,6 +3289,12 @@ "browserify>has>function-bind": true } }, + "browserify>https-browserify": { + "packages": { + "browserify>stream-http": true, + "browserify>url": true + } + }, "browserify>os-browserify": { "globals": { "location": true, @@ -3125,6 +3324,41 @@ "readable-stream": true } }, + "browserify>stream-http": { + "globals": { + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, + "clearTimeout": true, + "fetch": true, + "location.protocol.search": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-http>builtin-status-codes": true, + "browserify>stream-http>readable-stream": true, + "browserify>url": true, + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "browserify>stream-http>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "browserify>string_decoder": { "packages": { "ethereumjs-wallet>safe-buffer": true @@ -3270,22 +3504,18 @@ "setTimeout": true }, "packages": { - "@metamask/safe-event-emitter": true, - "eth-block-tracker>@metamask/utils": true, + "@metamask/utils": true, + "eth-block-tracker>@metamask/safe-event-emitter": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true } }, - "eth-block-tracker>@metamask/utils": { + "eth-block-tracker>@metamask/safe-event-emitter": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "browserify>events": true } }, "eth-ens-namehash": { @@ -3346,16 +3576,59 @@ "setInterval": true }, "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "browserify>buffer": true, "browserify>crypto-browserify": true, "browserify>events": true, + "eth-lattice-keyring>@ethereumjs/tx": true, "eth-lattice-keyring>bn.js": true, "eth-lattice-keyring>gridplus-sdk": true, "eth-lattice-keyring>rlp": true } }, + "eth-lattice-keyring>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>bn.js": { "globals": { "Buffer": true @@ -3379,12 +3652,12 @@ "setTimeout": true }, "packages": { - "@ethereumjs/common": true, "@ethereumjs/common>crc-32": true, - "@ethereumjs/tx": true, "@ethersproject/abi": true, "bn.js": true, "browserify>buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3401,6 +3674,65 @@ "lodash": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3530,22 +3862,14 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3584,21 +3908,13 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": true, "ethereumjs-abi>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ganache>secp256k1>elliptic": true } }, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-abi>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3783,22 +4099,14 @@ "ethereumjs-wallet>safe-buffer": true } }, - "ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-wallet>ethereumjs-util": { "packages": { "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>ethereum-cryptography": true, "ethereumjs-wallet>ethereumjs-util>ethjs-util": true, "ganache>secp256k1>elliptic": true } @@ -4714,34 +5022,6 @@ "react": true } }, - "react-transition-group": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "prop-types": true, - "react": true, - "react-dom": true, - "react-transition-group>chain-function": true, - "react-transition-group>dom-helpers": true, - "react-transition-group>warning": true - } - }, - "react-transition-group>dom-helpers": { - "globals": { - "document": true, - "setTimeout": true - }, - "packages": { - "@babel/runtime": true - } - }, - "react-transition-group>warning": { - "globals": { - "console": true - } - }, "readable-stream": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index cb10cb8fd..f80fbb328 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -142,7 +142,6 @@ "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@ethersproject/providers": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true } @@ -150,8 +149,6 @@ "@ethereumjs/tx>@chainsafe/ssz": { "packages": { "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, - "@ethereumjs/tx>@chainsafe/ssz>case": true, - "browserify": true, "browserify>buffer": true } }, @@ -160,7 +157,8 @@ "WeakRef": true }, "packages": { - "browserify": true + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/as-sha256": true, + "@metamask/key-tree>@noble/hashes": true } }, "@ethereumjs/tx>@ethereumjs/rlp": { @@ -175,50 +173,56 @@ "packages": { "@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, "browserify>buffer": true, "browserify>events": true, "browserify>insert-module-globals>is-buffer": true } }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "globals": { + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "browserify>util": true + } + }, "@ethereumjs/tx>ethereum-cryptography": { "globals": { "TextDecoder": true, "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true + "@metamask/key-tree>@noble/hashes": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": { - "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": { - "globals": { - "crypto": true - }, - "packages": { - "browserify>browser-resolve": true + "@metamask/key-tree>@noble/hashes": true } }, "@ethersproject/abi": { @@ -770,16 +774,17 @@ "URL": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, "console.info": true, "console.log": true, "setInterval": true, "setTimeout": true }, "packages": { + "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/assets-controllers>@metamask/rpc-errors": true, "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, @@ -790,7 +795,6 @@ "browserify>events": true, "eth-json-rpc-filters>async-mutex": true, "eth-query": true, - "eth-rpc-errors": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true @@ -814,6 +818,12 @@ "superstruct": true } }, + "@metamask/assets-controllers>@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/assets-controllers>abort-controller": { "globals": { "AbortController": true @@ -1060,22 +1070,74 @@ }, "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "@metamask/scure-bip39": true, "browserify>buffer": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": { + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, "ethereumjs-wallet>randombytes": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>obs-store": { "packages": { "@metamask/eth-token-tracker>safe-event-emitter": true, @@ -1111,25 +1173,17 @@ }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": { "packages": { - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1286,7 +1340,7 @@ "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true, "bn.js": true, "browserify>buffer": true, @@ -1294,6 +1348,21 @@ "eth-sig-util>tweetnacl-util": true } }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1593,8 +1662,8 @@ "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, - "@metamask/controller-utils": true, - "@metamask/notification-controller>nanoid": true + "@metamask/notification-controller>nanoid": true, + "@metamask/utils": true } }, "@metamask/notification-controller>nanoid": { @@ -1625,6 +1694,7 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/utils": true, "deep-freeze-strict": true, "eth-rpc-errors": true, "immer": true, @@ -1641,12 +1711,34 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/base-controller": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1724,15 +1816,15 @@ "@metamask/permission-controller": true, "@metamask/rpc-methods-flask>@metamask/snaps-ui": true, "@metamask/rpc-methods-flask>@metamask/snaps-utils": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, "@metamask/rpc-methods-flask>nanoid": true, - "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } }, "@metamask/rpc-methods-flask>@metamask/snaps-ui": { "packages": { - "@metamask/utils": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, "superstruct": true } }, @@ -1749,11 +1841,25 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/rpc-methods-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/rpc-methods-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, "semver": true, "superstruct": true } @@ -1781,8 +1887,14 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39>@noble/hashes": true + } + }, + "@metamask/scure-bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@metamask/signature-controller": { @@ -1887,6 +1999,7 @@ "@metamask/providers>@metamask/object-multiplex": true, "@metamask/snaps-controllers-flask>@metamask/rpc-methods": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, "@metamask/snaps-controllers-flask>concat-stream": true, "@metamask/snaps-controllers-flask>nanoid": true, "@metamask/snaps-controllers>@xstate/fsm": true, @@ -1894,7 +2007,6 @@ "@metamask/snaps-controllers>readable-web-to-node-stream": true, "@metamask/snaps-controllers>tar-stream": true, "@metamask/snaps-utils>@metamask/snaps-registry": true, - "@metamask/utils": true, "eth-rpc-errors": true, "json-rpc-engine": true, "json-rpc-middleware-stream": true, @@ -1908,8 +2020,8 @@ "@metamask/permission-controller": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils": true, "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, "@metamask/snaps-controllers-flask>nanoid": true, - "@metamask/utils": true, "eth-rpc-errors": true, "superstruct": true } @@ -1927,18 +2039,32 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, "semver": true, "superstruct": true } }, "@metamask/snaps-controllers-flask>@metamask/snaps-utils>@metamask/snaps-ui": { "packages": { - "@metamask/utils": true, + "@metamask/snaps-controllers-flask>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-controllers-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2095,7 +2221,19 @@ }, "@metamask/snaps-ui-flask": { "packages": { - "@metamask/utils": true, + "@metamask/snaps-ui-flask>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-ui-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2112,19 +2250,59 @@ "packages": { "@metamask/key-tree>@noble/hashes": true, "@metamask/key-tree>@scure/base": true, + "@metamask/snaps-utils-flask>@metamask/utils": true, + "@metamask/snaps-utils-flask>is-svg": true, "@metamask/snaps-utils>cron-parser": true, "@metamask/snaps-utils>fast-json-stable-stringify": true, "@metamask/snaps-utils>rfdc": true, "@metamask/snaps-utils>validate-npm-package-name": true, - "@metamask/utils": true, + "browserify>buffer": true, "semver": true, "superstruct": true } }, + "@metamask/snaps-utils-flask>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/snaps-utils-flask>is-svg": { + "packages": { + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser": true + } + }, + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser": { + "globals": { + "entityName": true, + "val": true + }, + "packages": { + "@metamask/snaps-utils-flask>is-svg>fast-xml-parser>strnum": true + } + }, "@metamask/snaps-utils>@metamask/snaps-registry": { "packages": { "@metamask/key-tree>@noble/secp256k1": true, - "@metamask/utils": true, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/snaps-utils>@metamask/snaps-registry>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, "superstruct": true } }, @@ -2152,7 +2330,12 @@ }, "@metamask/subject-metadata-controller": { "packages": { - "@metamask/base-controller": true + "@metamask/subject-metadata-controller>@metamask/base-controller": true + } + }, + "@metamask/subject-metadata-controller>@metamask/base-controller": { + "packages": { + "immer": true } }, "@metamask/utils": { @@ -2930,6 +3113,16 @@ "ethjs-query>babel-runtime>core-js": true } }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>browserify-zlib>pako": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, "browserify>buffer": { "globals": { "console": true @@ -3096,6 +3289,12 @@ "browserify>has>function-bind": true } }, + "browserify>https-browserify": { + "packages": { + "browserify>stream-http": true, + "browserify>url": true + } + }, "browserify>os-browserify": { "globals": { "location": true, @@ -3125,6 +3324,41 @@ "readable-stream": true } }, + "browserify>stream-http": { + "globals": { + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, + "clearTimeout": true, + "fetch": true, + "location.protocol.search": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-http>builtin-status-codes": true, + "browserify>stream-http>readable-stream": true, + "browserify>url": true, + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "browserify>stream-http>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "browserify>string_decoder": { "packages": { "ethereumjs-wallet>safe-buffer": true @@ -3270,22 +3504,18 @@ "setTimeout": true }, "packages": { - "@metamask/safe-event-emitter": true, - "eth-block-tracker>@metamask/utils": true, + "@metamask/utils": true, + "eth-block-tracker>@metamask/safe-event-emitter": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true } }, - "eth-block-tracker>@metamask/utils": { + "eth-block-tracker>@metamask/safe-event-emitter": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "browserify>events": true } }, "eth-ens-namehash": { @@ -3346,16 +3576,59 @@ "setInterval": true }, "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "browserify>buffer": true, "browserify>crypto-browserify": true, "browserify>events": true, + "eth-lattice-keyring>@ethereumjs/tx": true, "eth-lattice-keyring>bn.js": true, "eth-lattice-keyring>gridplus-sdk": true, "eth-lattice-keyring>rlp": true } }, + "eth-lattice-keyring>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>bn.js": { "globals": { "Buffer": true @@ -3379,12 +3652,12 @@ "setTimeout": true }, "packages": { - "@ethereumjs/common": true, "@ethereumjs/common>crc-32": true, - "@ethereumjs/tx": true, "@ethersproject/abi": true, "bn.js": true, "browserify>buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -3401,6 +3674,65 @@ "lodash": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3530,22 +3862,14 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3584,21 +3908,13 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": true, "ethereumjs-abi>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ganache>secp256k1>elliptic": true } }, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-abi>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3783,22 +4099,14 @@ "ethereumjs-wallet>safe-buffer": true } }, - "ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-wallet>ethereumjs-util": { "packages": { "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>ethereum-cryptography": true, "ethereumjs-wallet>ethereumjs-util>ethjs-util": true, "ganache>secp256k1>elliptic": true } @@ -4714,34 +5022,6 @@ "react": true } }, - "react-transition-group": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "prop-types": true, - "react": true, - "react-dom": true, - "react-transition-group>chain-function": true, - "react-transition-group>dom-helpers": true, - "react-transition-group>warning": true - } - }, - "react-transition-group>dom-helpers": { - "globals": { - "document": true, - "setTimeout": true - }, - "packages": { - "@babel/runtime": true - } - }, - "react-transition-group>warning": { - "globals": { - "console": true - } - }, "readable-stream": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 938ee7c44..c43c1c97b 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -142,7 +142,6 @@ "@ethereumjs/tx>@ethereumjs/rlp": true, "@ethereumjs/tx>@ethereumjs/util": true, "@ethereumjs/tx>ethereum-cryptography": true, - "@ethersproject/providers": true, "browserify>buffer": true, "browserify>insert-module-globals>is-buffer": true } @@ -150,8 +149,6 @@ "@ethereumjs/tx>@chainsafe/ssz": { "packages": { "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, - "@ethereumjs/tx>@chainsafe/ssz>case": true, - "browserify": true, "browserify>buffer": true } }, @@ -160,7 +157,8 @@ "WeakRef": true }, "packages": { - "browserify": true + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/as-sha256": true, + "@metamask/key-tree>@noble/hashes": true } }, "@ethereumjs/tx>@ethereumjs/rlp": { @@ -175,50 +173,56 @@ "packages": { "@ethereumjs/tx>@chainsafe/ssz": true, "@ethereumjs/tx>@ethereumjs/rlp": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, "browserify>buffer": true, "browserify>events": true, "browserify>insert-module-globals>is-buffer": true } }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "globals": { + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "browserify>util": true + } + }, "@ethereumjs/tx>ethereum-cryptography": { "globals": { "TextDecoder": true, "crypto": true }, "packages": { - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": true, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": true + "@metamask/key-tree>@noble/hashes": true } }, - "@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { "globals": { - "TextEncoder": true, - "crypto": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@noble/secp256k1": { - "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32": { - "packages": { - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": true, - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true - } - }, - "@ethereumjs/tx>ethereum-cryptography>@scure/bip32>@noble/secp256k1": { - "globals": { - "crypto": true - }, - "packages": { - "browserify>browser-resolve": true + "@metamask/key-tree>@noble/hashes": true } }, "@ethersproject/abi": { @@ -770,16 +774,17 @@ "URL": true, "clearInterval": true, "clearTimeout": true, - "console.error": true, "console.info": true, "console.log": true, "setInterval": true, "setTimeout": true }, "packages": { + "@ethersproject/abi>@ethersproject/address": true, "@ethersproject/contracts": true, "@ethersproject/providers": true, "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/assets-controllers>@metamask/rpc-errors": true, "@metamask/assets-controllers>abort-controller": true, "@metamask/assets-controllers>multiformats": true, "@metamask/base-controller": true, @@ -790,7 +795,6 @@ "browserify>events": true, "eth-json-rpc-filters>async-mutex": true, "eth-query": true, - "eth-rpc-errors": true, "ethereumjs-util": true, "single-call-balance-checker-abi": true, "uuid": true @@ -814,6 +818,12 @@ "superstruct": true } }, + "@metamask/assets-controllers>@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, "@metamask/assets-controllers>abort-controller": { "globals": { "AbortController": true @@ -989,22 +999,74 @@ }, "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "@metamask/scure-bip39": true, "browserify>buffer": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": { + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, "browserify>buffer": true, "browserify>events": true, "ethereumjs-wallet>randombytes": true } }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-keyring-controller>obs-store": { "packages": { "@metamask/eth-token-tracker>safe-event-emitter": true, @@ -1040,25 +1102,17 @@ }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": { "packages": { - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": true, "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1215,7 +1269,7 @@ "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { "packages": { "@ethereumjs/tx>@ethereumjs/util": true, - "@ethereumjs/tx>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true, "bn.js": true, "browserify>buffer": true, @@ -1223,6 +1277,21 @@ "eth-sig-util>tweetnacl-util": true } }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -1547,6 +1616,7 @@ "@metamask/base-controller": true, "@metamask/controller-utils": true, "@metamask/permission-controller>nanoid": true, + "@metamask/utils": true, "deep-freeze-strict": true, "eth-rpc-errors": true, "immer": true, @@ -1563,12 +1633,34 @@ "fetch": true }, "packages": { - "@metamask/base-controller": true, - "@metamask/controller-utils": true, + "@metamask/phishing-controller>@metamask/base-controller": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, "@metamask/phishing-warning>eth-phishing-detect": true, "punycode": true } }, + "@metamask/phishing-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, "@metamask/phishing-warning>eth-phishing-detect": { "packages": { "eslint>optionator>fast-levenshtein": true @@ -1605,8 +1697,14 @@ "TextEncoder": true }, "packages": { - "@metamask/key-tree>@noble/hashes": true, - "@metamask/key-tree>@scure/base": true + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39>@noble/hashes": true + } + }, + "@metamask/scure-bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true } }, "@metamask/signature-controller": { @@ -1706,7 +1804,12 @@ }, "@metamask/subject-metadata-controller": { "packages": { - "@metamask/base-controller": true + "@metamask/subject-metadata-controller>@metamask/base-controller": true + } + }, + "@metamask/subject-metadata-controller>@metamask/base-controller": { + "packages": { + "immer": true } }, "@metamask/utils": { @@ -2484,6 +2587,16 @@ "ethjs-query>babel-runtime>core-js": true } }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>browserify-zlib>pako": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, "browserify>buffer": { "globals": { "console": true @@ -2650,6 +2763,12 @@ "browserify>has>function-bind": true } }, + "browserify>https-browserify": { + "packages": { + "browserify>stream-http": true, + "browserify>url": true + } + }, "browserify>os-browserify": { "globals": { "location": true, @@ -2679,6 +2798,41 @@ "readable-stream": true } }, + "browserify>stream-http": { + "globals": { + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, + "clearTimeout": true, + "fetch": true, + "location.protocol.search": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-http>builtin-status-codes": true, + "browserify>stream-http>readable-stream": true, + "browserify>url": true, + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "browserify>stream-http>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, "browserify>string_decoder": { "packages": { "ethereumjs-wallet>safe-buffer": true @@ -2824,22 +2978,18 @@ "setTimeout": true }, "packages": { - "@metamask/safe-event-emitter": true, - "eth-block-tracker>@metamask/utils": true, + "@metamask/utils": true, + "eth-block-tracker>@metamask/safe-event-emitter": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true } }, - "eth-block-tracker>@metamask/utils": { + "eth-block-tracker>@metamask/safe-event-emitter": { "globals": { - "TextDecoder": true, - "TextEncoder": true + "setTimeout": true }, "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": true + "browserify>events": true } }, "eth-ens-namehash": { @@ -2900,16 +3050,59 @@ "setInterval": true }, "packages": { - "@ethereumjs/tx": true, "@ethereumjs/tx>@ethereumjs/util": true, "browserify>buffer": true, "browserify>crypto-browserify": true, "browserify>events": true, + "eth-lattice-keyring>@ethereumjs/tx": true, "eth-lattice-keyring>bn.js": true, "eth-lattice-keyring>gridplus-sdk": true, "eth-lattice-keyring>rlp": true } }, + "eth-lattice-keyring>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>bn.js": { "globals": { "Buffer": true @@ -2933,12 +3126,12 @@ "setTimeout": true }, "packages": { - "@ethereumjs/common": true, "@ethereumjs/common>crc-32": true, - "@ethereumjs/tx": true, "@ethersproject/abi": true, "bn.js": true, "browserify>buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, "eth-lattice-keyring>gridplus-sdk>bech32": true, "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, "eth-lattice-keyring>gridplus-sdk>bitwise": true, @@ -2955,6 +3148,65 @@ "lodash": true } }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, "eth-lattice-keyring>gridplus-sdk>bignumber.js": { "globals": { "crypto": true, @@ -3084,22 +3336,14 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": true, "eth-sig-util>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ethereumjs-wallet>safe-buffer": true, "ganache>secp256k1>elliptic": true } }, - "eth-sig-util>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "eth-sig-util>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3138,21 +3382,13 @@ "bn.js": true, "browserify>assert": true, "browserify>buffer": true, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": true, "ethereumjs-abi>ethereumjs-util>ethjs-util": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, "ganache>secp256k1>elliptic": true } }, - "ethereumjs-abi>ethereumjs-util>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-abi>ethereumjs-util>ethjs-util": { "packages": { "browserify>buffer": true, @@ -3337,22 +3573,14 @@ "ethereumjs-wallet>safe-buffer": true } }, - "ethereumjs-wallet>ethereum-cryptography": { - "packages": { - "browserify>buffer": true, - "ethereumjs-util>ethereum-cryptography>keccak": true, - "ethereumjs-util>ethereum-cryptography>secp256k1": true, - "ethereumjs-wallet>randombytes": true - } - }, "ethereumjs-wallet>ethereumjs-util": { "packages": { "bn.js": true, "browserify>assert": true, "browserify>buffer": true, "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, "ethereumjs-util>rlp": true, - "ethereumjs-wallet>ethereum-cryptography": true, "ethereumjs-wallet>ethereumjs-util>ethjs-util": true, "ganache>secp256k1>elliptic": true } @@ -4136,34 +4364,6 @@ "react": true } }, - "react-transition-group": { - "globals": { - "clearTimeout": true, - "setTimeout": true - }, - "packages": { - "prop-types": true, - "react": true, - "react-dom": true, - "react-transition-group>chain-function": true, - "react-transition-group>dom-helpers": true, - "react-transition-group>warning": true - } - }, - "react-transition-group>dom-helpers": { - "globals": { - "document": true, - "setTimeout": true - }, - "packages": { - "@babel/runtime": true - } - }, - "react-transition-group>warning": { - "globals": { - "console": true - } - }, "readable-stream": { "packages": { "browserify>browser-resolve": true, diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json new file mode 100644 index 000000000..9de49f001 --- /dev/null +++ b/lavamoat/browserify/mmi/policy.json @@ -0,0 +1,4742 @@ +{ + "resources": { + "@babel/runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, + "@babel/runtime>regenerator-runtime": { + "globals": { + "regeneratorRuntime": "write" + } + }, + "@download/blockies": { + "globals": { + "document.createElement": true + } + }, + "@ensdomains/content-hash": { + "globals": { + "console.warn": true + }, + "packages": { + "@ensdomains/content-hash>cids": true, + "@ensdomains/content-hash>js-base64": true, + "@ensdomains/content-hash>multicodec": true, + "@ensdomains/content-hash>multihashes": true, + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>cids": { + "packages": { + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>multicodec": true, + "@ensdomains/content-hash>cids>multihashes": true, + "@ensdomains/content-hash>cids>uint8arrays": true + } + }, + "@ensdomains/content-hash>cids>multibase": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true + } + }, + "@ensdomains/content-hash>cids>multicodec": { + "packages": { + "@ensdomains/content-hash>cids>multicodec>varint": true, + "@ensdomains/content-hash>cids>uint8arrays": true + } + }, + "@ensdomains/content-hash>cids>multihashes": { + "packages": { + "@ensdomains/content-hash>cids>multibase": true, + "@ensdomains/content-hash>cids>uint8arrays": true, + "@ensdomains/content-hash>multihashes>varint": true + } + }, + "@ensdomains/content-hash>cids>uint8arrays": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "@ensdomains/content-hash>cids>multibase": true + } + }, + "@ensdomains/content-hash>js-base64": { + "globals": { + "Base64": "write", + "TextDecoder": true, + "TextEncoder": true, + "atob": true, + "btoa": true, + "define": true + }, + "packages": { + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multicodec": { + "packages": { + "@ensdomains/content-hash>multicodec>uint8arrays": true, + "@ensdomains/content-hash>multicodec>varint": true + } + }, + "@ensdomains/content-hash>multicodec>uint8arrays": { + "packages": { + "@ensdomains/content-hash>multicodec>uint8arrays>multibase": true, + "@ensdomains/content-hash>multihashes>web-encoding": true + } + }, + "@ensdomains/content-hash>multicodec>uint8arrays>multibase": { + "packages": { + "@ensdomains/content-hash>cids>multibase>@multiformats/base-x": true, + "@ensdomains/content-hash>multihashes>web-encoding": true + } + }, + "@ensdomains/content-hash>multihashes": { + "packages": { + "@ensdomains/content-hash>multihashes>multibase": true, + "@ensdomains/content-hash>multihashes>varint": true, + "@ensdomains/content-hash>multihashes>web-encoding": true, + "browserify>buffer": true + } + }, + "@ensdomains/content-hash>multihashes>multibase": { + "packages": { + "@ensdomains/content-hash>multihashes>web-encoding": true, + "browserify>buffer": true, + "ethereumjs-wallet>bs58check>bs58>base-x": true + } + }, + "@ensdomains/content-hash>multihashes>web-encoding": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>util": true + } + }, + "@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "@ethereumjs/common>crc-32": { + "globals": { + "DO_NOT_EXPORT_CRC": true, + "define": true + } + }, + "@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@chainsafe/ssz": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethereumjs/tx>ethereum-cryptography": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "browserify>buffer": true + } + }, + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "@ethereumjs/tx>@chainsafe/ssz>@chainsafe/as-sha256": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/rlp": { + "globals": { + "TextEncoder": true + } + }, + "@ethereumjs/tx>@ethereumjs/util": { + "globals": { + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@chainsafe/ssz": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": true, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": true, + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>@ethereumjs/util>micro-ftch": { + "globals": { + "Headers": true, + "TextDecoder": true, + "URL": true, + "btoa": true, + "fetch": true + }, + "packages": { + "browserify>browserify-zlib": true, + "browserify>buffer": true, + "browserify>https-browserify": true, + "browserify>process": true, + "browserify>stream-http": true, + "browserify>url": true, + "browserify>util": true + } + }, + "@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethereumjs/tx>ethereum-cryptography>@noble/curves": { + "globals": { + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@noble/hashes": true + } + }, + "@ethersproject/abi": { + "globals": { + "console.log": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/hash": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/abi>@ethersproject/address": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true, + "@ethersproject/providers>@ethersproject/rlp": true + } + }, + "@ethersproject/abi>@ethersproject/bytes": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/abi>@ethersproject/constants": { + "packages": { + "@ethersproject/bignumber": true + } + }, + "@ethersproject/abi>@ethersproject/hash": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/providers>@ethersproject/base64": true + } + }, + "@ethersproject/abi>@ethersproject/keccak256": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256>js-sha3": true + } + }, + "@ethersproject/abi>@ethersproject/keccak256>js-sha3": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true + } + }, + "@ethersproject/abi>@ethersproject/logger": { + "globals": { + "console": true + } + }, + "@ethersproject/abi>@ethersproject/properties": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/abi>@ethersproject/strings": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/bignumber": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber>bn.js": true + } + }, + "@ethersproject/bignumber>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@ethersproject/contracts": { + "globals": { + "setTimeout": true + }, + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true, + "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/abstract-provider": true + } + }, + "@ethersproject/hdnode": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true + } + }, + "@ethersproject/hdnode>@ethersproject/abstract-signer": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hdnode>@ethersproject/basex": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/properties": true + } + }, + "@ethersproject/hdnode>@ethersproject/pbkdf2": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@ethersproject/hdnode>@ethersproject/sha2": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "ethereumjs-util>ethereum-cryptography>hash.js": true + } + }, + "@ethersproject/hdnode>@ethersproject/signing-key": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "ganache>secp256k1>elliptic": true + } + }, + "@ethersproject/hdnode>@ethersproject/transactions": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/providers>@ethersproject/rlp": true + } + }, + "@ethersproject/hdnode>@ethersproject/wordlists": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/hash": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true + } + }, + "@ethersproject/providers": { + "globals": { + "WebSocket": true, + "clearInterval": true, + "clearTimeout": true, + "console.log": true, + "console.warn": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/hash": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/abstract-provider": true, + "@ethersproject/providers>@ethersproject/base64": true, + "@ethersproject/providers>@ethersproject/networks": true, + "@ethersproject/providers>@ethersproject/random": true, + "@ethersproject/providers>@ethersproject/web": true, + "@ethersproject/providers>bech32": true + } + }, + "@ethersproject/providers>@ethersproject/abstract-provider": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/bignumber": true + } + }, + "@ethersproject/providers>@ethersproject/base64": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true + } + }, + "@ethersproject/providers>@ethersproject/networks": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/providers>@ethersproject/random": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/providers>@ethersproject/rlp": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true + } + }, + "@ethersproject/providers>@ethersproject/web": { + "globals": { + "clearTimeout": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/providers>@ethersproject/base64": true + } + }, + "@formatjs/intl-relativetimeformat": { + "globals": { + "Intl": true + }, + "packages": { + "@formatjs/intl-relativetimeformat>@formatjs/intl-utils": true + } + }, + "@formatjs/intl-relativetimeformat>@formatjs/intl-utils": { + "globals": { + "Intl.getCanonicalLocales": true + } + }, + "@keystonehq/bc-ur-registry-eth": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": true, + "@keystonehq/bc-ur-registry-eth>hdkey": true, + "browserify>buffer": true, + "uuid": true + } + }, + "@keystonehq/bc-ur-registry-eth>@keystonehq/bc-ur-registry": { + "globals": { + "define": true + }, + "packages": { + "@ngraveio/bc-ur": true, + "browserify>buffer": true, + "ethereumjs-wallet>bs58check": true, + "wait-on>rxjs>tslib": true + } + }, + "@keystonehq/bc-ur-registry-eth>hdkey": { + "packages": { + "browserify>assert": true, + "browserify>crypto-browserify": true, + "ethereumjs-util>ethereum-cryptography>secp256k1": true, + "ethereumjs-wallet>bs58check": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@keystonehq/metamask-airgapped-keyring": { + "packages": { + "@ethereumjs/tx": true, + "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": true, + "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": true, + "browserify>buffer": true, + "browserify>events": true, + "ethereumjs-util>rlp": true, + "uuid": true + } + }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring": { + "packages": { + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@keystonehq/bc-ur-registry-eth": true, + "@keystonehq/bc-ur-registry-eth>hdkey": true, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": true, + "browserify>buffer": true, + "uuid": true + } + }, + "@keystonehq/metamask-airgapped-keyring>@keystonehq/base-eth-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, + "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store": { + "packages": { + "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2": true, + "@metamask/safe-event-emitter": true, + "browserify>stream-browserify": true + } + }, + "@keystonehq/metamask-airgapped-keyring>@metamask/obs-store>through2": { + "packages": { + "browserify>process": true, + "browserify>util": true, + "readable-stream": true, + "watchify>xtend": true + } + }, + "@material-ui/core": { + "globals": { + "Image": true, + "_formatMuiErrorMessage": true, + "addEventListener": true, + "clearInterval": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "getSelection": true, + "innerHeight": true, + "innerWidth": true, + "matchMedia": true, + "navigator": true, + "performance.now": true, + "removeEventListener": true, + "requestAnimationFrame": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles": true, + "@material-ui/core>@material-ui/system": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "@material-ui/core>popper.js": true, + "@material-ui/core>react-transition-group": true, + "prop-types": true, + "prop-types>react-is": true, + "react": true, + "react-dom": true, + "react-redux>hoist-non-react-statics": true + } + }, + "@material-ui/core>@material-ui/styles": { + "globals": { + "console.error": true, + "console.warn": true, + "document.createComment": true, + "document.head": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": true, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": true, + "@material-ui/core>@material-ui/styles>jss-plugin-global": true, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": true, + "@material-ui/core>@material-ui/styles>jss-plugin-props-sort": true, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": true, + "@material-ui/core>@material-ui/utils": true, + "@material-ui/core>clsx": true, + "prop-types": true, + "react": true, + "react-redux>hoist-non-react-statics": true + } + }, + "@material-ui/core>@material-ui/styles>jss": { + "globals": { + "CSS": true, + "document.createElement": true, + "document.querySelector": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true, + "react-router-dom>tiny-warning": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case": { + "packages": { + "@material-ui/core>@material-ui/styles>jss-plugin-camel-case>hyphenate-style-name": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-default-unit": { + "globals": { + "CSS": true + }, + "packages": { + "@material-ui/core>@material-ui/styles>jss": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-global": { + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-nested": { + "packages": { + "@babel/runtime": true, + "react-router-dom>tiny-warning": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-rule-value-function": { + "packages": { + "@material-ui/core>@material-ui/styles>jss": true, + "react-router-dom>tiny-warning": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer": { + "packages": { + "@material-ui/core>@material-ui/styles>jss": true, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": true + } + }, + "@material-ui/core>@material-ui/styles>jss-plugin-vendor-prefixer>css-vendor": { + "globals": { + "document.createElement": true, + "document.documentElement": true, + "getComputedStyle": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": true + } + }, + "@material-ui/core>@material-ui/styles>jss>is-in-browser": { + "globals": { + "document": true + } + }, + "@material-ui/core>@material-ui/system": { + "globals": { + "console.error": true + }, + "packages": { + "@babel/runtime": true, + "@material-ui/core>@material-ui/utils": true, + "prop-types": true + } + }, + "@material-ui/core>@material-ui/utils": { + "packages": { + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true + } + }, + "@material-ui/core>popper.js": { + "globals": { + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator": true, + "requestAnimationFrame": true, + "setTimeout": true + } + }, + "@material-ui/core>react-transition-group": { + "globals": { + "Element": true, + "setTimeout": true + }, + "packages": { + "@material-ui/core>react-transition-group>dom-helpers": true, + "prop-types": true, + "react": true, + "react-dom": true + } + }, + "@material-ui/core>react-transition-group>dom-helpers": { + "packages": { + "@babel/runtime": true + } + }, + "@metamask-institutional/custody-controller": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-controller>@metamask-institutional/custody-keyring": true, + "@metamask/obs-store": true + } + }, + "@metamask-institutional/custody-controller>@metamask-institutional/custody-keyring": { + "globals": { + "console.log": true, + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/sdk>@metamask-institutional/types": true, + "@metamask/obs-store": true, + "browserify>crypto-browserify": true, + "browserify>events": true, + "gulp-sass>lodash.clonedeep": true + } + }, + "@metamask-institutional/custody-keyring": { + "globals": { + "console.log": true, + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/sdk>@metamask-institutional/types": true, + "@metamask/obs-store": true, + "browserify>crypto-browserify": true, + "browserify>events": true, + "gulp-sass>lodash.clonedeep": true + } + }, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": { + "globals": { + "console.log": true, + "fetch": true + } + }, + "@metamask-institutional/extension": { + "globals": { + "console.log": true, + "fetch": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-controller": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/sdk>@metamask-institutional/types": true + } + }, + "@metamask-institutional/institutional-features": { + "globals": { + "chrome.runtime.id": true, + "console.log": true, + "fetch": true, + "setInterval": true + }, + "packages": { + "@metamask-institutional/institutional-features>@auth0/auth0-spa-js": true, + "@metamask-institutional/institutional-features>@metamask-institutional/custody-keyring": true, + "@metamask-institutional/sdk>@metamask-institutional/simplecache": true, + "@metamask/obs-store": true, + "browserify>events": true + } + }, + "@metamask-institutional/institutional-features>@auth0/auth0-spa-js": { + "globals": { + "AbortController": true, + "Blob": true, + "MessageChannel": true, + "TextEncoder": true, + "URL.createObjectURL": true, + "URLSearchParams": true, + "Worker": true, + "addEventListener": true, + "atob": true, + "btoa": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "crossOriginIsolated": true, + "crypto": true, + "document.body.appendChild": true, + "document.body.contains": true, + "document.body.removeChild": true, + "document.cookie": "write", + "document.createElement": true, + "fetch": true, + "innerHeight": true, + "innerWidth": true, + "localStorage": true, + "location.assign": true, + "location.href": true, + "location.origin": true, + "location.protocol": true, + "open": true, + "removeEventListener": true, + "screenX": true, + "screenY": true, + "sessionStorage": true, + "setInterval": true, + "setTimeout": true + } + }, + "@metamask-institutional/institutional-features>@metamask-institutional/custody-keyring": { + "globals": { + "console.log": true, + "console.warn": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask-institutional/custody-keyring>@metamask-institutional/configuration-client": true, + "@metamask-institutional/sdk": true, + "@metamask-institutional/sdk>@metamask-institutional/types": true, + "@metamask/obs-store": true, + "browserify>crypto-browserify": true, + "browserify>events": true, + "gulp-sass>lodash.clonedeep": true + } + }, + "@metamask-institutional/portfolio-dashboard": { + "globals": { + "console.log": true, + "fetch": true + } + }, + "@metamask-institutional/sdk": { + "globals": { + "URLSearchParams": true, + "console.debug": true, + "console.log": true, + "console.warn": true, + "fetch": true + }, + "packages": { + "@metamask-institutional/sdk>@metamask-institutional/simplecache": true, + "@metamask-institutional/sdk>bignumber.js": true, + "@metamask-institutional/sdk>jsonwebtoken": true, + "browserify>crypto-browserify": true, + "browserify>events": true + } + }, + "@metamask-institutional/sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask-institutional/sdk>jsonwebtoken": { + "packages": { + "@metamask-institutional/sdk>jsonwebtoken>jws": true, + "browserify>buffer": true, + "browserify>crypto-browserify": true, + "browserify>process": true, + "lodash": true, + "mocha>ms": true, + "semver": true + } + }, + "@metamask-institutional/sdk>jsonwebtoken>jws": { + "packages": { + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa": { + "packages": { + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>buffer-equal-constant-time": true, + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>ecdsa-sig-formatter": true, + "browserify>crypto-browserify": true, + "browserify>util": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>buffer-equal-constant-time": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask-institutional/sdk>jsonwebtoken>jws>jwa>ecdsa-sig-formatter": { + "packages": { + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask-institutional/transaction-update": { + "globals": { + "clearInterval": true, + "console.info": true, + "console.log": true, + "setInterval": true + }, + "packages": { + "@metamask-institutional/sdk": true, + "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": true, + "@metamask/obs-store": true, + "browserify>events": true, + "ethereumjs-util": true + } + }, + "@metamask-institutional/transaction-update>@metamask-institutional/websocket-client": { + "globals": { + "WebSocket": true, + "clearTimeout": true, + "console.log": true, + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, + "@metamask/address-book-controller": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true + } + }, + "@metamask/announcement-controller": { + "packages": { + "@metamask/base-controller": true + } + }, + "@metamask/approval-controller": { + "packages": { + "@metamask/approval-controller>nanoid": true, + "@metamask/base-controller": true, + "eth-rpc-errors": true + } + }, + "@metamask/approval-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/assets-controllers": { + "globals": { + "Headers": true, + "URL": true, + "clearInterval": true, + "clearTimeout": true, + "console.info": true, + "console.log": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/contracts": true, + "@ethersproject/providers": true, + "@metamask/assets-controllers>@metamask/abi-utils": true, + "@metamask/assets-controllers>@metamask/rpc-errors": true, + "@metamask/assets-controllers>abort-controller": true, + "@metamask/assets-controllers>multiformats": true, + "@metamask/base-controller": true, + "@metamask/contract-metadata": true, + "@metamask/controller-utils": true, + "@metamask/metamask-eth-abis": true, + "@metamask/utils": true, + "browserify>events": true, + "eth-json-rpc-filters>async-mutex": true, + "eth-query": true, + "ethereumjs-util": true, + "single-call-balance-checker-abi": true, + "uuid": true + } + }, + "@metamask/assets-controllers>@metamask/abi-utils": { + "packages": { + "@metamask/assets-controllers>@metamask/abi-utils>@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/assets-controllers>@metamask/abi-utils>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/assets-controllers>@metamask/rpc-errors": { + "packages": { + "@metamask/utils": true, + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "@metamask/assets-controllers>abort-controller": { + "globals": { + "AbortController": true + } + }, + "@metamask/assets-controllers>multiformats": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "console.warn": true + } + }, + "@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/browser-passworder": { + "globals": { + "btoa": true, + "crypto.getRandomValues": true, + "crypto.subtle.decrypt": true, + "crypto.subtle.deriveKey": true, + "crypto.subtle.encrypt": true, + "crypto.subtle.exportKey": true, + "crypto.subtle.importKey": true + }, + "packages": { + "browserify>buffer": true + } + }, + "@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser": { + "globals": { + "console.error": true, + "console.log": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": true + } + }, + "@metamask/controller-utils>@spruceid/siwe-parser>apg-js": { + "globals": { + "mode": true + }, + "packages": { + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true + } + }, + "@metamask/controllers>web3": { + "globals": { + "XMLHttpRequest": true + } + }, + "@metamask/controllers>web3-provider-engine>cross-fetch>node-fetch": { + "globals": { + "fetch": true + } + }, + "@metamask/controllers>web3-provider-engine>eth-json-rpc-middleware>node-fetch": { + "globals": { + "fetch": true + } + }, + "@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-middleware": { + "globals": { + "URL": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-middleware>@metamask/utils": true, + "@metamask/eth-json-rpc-middleware>pify": true, + "@metamask/eth-json-rpc-middleware>safe-stable-stringify": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-middleware>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/eth-keyring-controller": { + "packages": { + "@metamask/browser-passworder": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": true, + "@metamask/eth-keyring-controller>obs-store": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "browserify>events": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring": { + "globals": { + "TextEncoder": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/scure-bip39": true, + "browserify>buffer": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip32": { + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@noble/secp256k1": true, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": true, + "@metamask/key-tree>@scure/base": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-hd-keyring>ethereum-cryptography>@scure/bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "browserify>buffer": true, + "browserify>events": true, + "ethereumjs-wallet>randombytes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-keyring-controller>@metamask/eth-simple-keyring>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-keyring-controller>obs-store": { + "packages": { + "@metamask/eth-token-tracker>safe-event-emitter": true, + "watchify>xtend": true + } + }, + "@metamask/eth-ledger-bridge-keyring": { + "globals": { + "addEventListener": true, + "console.log": true, + "document.createElement": true, + "document.head.appendChild": true, + "fetch": true, + "removeEventListener": true + }, + "packages": { + "@ethereumjs/tx": true, + "@metamask/eth-ledger-bridge-keyring>eth-sig-util": true, + "@metamask/eth-ledger-bridge-keyring>hdkey": true, + "browserify>buffer": true, + "browserify>events": true, + "ethereumjs-util": true + } + }, + "@metamask/eth-ledger-bridge-keyring>eth-sig-util": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": true, + "browserify>buffer": true, + "eth-sig-util>tweetnacl": true, + "eth-sig-util>tweetnacl-util": true, + "ethereumjs-abi": true + } + }, + "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true, + "ganache>secp256k1>elliptic": true + } + }, + "@metamask/eth-ledger-bridge-keyring>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "@metamask/eth-ledger-bridge-keyring>hdkey": { + "packages": { + "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": true, + "@metamask/eth-trezor-keyring>hdkey>coinstring": true, + "browserify>assert": true, + "browserify>crypto-browserify": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-ledger-bridge-keyring>hdkey>secp256k1": { + "packages": { + "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": true, + "bn.js": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-wallet>safe-buffer": true, + "ganache>secp256k1>elliptic": true + } + }, + "@metamask/eth-token-tracker": { + "globals": { + "console.warn": true + }, + "packages": { + "@babel/runtime": true, + "@metamask/eth-token-tracker>deep-equal": true, + "@metamask/eth-token-tracker>eth-block-tracker": true, + "@metamask/eth-token-tracker>ethjs": true, + "@metamask/eth-token-tracker>human-standard-token-abi": true, + "@metamask/eth-token-tracker>safe-event-emitter": true, + "ethjs-contract": true, + "ethjs-query": true + } + }, + "@metamask/eth-token-tracker>deep-equal": { + "packages": { + "@metamask/eth-token-tracker>deep-equal>is-arguments": true, + "@metamask/eth-token-tracker>deep-equal>is-date-object": true, + "@ngraveio/bc-ur>assert>object-is": true, + "globalthis>define-properties>object-keys": true, + "string.prototype.matchall>es-abstract>is-regex": true, + "string.prototype.matchall>regexp.prototype.flags": true + } + }, + "@metamask/eth-token-tracker>deep-equal>is-arguments": { + "packages": { + "koa>is-generator-function>has-tostringtag": true, + "string.prototype.matchall>call-bind": true + } + }, + "@metamask/eth-token-tracker>deep-equal>is-date-object": { + "packages": { + "koa>is-generator-function>has-tostringtag": true + } + }, + "@metamask/eth-token-tracker>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-token-tracker>eth-block-tracker>pify": true, + "@metamask/eth-token-tracker>safe-event-emitter": true, + "eth-query": true + } + }, + "@metamask/eth-token-tracker>ethjs": { + "globals": { + "clearInterval": true, + "setInterval": true + }, + "packages": { + "@metamask/eth-token-tracker>ethjs>bn.js": true, + "@metamask/eth-token-tracker>ethjs>ethjs-abi": true, + "@metamask/eth-token-tracker>ethjs>ethjs-contract": true, + "@metamask/eth-token-tracker>ethjs>ethjs-query": true, + "browserify>buffer": true, + "ethjs>ethjs-filter": true, + "ethjs>ethjs-provider-http": true, + "ethjs>ethjs-unit": true, + "ethjs>ethjs-util": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "@metamask/eth-token-tracker>ethjs>ethjs-abi": { + "packages": { + "@metamask/eth-token-tracker>ethjs>bn.js": true, + "browserify>buffer": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "@metamask/eth-token-tracker>ethjs>ethjs-contract": { + "packages": { + "@metamask/eth-token-tracker>ethjs>ethjs-contract>ethjs-abi": true, + "ethjs-query>babel-runtime": true, + "ethjs>ethjs-filter": true, + "ethjs>ethjs-util": true, + "ethjs>js-sha3": true, + "promise-to-callback": true + } + }, + "@metamask/eth-token-tracker>ethjs>ethjs-contract>ethjs-abi": { + "packages": { + "@metamask/eth-token-tracker>ethjs>bn.js": true, + "browserify>buffer": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "@metamask/eth-token-tracker>ethjs>ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "ethjs-query>babel-runtime": true, + "ethjs-query>ethjs-format": true, + "ethjs-query>ethjs-rpc": true, + "promise-to-callback": true + } + }, + "@metamask/eth-token-tracker>safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>util": true, + "webpack>events": true + } + }, + "@metamask/eth-trezor-keyring": { + "globals": { + "setTimeout": true + }, + "packages": { + "@ethereumjs/tx": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web": true, + "@metamask/eth-trezor-keyring>hdkey": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": { + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": true, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": true, + "bn.js": true, + "browserify>buffer": true, + "eth-sig-util>tweetnacl": true, + "eth-sig-util>tweetnacl-util": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-plugin-ethereum": { + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web": { + "globals": { + "addEventListener": true, + "btoa": true, + "chrome": true, + "clearInterval": true, + "clearTimeout": true, + "console.warn": true, + "document.body": true, + "document.createElement": true, + "document.createTextNode": true, + "document.getElementById": true, + "document.querySelectorAll": true, + "navigator.usb.requestDevice": true, + "open": true, + "removeEventListener": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/utils": true, + "browserify>events": true, + "wait-on>rxjs>tslib": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect": { + "globals": { + "__TREZOR_CONNECT_SRC": true, + "chrome": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "location": true, + "navigator": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport": true, + "wait-on>rxjs>tslib": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport": { + "globals": { + "fetch": true, + "navigator.usb": true, + "onconnect": "write", + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>bytebuffer": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>long": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/utils": true, + "browserify>buffer": true, + "browserify>events": true, + "lavamoat>json-stable-stringify": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>bytebuffer": { + "globals": { + "console": true, + "define": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>bytebuffer>long": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>bytebuffer>long": { + "globals": { + "define": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>long": { + "globals": { + "WebAssembly.Instance": true, + "WebAssembly.Module": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs": { + "globals": { + "process": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/aspromise": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/base64": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/codegen": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/eventemitter": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/fetch": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/float": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/inquire": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/path": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/pool": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/utf8": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/codegen": { + "globals": { + "console.log": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/fetch": { + "globals": { + "XMLHttpRequest": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/aspromise": true, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/connect>@trezor/transport>protobufjs>@protobufjs/inquire": true + } + }, + "@metamask/eth-trezor-keyring>@trezor/connect-web>@trezor/utils": { + "globals": { + "AbortController": true, + "clearTimeout": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true + } + }, + "@metamask/eth-trezor-keyring>hdkey": { + "packages": { + "@metamask/eth-trezor-keyring>hdkey>coinstring": true, + "@metamask/eth-trezor-keyring>hdkey>secp256k1": true, + "browserify>assert": true, + "browserify>crypto-browserify": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-trezor-keyring>hdkey>coinstring": { + "packages": { + "@metamask/eth-trezor-keyring>hdkey>coinstring>bs58": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true + } + }, + "@metamask/eth-trezor-keyring>hdkey>secp256k1": { + "packages": { + "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": true, + "bn.js": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-wallet>safe-buffer": true, + "ganache>secp256k1>elliptic": true + } + }, + "@metamask/eth-trezor-keyring>hdkey>secp256k1>bip66": { + "packages": { + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/etherscan-link": { + "globals": { + "URL": true + } + }, + "@metamask/gas-fee-controller": { + "globals": { + "clearInterval": true, + "console.error": true, + "setInterval": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "eth-query": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true, + "uuid": true + } + }, + "@metamask/jazzicon": { + "globals": { + "document.createElement": true, + "document.createElementNS": true + }, + "packages": { + "@metamask/jazzicon>color": true, + "@metamask/jazzicon>mersenne-twister": true + } + }, + "@metamask/jazzicon>color": { + "packages": { + "@metamask/jazzicon>color>clone": true, + "@metamask/jazzicon>color>color-convert": true, + "@metamask/jazzicon>color>color-string": true + } + }, + "@metamask/jazzicon>color>clone": { + "packages": { + "browserify>buffer": true + } + }, + "@metamask/jazzicon>color>color-convert": { + "packages": { + "@metamask/jazzicon>color>color-convert>color-name": true + } + }, + "@metamask/jazzicon>color>color-string": { + "packages": { + "jest-canvas-mock>moo-color>color-name": true + } + }, + "@metamask/key-tree": { + "packages": { + "@metamask/key-tree>@metamask/utils": true, + "@metamask/key-tree>@noble/ed25519": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/key-tree>@noble/secp256k1": true, + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39": true + } + }, + "@metamask/key-tree>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/key-tree>@noble/ed25519": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/key-tree>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/key-tree>@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@metamask/key-tree>@scure/base": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + } + }, + "@metamask/logo": { + "globals": { + "addEventListener": true, + "document.body.appendChild": true, + "document.createElementNS": true, + "innerHeight": true, + "innerWidth": true, + "requestAnimationFrame": true + }, + "packages": { + "@metamask/logo>gl-mat4": true, + "@metamask/logo>gl-vec3": true + } + }, + "@metamask/message-manager": { + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/message-manager>jsonschema": true, + "browserify>buffer": true, + "browserify>events": true, + "eth-sig-util": true, + "ethereumjs-util": true, + "uuid": true + } + }, + "@metamask/message-manager>jsonschema": { + "packages": { + "browserify>url": true + } + }, + "@metamask/notification-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/obs-store": { + "packages": { + "@metamask/obs-store>through2": true, + "@metamask/safe-event-emitter": true, + "browserify>stream-browserify": true + } + }, + "@metamask/obs-store>through2": { + "packages": { + "browserify>process": true, + "browserify>util": true, + "readable-stream": true, + "watchify>xtend": true + } + }, + "@metamask/permission-controller": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/permission-controller>nanoid": true, + "@metamask/utils": true, + "deep-freeze-strict": true, + "eth-rpc-errors": true, + "immer": true, + "json-rpc-engine": true + } + }, + "@metamask/permission-controller>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/phishing-controller": { + "globals": { + "fetch": true + }, + "packages": { + "@metamask/phishing-controller>@metamask/base-controller": true, + "@metamask/phishing-controller>@metamask/controller-utils": true, + "@metamask/phishing-warning>eth-phishing-detect": true, + "punycode": true + } + }, + "@metamask/phishing-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/phishing-controller>@metamask/controller-utils": { + "globals": { + "URL": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/controller-utils>@spruceid/siwe-parser": true, + "@metamask/utils": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/phishing-warning>eth-phishing-detect": { + "packages": { + "eslint>optionator>fast-levenshtein": true + } + }, + "@metamask/rpc-methods": { + "packages": { + "@metamask/key-tree": true, + "@metamask/key-tree>@noble/hashes": true, + "@metamask/utils": true, + "superstruct": true + } + }, + "@metamask/rpc-methods-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/rpc-methods>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, + "@metamask/scure-bip39": { + "globals": { + "TextEncoder": true + }, + "packages": { + "@metamask/key-tree>@scure/base": true, + "@metamask/scure-bip39>@noble/hashes": true + } + }, + "@metamask/scure-bip39>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "@metamask/signature-controller": { + "globals": { + "console.info": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/message-manager": true, + "browserify>buffer": true, + "browserify>events": true, + "eth-rpc-errors": true, + "ethereumjs-util": true + } + }, + "@metamask/smart-transactions-controller": { + "globals": { + "URLSearchParams": true, + "clearInterval": true, + "console.error": true, + "console.log": true, + "fetch": true, + "setInterval": true + }, + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/bignumber": true, + "@ethersproject/providers": true, + "@metamask/smart-transactions-controller>@metamask/base-controller": true, + "@metamask/smart-transactions-controller>@metamask/controller-utils": true, + "@metamask/smart-transactions-controller>bignumber.js": true, + "@metamask/smart-transactions-controller>isomorphic-fetch": true, + "fast-json-patch": true, + "lodash": true + } + }, + "@metamask/smart-transactions-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controller-utils": { + "globals": { + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/smart-transactions-controller>isomorphic-fetch": true, + "browserify>buffer": true, + "eslint>fast-deep-equal": true, + "eth-ens-namehash": true, + "ethereumjs-util": true, + "ethjs>ethjs-unit": true + } + }, + "@metamask/smart-transactions-controller>@metamask/controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/smart-transactions-controller>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@metamask/smart-transactions-controller>isomorphic-fetch": { + "globals": { + "fetch.bind": true + }, + "packages": { + "@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": true + } + }, + "@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": { + "globals": { + "Blob": true, + "FileReader": true, + "FormData": true, + "URLSearchParams.prototype.isPrototypeOf": true, + "XMLHttpRequest": true, + "define": true, + "setTimeout": true + } + }, + "@metamask/snaps-controllers-flask>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/snaps-controllers>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "@metamask/subject-metadata-controller": { + "packages": { + "@metamask/subject-metadata-controller>@metamask/base-controller": true + } + }, + "@metamask/subject-metadata-controller>@metamask/base-controller": { + "packages": { + "immer": true + } + }, + "@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@ngraveio/bc-ur": { + "packages": { + "@ngraveio/bc-ur>@apocentre/alias-sampling": true, + "@ngraveio/bc-ur>bignumber.js": true, + "@ngraveio/bc-ur>cbor-sync": true, + "@ngraveio/bc-ur>crc": true, + "@ngraveio/bc-ur>jsbi": true, + "addons-linter>sha.js": true, + "browserify>assert": true, + "browserify>buffer": true + } + }, + "@ngraveio/bc-ur>assert>object-is": { + "packages": { + "globalthis>define-properties": true, + "string.prototype.matchall>call-bind": true + } + }, + "@ngraveio/bc-ur>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@ngraveio/bc-ur>cbor-sync": { + "globals": { + "define": true + }, + "packages": { + "browserify>buffer": true + } + }, + "@ngraveio/bc-ur>crc": { + "packages": { + "browserify>buffer": true + } + }, + "@ngraveio/bc-ur>jsbi": { + "globals": { + "define": true + } + }, + "@popperjs/core": { + "globals": { + "Element": true, + "HTMLElement": true, + "ShadowRoot": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator.userAgent": true + } + }, + "@reduxjs/toolkit": { + "globals": { + "AbortController": true, + "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__": true, + "__REDUX_DEVTOOLS_EXTENSION__": true, + "console.error": true, + "console.info": true, + "console.warn": true + }, + "packages": { + "@reduxjs/toolkit>reselect": true, + "immer": true, + "redux": true, + "redux-thunk": true + } + }, + "@segment/loosely-validate-event": { + "packages": { + "@segment/loosely-validate-event>component-type": true, + "@segment/loosely-validate-event>join-component": true, + "browserify>assert": true, + "browserify>buffer": true + } + }, + "@sentry/browser": { + "globals": { + "XMLHttpRequest": true, + "setTimeout": true + }, + "packages": { + "@sentry/browser>@sentry/core": true, + "@sentry/browser>tslib": true, + "@sentry/types": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry/core": { + "globals": { + "clearInterval": true, + "setInterval": true + }, + "packages": { + "@sentry/browser>@sentry/core>@sentry/hub": true, + "@sentry/browser>@sentry/core>@sentry/minimal": true, + "@sentry/browser>@sentry/core>tslib": true, + "@sentry/types": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry/core>@sentry/hub": { + "globals": { + "clearInterval": true, + "setInterval": true + }, + "packages": { + "@sentry/browser>@sentry/core>@sentry/hub>tslib": true, + "@sentry/types": true, + "@sentry/utils": true + } + }, + "@sentry/browser>@sentry/core>@sentry/hub>tslib": { + "globals": { + "define": true + } + }, + "@sentry/browser>@sentry/core>@sentry/minimal": { + "packages": { + "@sentry/browser>@sentry/core>@sentry/hub": true, + "@sentry/browser>@sentry/core>@sentry/minimal>tslib": true + } + }, + "@sentry/browser>@sentry/core>@sentry/minimal>tslib": { + "globals": { + "define": true + } + }, + "@sentry/browser>@sentry/core>tslib": { + "globals": { + "define": true + } + }, + "@sentry/browser>tslib": { + "globals": { + "define": true + } + }, + "@sentry/integrations": { + "globals": { + "clearTimeout": true, + "console.error": true, + "console.log": true, + "setTimeout": true + }, + "packages": { + "@sentry/integrations>tslib": true, + "@sentry/types": true, + "@sentry/utils": true, + "localforage": true + } + }, + "@sentry/integrations>tslib": { + "globals": { + "define": true + } + }, + "@sentry/utils": { + "globals": { + "CustomEvent": true, + "DOMError": true, + "DOMException": true, + "Element": true, + "ErrorEvent": true, + "Event": true, + "Headers": true, + "Request": true, + "Response": true, + "XMLHttpRequest.prototype": true, + "clearTimeout": true, + "console.error": true, + "document": true, + "setTimeout": true + }, + "packages": { + "@sentry/utils>tslib": true, + "browserify>process": true + } + }, + "@sentry/utils>tslib": { + "globals": { + "define": true + } + }, + "@truffle/codec": { + "packages": { + "@truffle/codec>@truffle/abi-utils": true, + "@truffle/codec>@truffle/compile-common": true, + "@truffle/codec>big.js": true, + "@truffle/codec>bn.js": true, + "@truffle/codec>cbor": true, + "@truffle/codec>semver": true, + "@truffle/codec>utf8": true, + "@truffle/codec>web3-utils": true, + "browserify>buffer": true, + "browserify>os-browserify": true, + "browserify>util": true, + "lodash": true, + "nock>debug": true + } + }, + "@truffle/codec>@truffle/abi-utils": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case": true, + "@truffle/codec>@truffle/abi-utils>fast-check": true, + "@truffle/codec>web3-utils": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>camel-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>constant-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>dot-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>header-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>is-lower-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>is-upper-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>lower-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>lower-case-first": true, + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>param-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>pascal-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>path-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>sentence-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>snake-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>swap-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>title-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case-first": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>camel-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>constant-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>snake-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>dot-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>header-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>is-lower-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>lower-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>is-upper-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>lower-case-first": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>lower-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>no-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>lower-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>param-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>pascal-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>camel-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case-first": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>path-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>sentence-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case-first": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>snake-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>swap-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>lower-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>title-case": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>no-case": true, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>change-case>upper-case-first": { + "packages": { + "@truffle/codec>@truffle/abi-utils>change-case>upper-case": true + } + }, + "@truffle/codec>@truffle/abi-utils>fast-check": { + "globals": { + "clearTimeout": true, + "console.log": true, + "setTimeout": true + }, + "packages": { + "@truffle/codec>@truffle/abi-utils>fast-check>pure-rand": true, + "browserify>buffer": true + } + }, + "@truffle/codec>@truffle/compile-common": { + "packages": { + "@truffle/codec>@truffle/compile-common>@truffle/error": true, + "@truffle/codec>@truffle/compile-common>colors": true, + "browserify>path-browserify": true + } + }, + "@truffle/codec>@truffle/compile-common>colors": { + "globals": { + "console.log": true + }, + "packages": { + "browserify>os-browserify": true, + "browserify>process": true, + "browserify>util": true + } + }, + "@truffle/codec>big.js": { + "globals": { + "define": true + } + }, + "@truffle/codec>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@truffle/codec>cbor": { + "globals": { + "TextDecoder": true + }, + "packages": { + "@truffle/codec>cbor>bignumber.js": true, + "@truffle/codec>cbor>nofilter": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "browserify>stream-browserify": true, + "browserify>url": true, + "browserify>util": true + } + }, + "@truffle/codec>cbor>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@truffle/codec>cbor>nofilter": { + "packages": { + "browserify>buffer": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, + "@truffle/codec>semver": { + "globals": { + "console.error": true + }, + "packages": { + "browserify>process": true, + "semver>lru-cache": true + } + }, + "@truffle/codec>web3-utils": { + "globals": { + "setTimeout": true + }, + "packages": { + "@truffle/codec>utf8": true, + "@truffle/codec>web3-utils>bn.js": true, + "@truffle/codec>web3-utils>ethereum-bloom-filters": true, + "browserify>buffer": true, + "ethereumjs-util": true, + "ethereumjs-wallet>randombytes": true, + "ethjs>ethjs-unit": true, + "ethjs>number-to-bn": true + } + }, + "@truffle/codec>web3-utils>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@truffle/codec>web3-utils>ethereum-bloom-filters": { + "packages": { + "@truffle/codec>web3-utils>ethereum-bloom-filters>js-sha3": true + } + }, + "@truffle/codec>web3-utils>ethereum-bloom-filters>js-sha3": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true + } + }, + "@truffle/decoder": { + "packages": { + "@truffle/codec": true, + "@truffle/codec>@truffle/abi-utils": true, + "@truffle/codec>@truffle/compile-common": true, + "@truffle/codec>web3-utils": true, + "@truffle/decoder>@truffle/encoder": true, + "@truffle/decoder>@truffle/source-map-utils": true, + "@truffle/decoder>bn.js": true, + "nock>debug": true + } + }, + "@truffle/decoder>@truffle/encoder": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/bignumber": true, + "@truffle/codec": true, + "@truffle/codec>@truffle/abi-utils": true, + "@truffle/codec>@truffle/compile-common": true, + "@truffle/codec>big.js": true, + "@truffle/codec>web3-utils": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs": true, + "@truffle/decoder>@truffle/encoder>bignumber.js": true, + "lodash": true, + "nock>debug": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs": { + "globals": { + "console.log": true, + "console.warn": true, + "registries": true + }, + "packages": { + "@babel/runtime": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/ens": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/resolver": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": true, + "browserify>buffer": true, + "eth-ens-namehash": true, + "ethereumjs-wallet>bs58check>bs58": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>@ensdomains/address-encoder": { + "globals": { + "console": true + }, + "packages": { + "bn.js": true, + "browserify>buffer": true, + "browserify>crypto-browserify": true, + "ethereumjs-util>create-hash>ripemd160": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash": { + "packages": { + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multicodec": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids": { + "packages": { + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>class-is": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multibase": { + "packages": { + "browserify>buffer": true, + "ethereumjs-wallet>bs58check>bs58>base-x": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>cids>multicodec": { + "packages": { + "@ensdomains/content-hash>multihashes>varint": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multicodec": { + "packages": { + "@ensdomains/content-hash>multihashes>varint": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes": { + "packages": { + "@ensdomains/content-hash>multihashes>varint": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>content-hash>multihashes>multibase": { + "packages": { + "browserify>buffer": true, + "ethereumjs-wallet>bs58check>bs58>base-x": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers": { + "packages": { + "@ethersproject/abi": true, + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/constants": true, + "@ethersproject/abi>@ethersproject/hash": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/contracts": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode>@ethersproject/basex": true, + "@ethersproject/hdnode>@ethersproject/sha2": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/hdnode>@ethersproject/wordlists": true, + "@ethersproject/providers": true, + "@ethersproject/providers>@ethersproject/base64": true, + "@ethersproject/providers>@ethersproject/random": true, + "@ethersproject/providers>@ethersproject/rlp": true, + "@ethersproject/providers>@ethersproject/web": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/json-wallets": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/solidity": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/units": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/wallet": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/json-wallets": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/pbkdf2": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/random": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/json-wallets>aes-js": true, + "ethereumjs-util>ethereum-cryptography>scrypt-js": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/json-wallets>aes-js": { + "globals": { + "define": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/solidity": { + "packages": { + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/strings": true, + "@ethersproject/bignumber": true, + "@ethersproject/hdnode>@ethersproject/sha2": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/units": { + "packages": { + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/bignumber": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/wallet": { + "packages": { + "@ethersproject/abi>@ethersproject/address": true, + "@ethersproject/abi>@ethersproject/bytes": true, + "@ethersproject/abi>@ethersproject/hash": true, + "@ethersproject/abi>@ethersproject/keccak256": true, + "@ethersproject/abi>@ethersproject/logger": true, + "@ethersproject/abi>@ethersproject/properties": true, + "@ethersproject/hdnode": true, + "@ethersproject/hdnode>@ethersproject/abstract-signer": true, + "@ethersproject/hdnode>@ethersproject/signing-key": true, + "@ethersproject/hdnode>@ethersproject/transactions": true, + "@ethersproject/providers>@ethersproject/abstract-provider": true, + "@ethersproject/providers>@ethersproject/random": true, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>ethers>@ethersproject/json-wallets": true + } + }, + "@truffle/decoder>@truffle/encoder>@ensdomains/ensjs>js-sha3": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true + } + }, + "@truffle/decoder>@truffle/encoder>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "@truffle/decoder>@truffle/source-map-utils": { + "packages": { + "@truffle/codec": true, + "@truffle/codec>web3-utils": true, + "@truffle/decoder>@truffle/source-map-utils>@truffle/code-utils": true, + "@truffle/decoder>@truffle/source-map-utils>json-pointer": true, + "@truffle/decoder>@truffle/source-map-utils>node-interval-tree": true, + "nock>debug": true + } + }, + "@truffle/decoder>@truffle/source-map-utils>@truffle/code-utils": { + "packages": { + "@truffle/codec>cbor": true, + "browserify>buffer": true + } + }, + "@truffle/decoder>@truffle/source-map-utils>json-pointer": { + "packages": { + "@truffle/decoder>@truffle/source-map-utils>json-pointer>foreach": true + } + }, + "@truffle/decoder>@truffle/source-map-utils>node-interval-tree": { + "packages": { + "@truffle/decoder>@truffle/source-map-utils>node-interval-tree>shallowequal": true + } + }, + "@truffle/decoder>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "@zxing/browser": { + "globals": { + "HTMLElement": true, + "HTMLImageElement": true, + "HTMLVideoElement": true, + "URL.createObjectURL": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "document": true, + "navigator": true, + "setTimeout": true + }, + "packages": { + "@zxing/library": true + } + }, + "@zxing/library": { + "globals": { + "TextDecoder": true, + "TextEncoder": true, + "btoa": true, + "clearTimeout": true, + "define": true, + "document.createElement": true, + "document.createElementNS": true, + "document.getElementById": true, + "navigator.mediaDevices.enumerateDevices": true, + "navigator.mediaDevices.getUserMedia": true, + "setTimeout": true + } + }, + "addons-linter>sha.js": { + "packages": { + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "await-semaphore": { + "packages": { + "browserify>process": true, + "browserify>timers-browserify": true + } + }, + "base32-encode": { + "packages": { + "base32-encode>to-data-view": true + } + }, + "bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "bowser": { + "globals": { + "define": true + } + }, + "browserify>assert": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>assert>util": true, + "react>object-assign": true + } + }, + "browserify>assert>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, + "packages": { + "browserify>assert>util>inherits": true, + "browserify>process": true + } + }, + "browserify>browser-resolve": { + "packages": { + "ethjs-query>babel-runtime>core-js": true + } + }, + "browserify>browserify-zlib": { + "packages": { + "browserify>assert": true, + "browserify>browserify-zlib>pako": true, + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-browserify": true, + "browserify>util": true + } + }, + "browserify>buffer": { + "globals": { + "console": true + }, + "packages": { + "base64-js": true, + "browserify>buffer>ieee754": true + } + }, + "browserify>crypto-browserify": { + "packages": { + "browserify>crypto-browserify>browserify-cipher": true, + "browserify>crypto-browserify>browserify-sign": true, + "browserify>crypto-browserify>create-ecdh": true, + "browserify>crypto-browserify>create-hmac": true, + "browserify>crypto-browserify>diffie-hellman": true, + "browserify>crypto-browserify>pbkdf2": true, + "browserify>crypto-browserify>public-encrypt": true, + "browserify>crypto-browserify>randomfill": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-wallet>randombytes": true + } + }, + "browserify>crypto-browserify>browserify-cipher": { + "packages": { + "browserify>crypto-browserify>browserify-cipher>browserify-des": true, + "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true + } + }, + "browserify>crypto-browserify>browserify-cipher>browserify-des": { + "packages": { + "browserify>buffer": true, + "browserify>crypto-browserify>browserify-cipher>browserify-des>des.js": true, + "ethereumjs-util>create-hash>cipher-base": true, + "pumpify>inherits": true + } + }, + "browserify>crypto-browserify>browserify-cipher>browserify-des>des.js": { + "packages": { + "ganache>secp256k1>elliptic>minimalistic-assert": true, + "pumpify>inherits": true + } + }, + "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": { + "packages": { + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "browserify>crypto-browserify>browserify-sign": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "browserify>crypto-browserify>create-hmac": true, + "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>crypto-browserify>public-encrypt>parse-asn1": true, + "browserify>stream-browserify": true, + "ethereumjs-util>create-hash": true, + "ganache>secp256k1>elliptic": true, + "pumpify>inherits": true + } + }, + "browserify>crypto-browserify>create-ecdh": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "ganache>secp256k1>elliptic": true + } + }, + "browserify>crypto-browserify>create-hmac": { + "packages": { + "addons-linter>sha.js": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash>ripemd160": true, + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "browserify>crypto-browserify>diffie-hellman": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "browserify>crypto-browserify>diffie-hellman>miller-rabin": true, + "ethereumjs-wallet>randombytes": true + } + }, + "browserify>crypto-browserify>diffie-hellman>miller-rabin": { + "packages": { + "bn.js": true, + "ganache>secp256k1>elliptic>brorand": true + } + }, + "browserify>crypto-browserify>pbkdf2": { + "globals": { + "crypto": true, + "process": true, + "queueMicrotask": true, + "setImmediate": true, + "setTimeout": true + }, + "packages": { + "addons-linter>sha.js": true, + "browserify>process": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>create-hash>ripemd160": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "browserify>crypto-browserify>public-encrypt": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "browserify>crypto-browserify>public-encrypt>browserify-rsa": true, + "browserify>crypto-browserify>public-encrypt>parse-asn1": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-wallet>randombytes": true + } + }, + "browserify>crypto-browserify>public-encrypt>browserify-rsa": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "ethereumjs-wallet>randombytes": true + } + }, + "browserify>crypto-browserify>public-encrypt>parse-asn1": { + "packages": { + "browserify>buffer": true, + "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, + "browserify>crypto-browserify>pbkdf2": true, + "browserify>crypto-browserify>public-encrypt>parse-asn1>asn1.js": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes": true + } + }, + "browserify>crypto-browserify>public-encrypt>parse-asn1>asn1.js": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "browserify>vm-browserify": true, + "ganache>secp256k1>elliptic>minimalistic-assert": true, + "pumpify>inherits": true + } + }, + "browserify>crypto-browserify>randomfill": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "ethereumjs-wallet>randombytes": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "browserify>events": { + "globals": { + "console": true + } + }, + "browserify>has": { + "packages": { + "browserify>has>function-bind": true + } + }, + "browserify>https-browserify": { + "packages": { + "browserify>stream-http": true, + "browserify>url": true + } + }, + "browserify>os-browserify": { + "globals": { + "location": true, + "navigator": true + } + }, + "browserify>path-browserify": { + "packages": { + "browserify>process": true + } + }, + "browserify>process": { + "globals": { + "clearTimeout": true, + "setTimeout": true + } + }, + "browserify>punycode": { + "globals": { + "define": true + } + }, + "browserify>stream-browserify": { + "packages": { + "browserify>events": true, + "pumpify>inherits": true, + "readable-stream": true + } + }, + "browserify>stream-http": { + "globals": { + "AbortController": true, + "Blob": true, + "MSStreamReader": true, + "ReadableStream": true, + "WritableStream": true, + "XDomainRequest": true, + "XMLHttpRequest": true, + "clearTimeout": true, + "fetch": true, + "location.protocol.search": true, + "setTimeout": true + }, + "packages": { + "browserify>buffer": true, + "browserify>process": true, + "browserify>stream-http>builtin-status-codes": true, + "browserify>stream-http>readable-stream": true, + "browserify>url": true, + "pumpify>inherits": true, + "watchify>xtend": true + } + }, + "browserify>stream-http>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, + "browserify>string_decoder": { + "packages": { + "ethereumjs-wallet>safe-buffer": true + } + }, + "browserify>timers-browserify": { + "globals": { + "clearInterval": true, + "clearTimeout": true, + "setInterval": true, + "setTimeout": true + }, + "packages": { + "browserify>process": true + } + }, + "browserify>url": { + "packages": { + "browserify>punycode": true, + "browserify>querystring-es3": true + } + }, + "browserify>util": { + "globals": { + "console.error": true, + "console.log": true, + "console.trace": true, + "process": true + }, + "packages": { + "browserify>process": true, + "browserify>util>inherits": true + } + }, + "browserify>vm-browserify": { + "globals": { + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true + } + }, + "classnames": { + "globals": { + "classNames": "write", + "define": true + } + }, + "copy-to-clipboard": { + "globals": { + "clipboardData": true, + "console.error": true, + "console.warn": true, + "document.body.appendChild": true, + "document.body.removeChild": true, + "document.createElement": true, + "document.createRange": true, + "document.execCommand": true, + "document.getSelection": true, + "navigator.userAgent": true, + "prompt": true + }, + "packages": { + "copy-to-clipboard>toggle-selection": true + } + }, + "copy-to-clipboard>toggle-selection": { + "globals": { + "document.activeElement": true, + "document.getSelection": true + } + }, + "currency-formatter": { + "packages": { + "currency-formatter>accounting": true, + "currency-formatter>locale-currency": true, + "react>object-assign": true + } + }, + "currency-formatter>accounting": { + "globals": { + "define": true + } + }, + "currency-formatter>locale-currency": { + "globals": { + "countryCode": true + } + }, + "debounce-stream": { + "packages": { + "debounce-stream>debounce": true, + "debounce-stream>duplexer": true, + "debounce-stream>through": true + } + }, + "debounce-stream>debounce": { + "globals": { + "clearTimeout": true, + "setTimeout": true + } + }, + "debounce-stream>duplexer": { + "packages": { + "browserify>stream-browserify": true + } + }, + "debounce-stream>through": { + "packages": { + "browserify>process": true, + "browserify>stream-browserify": true + } + }, + "depcheck>@vue/compiler-sfc>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "dependency-tree>precinct>detective-postcss>postcss>nanoid": { + "globals": { + "crypto.getRandomValues": true + } + }, + "end-of-stream": { + "packages": { + "browserify>process": true, + "pump>once": true + } + }, + "eslint>optionator>fast-levenshtein": { + "globals": { + "Intl": true, + "Levenshtein": "write", + "console.log": true, + "define": true, + "importScripts": true, + "postMessage": true + } + }, + "eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/utils": true, + "eth-block-tracker>@metamask/safe-event-emitter": true, + "eth-block-tracker>pify": true, + "eth-query>json-rpc-random-id": true + } + }, + "eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, + "eth-ens-namehash": { + "globals": { + "name": "write" + }, + "packages": { + "browserify>buffer": true, + "eth-ens-namehash>idna-uts46-hx": true, + "eth-ens-namehash>js-sha3": true + } + }, + "eth-ens-namehash>idna-uts46-hx": { + "globals": { + "define": true + }, + "packages": { + "browserify>punycode": true + } + }, + "eth-ens-namehash>js-sha3": { + "packages": { + "browserify>process": true + } + }, + "eth-json-rpc-filters": { + "globals": { + "console.error": true + }, + "packages": { + "@metamask/safe-event-emitter": true, + "eth-json-rpc-filters>async-mutex": true, + "eth-query": true, + "json-rpc-engine": true, + "pify": true + } + }, + "eth-json-rpc-filters>async-mutex": { + "globals": { + "setTimeout": true + }, + "packages": { + "wait-on>rxjs>tslib": true + } + }, + "eth-keyring-controller>@metamask/browser-passworder": { + "globals": { + "crypto": true + } + }, + "eth-lattice-keyring": { + "globals": { + "addEventListener": true, + "browser": true, + "clearInterval": true, + "fetch": true, + "open": true, + "setInterval": true + }, + "packages": { + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>crypto-browserify": true, + "browserify>events": true, + "eth-lattice-keyring>@ethereumjs/tx": true, + "eth-lattice-keyring>bn.js": true, + "eth-lattice-keyring>gridplus-sdk": true, + "eth-lattice-keyring>rlp": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx": { + "packages": { + "@ethereumjs/common": true, + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "eth-lattice-keyring>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "eth-lattice-keyring>gridplus-sdk": { + "globals": { + "AbortController": true, + "Request": true, + "URL": true, + "__values": true, + "caches": true, + "clearTimeout": true, + "console.error": true, + "console.log": true, + "console.warn": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethersproject/abi": true, + "bn.js": true, + "browserify>buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": true, + "eth-lattice-keyring>gridplus-sdk>bech32": true, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>bitwise": true, + "eth-lattice-keyring>gridplus-sdk>borc": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": true, + "eth-lattice-keyring>gridplus-sdk>js-sha3": true, + "eth-lattice-keyring>gridplus-sdk>rlp": true, + "eth-lattice-keyring>gridplus-sdk>secp256k1": true, + "eth-lattice-keyring>gridplus-sdk>uuid": true, + "ethereumjs-util>ethereum-cryptography>hash.js": true, + "ethereumjs-wallet>aes-js": true, + "ethereumjs-wallet>bs58check": true, + "ganache>secp256k1>elliptic": true, + "lodash": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx": { + "packages": { + "@ethereumjs/tx>@ethereumjs/rlp": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "@ethersproject/providers": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz": { + "packages": { + "browserify": true, + "browserify>buffer": true, + "eth-lattice-keyring>@ethereumjs/tx>@chainsafe/ssz>case": true, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@chainsafe/ssz>@chainsafe/persistent-merkle-tree": { + "globals": { + "WeakRef": true + }, + "packages": { + "browserify": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>@ethereumjs/common": { + "packages": { + "@ethereumjs/common>crc-32": true, + "@ethereumjs/tx>@ethereumjs/util": true, + "browserify>buffer": true, + "browserify>events": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography": { + "globals": { + "TextDecoder": true, + "crypto": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": true + } + }, + "eth-lattice-keyring>gridplus-sdk>@ethereumjs/tx>ethereum-cryptography>@noble/hashes": { + "globals": { + "TextEncoder": true, + "crypto": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>bitwise": { + "packages": { + "browserify>buffer": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc": { + "globals": { + "console": true + }, + "packages": { + "browserify>buffer": true, + "browserify>buffer>ieee754": true, + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": true, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc>bignumber.js": { + "globals": { + "crypto": true, + "define": true + } + }, + "eth-lattice-keyring>gridplus-sdk>borc>iso-url": { + "globals": { + "URL": true, + "URLSearchParams": true, + "location": true + } + }, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser": { + "globals": { + "intToBuffer": true + }, + "packages": { + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>bn.js": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": true, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>js-sha3": true + } + }, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>buffer": { + "globals": { + "console": true + }, + "packages": { + "base64-js": true, + "browserify>buffer>ieee754": true + } + }, + "eth-lattice-keyring>gridplus-sdk>eth-eip712-util-browser>js-sha3": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true + } + }, + "eth-lattice-keyring>gridplus-sdk>js-sha3": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true + } + }, + "eth-lattice-keyring>gridplus-sdk>rlp": { + "globals": { + "TextEncoder": true + } + }, + "eth-lattice-keyring>gridplus-sdk>secp256k1": { + "packages": { + "ganache>secp256k1>elliptic": true + } + }, + "eth-lattice-keyring>gridplus-sdk>uuid": { + "globals": { + "crypto": true + } + }, + "eth-lattice-keyring>rlp": { + "globals": { + "TextEncoder": true + } + }, + "eth-method-registry": { + "packages": { + "ethjs": true + } + }, + "eth-query": { + "packages": { + "eth-query>json-rpc-random-id": true, + "nock>debug": true, + "watchify>xtend": true + } + }, + "eth-rpc-errors": { + "packages": { + "eth-rpc-errors>fast-safe-stringify": true + } + }, + "eth-sig-util": { + "packages": { + "browserify>buffer": true, + "eth-sig-util>ethereumjs-util": true, + "eth-sig-util>tweetnacl": true, + "eth-sig-util>tweetnacl-util": true, + "ethereumjs-abi": true + } + }, + "eth-sig-util>ethereumjs-util": { + "packages": { + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "eth-sig-util>ethereumjs-util>ethjs-util": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true, + "ganache>secp256k1>elliptic": true + } + }, + "eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "eth-sig-util>tweetnacl": { + "globals": { + "crypto": true, + "msCrypto": true, + "nacl": "write" + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "eth-sig-util>tweetnacl-util": { + "globals": { + "atob": true, + "btoa": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "ethereumjs-abi": { + "packages": { + "bn.js": true, + "browserify>buffer": true, + "ethereumjs-abi>ethereumjs-util": true + } + }, + "ethereumjs-abi>ethereumjs-util": { + "packages": { + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-abi>ethereumjs-util>ethjs-util": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ganache>secp256k1>elliptic": true + } + }, + "ethereumjs-abi>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "ethereumjs-util": { + "packages": { + "browserify>assert": true, + "browserify>buffer": true, + "browserify>insert-module-globals>is-buffer": true, + "ethereumjs-util>bn.js": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "ethereumjs-util>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "ethereumjs-util>create-hash": { + "packages": { + "addons-linter>sha.js": true, + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>create-hash>md5.js": true, + "ethereumjs-util>create-hash>ripemd160": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>create-hash>cipher-base": { + "packages": { + "browserify>stream-browserify": true, + "browserify>string_decoder": true, + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>create-hash>md5.js": { + "packages": { + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>create-hash>md5.js>hash-base": { + "packages": { + "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": true, + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>create-hash>md5.js>hash-base>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, + "ethereumjs-util>create-hash>ripemd160": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>create-hash>md5.js>hash-base": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>ethereum-cryptography": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>ethereum-cryptography>keccak": true, + "ethereumjs-util>ethereum-cryptography>secp256k1": true, + "ethereumjs-wallet>randombytes": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes": { + "packages": { + "browserify>buffer": true, + "browserify>crypto-browserify>browserify-cipher>evp_bytestokey": true, + "ethereumjs-util>create-hash>cipher-base": true, + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": true, + "ethereumjs-wallet>safe-buffer": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>ethereum-cryptography>browserify-aes>buffer-xor": { + "packages": { + "browserify>buffer": true + } + }, + "ethereumjs-util>ethereum-cryptography>hash.js": { + "packages": { + "ganache>secp256k1>elliptic>minimalistic-assert": true, + "pumpify>inherits": true + } + }, + "ethereumjs-util>ethereum-cryptography>keccak": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>ethereum-cryptography>keccak>readable-stream": true + } + }, + "ethereumjs-util>ethereum-cryptography>keccak>readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>buffer": true, + "browserify>events": true, + "browserify>process": true, + "browserify>string_decoder": true, + "pumpify>inherits": true, + "readable-stream>util-deprecate": true + } + }, + "ethereumjs-util>ethereum-cryptography>scrypt-js": { + "globals": { + "define": true, + "setTimeout": true + }, + "packages": { + "browserify>timers-browserify": true + } + }, + "ethereumjs-util>ethereum-cryptography>secp256k1": { + "packages": { + "ganache>secp256k1>elliptic": true + } + }, + "ethereumjs-util>rlp": { + "packages": { + "browserify>buffer": true, + "ethereumjs-util>rlp>bn.js": true + } + }, + "ethereumjs-util>rlp>bn.js": { + "globals": { + "Buffer": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "ethereumjs-wallet": { + "packages": { + "@truffle/codec>utf8": true, + "browserify>crypto-browserify": true, + "ethereumjs-wallet>aes-js": true, + "ethereumjs-wallet>bs58check": true, + "ethereumjs-wallet>ethereumjs-util": true, + "ethereumjs-wallet>randombytes": true, + "ethereumjs-wallet>safe-buffer": true, + "ethereumjs-wallet>scryptsy": true, + "ethereumjs-wallet>uuid": true + } + }, + "ethereumjs-wallet>aes-js": { + "globals": { + "define": true + } + }, + "ethereumjs-wallet>bs58check": { + "packages": { + "ethereumjs-util>create-hash": true, + "ethereumjs-wallet>bs58check>bs58": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "ethereumjs-wallet>bs58check>bs58": { + "packages": { + "ethereumjs-wallet>bs58check>bs58>base-x": true + } + }, + "ethereumjs-wallet>bs58check>bs58>base-x": { + "packages": { + "ethereumjs-wallet>safe-buffer": true + } + }, + "ethereumjs-wallet>ethereumjs-util": { + "packages": { + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>ethereumjs-util>ethjs-util": true, + "ganache>secp256k1>elliptic": true + } + }, + "ethereumjs-wallet>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "ethereumjs-wallet>randombytes": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>process": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "ethereumjs-wallet>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "ethereumjs-wallet>scryptsy": { + "packages": { + "browserify>buffer": true, + "browserify>crypto-browserify>pbkdf2": true + } + }, + "ethereumjs-wallet>uuid": { + "globals": { + "crypto": true, + "msCrypto": true + } + }, + "ethers>@ethersproject/random": { + "globals": { + "crypto.getRandomValues": true + } + }, + "ethjs": { + "globals": { + "clearInterval": true, + "setInterval": true + }, + "packages": { + "browserify>buffer": true, + "ethjs-contract": true, + "ethjs-query": true, + "ethjs>bn.js": true, + "ethjs>ethjs-abi": true, + "ethjs>ethjs-filter": true, + "ethjs>ethjs-provider-http": true, + "ethjs>ethjs-unit": true, + "ethjs>ethjs-util": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "ethjs-contract": { + "packages": { + "ethjs-contract>ethjs-abi": true, + "ethjs-query>babel-runtime": true, + "ethjs>ethjs-filter": true, + "ethjs>ethjs-util": true, + "ethjs>js-sha3": true, + "promise-to-callback": true + } + }, + "ethjs-contract>ethjs-abi": { + "packages": { + "browserify>buffer": true, + "ethjs-contract>ethjs-abi>bn.js": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "ethjs-query": { + "globals": { + "console": true + }, + "packages": { + "ethjs-query>ethjs-format": true, + "ethjs-query>ethjs-rpc": true, + "promise-to-callback": true + } + }, + "ethjs-query>babel-runtime": { + "packages": { + "@babel/runtime": true, + "@babel/runtime>regenerator-runtime": true, + "ethjs-query>babel-runtime>core-js": true + } + }, + "ethjs-query>babel-runtime>core-js": { + "globals": { + "PromiseRejectionEvent": true, + "__e": "write", + "__g": "write", + "document.createTextNode": true, + "postMessage": true, + "setTimeout": true + } + }, + "ethjs-query>ethjs-format": { + "packages": { + "ethjs-query>ethjs-format>ethjs-schema": true, + "ethjs>ethjs-util": true, + "ethjs>ethjs-util>strip-hex-prefix": true, + "ethjs>number-to-bn": true + } + }, + "ethjs-query>ethjs-rpc": { + "packages": { + "promise-to-callback": true + } + }, + "ethjs>ethjs-abi": { + "packages": { + "browserify>buffer": true, + "ethjs>bn.js": true, + "ethjs>js-sha3": true, + "ethjs>number-to-bn": true + } + }, + "ethjs>ethjs-filter": { + "globals": { + "clearInterval": true, + "setInterval": true + } + }, + "ethjs>ethjs-provider-http": { + "packages": { + "ethjs>ethjs-provider-http>xhr2": true + } + }, + "ethjs>ethjs-provider-http>xhr2": { + "globals": { + "XMLHttpRequest": true + } + }, + "ethjs>ethjs-unit": { + "packages": { + "ethjs>ethjs-unit>bn.js": true, + "ethjs>number-to-bn": true + } + }, + "ethjs>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, + "ethjs>ethjs-util>strip-hex-prefix": { + "packages": { + "ethjs>ethjs-util>is-hex-prefixed": true + } + }, + "ethjs>js-sha3": { + "packages": { + "browserify>process": true + } + }, + "ethjs>number-to-bn": { + "packages": { + "ethjs>ethjs-util>strip-hex-prefix": true, + "ethjs>number-to-bn>bn.js": true + } + }, + "extension-port-stream": { + "packages": { + "browserify>buffer": true, + "browserify>stream-browserify": true + } + }, + "fast-json-patch": { + "globals": { + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true, + "setTimeout": true + } + }, + "fuse.js": { + "globals": { + "console": true, + "define": true + } + }, + "ganache>secp256k1>elliptic": { + "packages": { + "bn.js": true, + "ethereumjs-util>ethereum-cryptography>hash.js": true, + "ganache>secp256k1>elliptic>brorand": true, + "ganache>secp256k1>elliptic>hmac-drbg": true, + "ganache>secp256k1>elliptic>minimalistic-assert": true, + "ganache>secp256k1>elliptic>minimalistic-crypto-utils": true, + "pumpify>inherits": true + } + }, + "ganache>secp256k1>elliptic>brorand": { + "globals": { + "crypto": true, + "msCrypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, + "ganache>secp256k1>elliptic>hmac-drbg": { + "packages": { + "ethereumjs-util>ethereum-cryptography>hash.js": true, + "ganache>secp256k1>elliptic>minimalistic-assert": true, + "ganache>secp256k1>elliptic>minimalistic-crypto-utils": true + } + }, + "globalthis>define-properties": { + "packages": { + "globalthis>define-properties>has-property-descriptors": true, + "globalthis>define-properties>object-keys": true + } + }, + "globalthis>define-properties>has-property-descriptors": { + "packages": { + "string.prototype.matchall>get-intrinsic": true + } + }, + "json-rpc-engine": { + "packages": { + "@metamask/safe-event-emitter": true, + "eth-rpc-errors": true + } + }, + "json-rpc-middleware-stream": { + "globals": { + "console.warn": true, + "setTimeout": true + }, + "packages": { + "@metamask/safe-event-emitter": true, + "readable-stream": true + } + }, + "koa>is-generator-function>has-tostringtag": { + "packages": { + "string.prototype.matchall>has-symbols": true + } + }, + "lavamoat>json-stable-stringify": { + "packages": { + "lavamoat>json-stable-stringify>jsonify": true + } + }, + "localforage": { + "globals": { + "Blob": true, + "BlobBuilder": true, + "FileReader": true, + "IDBKeyRange": true, + "MSBlobBuilder": true, + "MozBlobBuilder": true, + "OIndexedDB": true, + "WebKitBlobBuilder": true, + "atob": true, + "btoa": true, + "console.error": true, + "console.info": true, + "console.warn": true, + "define": true, + "fetch": true, + "indexedDB": true, + "localStorage": true, + "mozIndexedDB": true, + "msIndexedDB": true, + "navigator.platform": true, + "navigator.userAgent": true, + "openDatabase": true, + "setTimeout": true, + "webkitIndexedDB": true + } + }, + "lodash": { + "globals": { + "clearTimeout": true, + "define": true, + "setTimeout": true + } + }, + "loglevel": { + "globals": { + "console": true, + "define": true, + "document.cookie": true, + "localStorage": true, + "log": "write", + "navigator": true + } + }, + "luxon": { + "globals": { + "Intl": true + } + }, + "nanoid": { + "globals": { + "crypto": true, + "msCrypto": true, + "navigator": true + } + }, + "nock>debug": { + "globals": { + "console": true, + "document": true, + "localStorage": true, + "navigator": true, + "process": true + }, + "packages": { + "browserify>process": true, + "nock>debug>ms": true + } + }, + "node-fetch": { + "globals": { + "Headers": true, + "Request": true, + "Response": true, + "fetch": true + } + }, + "nonce-tracker": { + "packages": { + "await-semaphore": true, + "browserify>assert": true, + "ethjs-query": true + } + }, + "obj-multiplex": { + "globals": { + "console.warn": true + }, + "packages": { + "end-of-stream": true, + "pump>once": true, + "readable-stream": true + } + }, + "promise-to-callback": { + "packages": { + "promise-to-callback>is-fn": true, + "promise-to-callback>set-immediate-shim": true + } + }, + "promise-to-callback>set-immediate-shim": { + "globals": { + "setTimeout.apply": true + }, + "packages": { + "browserify>timers-browserify": true + } + }, + "prop-types": { + "globals": { + "console": true + }, + "packages": { + "prop-types>react-is": true, + "react>object-assign": true + } + }, + "prop-types>react-is": { + "globals": { + "console": true + } + }, + "pump": { + "packages": { + "browserify>browser-resolve": true, + "browserify>process": true, + "end-of-stream": true, + "pump>once": true + } + }, + "pump>once": { + "packages": { + "pump>once>wrappy": true + } + }, + "qrcode-generator": { + "globals": { + "define": true + } + }, + "qrcode.react": { + "globals": { + "Path2D": true, + "devicePixelRatio": true + }, + "packages": { + "prop-types": true, + "qrcode.react>qr.js": true, + "react": true + } + }, + "react": { + "globals": { + "console": true + }, + "packages": { + "prop-types": true, + "react>object-assign": true + } + }, + "react-devtools": { + "packages": { + "react-devtools>react-devtools-core": true + } + }, + "react-devtools>react-devtools-core": { + "globals": { + "WebSocket": true, + "setTimeout": true + } + }, + "react-dnd-html5-backend": { + "globals": { + "addEventListener": true, + "clearTimeout": true, + "removeEventListener": true + } + }, + "react-dom": { + "globals": { + "HTMLIFrameElement": true, + "MSApp": true, + "__REACT_DEVTOOLS_GLOBAL_HOOK__": true, + "addEventListener": true, + "clearTimeout": true, + "clipboardData": true, + "console": true, + "dispatchEvent": true, + "document": true, + "event": "write", + "jest": true, + "location.protocol": true, + "navigator.userAgent.indexOf": true, + "performance": true, + "removeEventListener": true, + "self": true, + "setTimeout": true, + "top": true, + "trustedTypes": true + }, + "packages": { + "prop-types": true, + "react": true, + "react-dom>scheduler": true, + "react>object-assign": true + } + }, + "react-dom>scheduler": { + "globals": { + "MessageChannel": true, + "cancelAnimationFrame": true, + "clearTimeout": true, + "console": true, + "navigator": true, + "performance": true, + "requestAnimationFrame": true, + "setTimeout": true + } + }, + "react-focus-lock": { + "globals": { + "addEventListener": true, + "console.error": true, + "console.warn": true, + "document": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "@babel/runtime": true, + "prop-types": true, + "react": true, + "react-focus-lock>focus-lock": true, + "react-focus-lock>react-clientside-effect": true, + "react-focus-lock>use-callback-ref": true, + "react-focus-lock>use-sidecar": true + } + }, + "react-focus-lock>focus-lock": { + "globals": { + "HTMLIFrameElement": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_POSITION_CONTAINED_BY": true, + "Node.DOCUMENT_POSITION_CONTAINS": true, + "Node.ELEMENT_NODE": true, + "console.error": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "setTimeout": true + }, + "packages": { + "wait-on>rxjs>tslib": true + } + }, + "react-focus-lock>react-clientside-effect": { + "packages": { + "@babel/runtime": true, + "react": true + } + }, + "react-focus-lock>use-callback-ref": { + "packages": { + "react": true + } + }, + "react-focus-lock>use-sidecar": { + "globals": { + "console.error": true + }, + "packages": { + "react": true, + "react-focus-lock>use-sidecar>detect-node-es": true, + "wait-on>rxjs>tslib": true + } + }, + "react-idle-timer": { + "globals": { + "clearTimeout": true, + "document": true, + "setTimeout": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-inspector": { + "globals": { + "Node.CDATA_SECTION_NODE": true, + "Node.COMMENT_NODE": true, + "Node.DOCUMENT_FRAGMENT_NODE": true, + "Node.DOCUMENT_NODE": true, + "Node.DOCUMENT_TYPE_NODE": true, + "Node.ELEMENT_NODE": true, + "Node.PROCESSING_INSTRUCTION_NODE": true, + "Node.TEXT_NODE": true + }, + "packages": { + "ethjs-query>babel-runtime": true, + "prop-types": true, + "react": true, + "react-inspector>is-dom": true + } + }, + "react-inspector>is-dom": { + "globals": { + "Node": true + }, + "packages": { + "@lavamoat/snow>is-cross-origin>is-window": true, + "proxyquire>fill-keys>is-object": true + } + }, + "react-popper": { + "globals": { + "document": true + }, + "packages": { + "@popperjs/core": true, + "react": true, + "react-popper>react-fast-compare": true, + "react-popper>warning": true + } + }, + "react-popper>react-fast-compare": { + "globals": { + "Element": true, + "console.warn": true + } + }, + "react-popper>warning": { + "globals": { + "console": true + } + }, + "react-redux": { + "globals": { + "console": true, + "document": true + }, + "packages": { + "@babel/runtime": true, + "prop-types": true, + "prop-types>react-is": true, + "react": true, + "react-dom": true, + "react-redux>hoist-non-react-statics": true, + "redux": true + } + }, + "react-redux>hoist-non-react-statics": { + "packages": { + "prop-types>react-is": true + } + }, + "react-responsive-carousel": { + "globals": { + "HTMLElement": true, + "addEventListener": true, + "clearTimeout": true, + "console.warn": true, + "document": true, + "getComputedStyle": true, + "removeEventListener": true, + "setTimeout": true + }, + "packages": { + "classnames": true, + "react": true, + "react-dom": true, + "react-responsive-carousel>react-easy-swipe": true + } + }, + "react-responsive-carousel>react-easy-swipe": { + "globals": { + "addEventListener": true, + "define": true, + "document.addEventListener": true, + "document.removeEventListener": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-router-dom": { + "packages": { + "prop-types": true, + "react": true, + "react-router-dom>history": true, + "react-router-dom>react-router": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom>history": { + "globals": { + "addEventListener": true, + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true + }, + "packages": { + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>history>value-equal": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom>react-router": { + "packages": { + "prop-types": true, + "prop-types>react-is": true, + "react": true, + "react-redux>hoist-non-react-statics": true, + "react-router-dom>react-router>history": true, + "react-router-dom>react-router>mini-create-react-context": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true, + "sinon>nise>path-to-regexp": true + } + }, + "react-router-dom>react-router>history": { + "globals": { + "addEventListener": true, + "confirm": true, + "document": true, + "history": true, + "location": true, + "navigator.userAgent": true, + "removeEventListener": true + }, + "packages": { + "react-router-dom>history>resolve-pathname": true, + "react-router-dom>history>value-equal": true, + "react-router-dom>tiny-invariant": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom>react-router>mini-create-react-context": { + "packages": { + "@babel/runtime": true, + "prop-types": true, + "react": true, + "react-router-dom>react-router>mini-create-react-context>gud": true, + "react-router-dom>tiny-warning": true + } + }, + "react-router-dom>tiny-warning": { + "globals": { + "console": true + } + }, + "react-simple-file-input": { + "globals": { + "File": true, + "FileReader": true, + "console.warn": true + }, + "packages": { + "prop-types": true, + "react": true + } + }, + "react-tippy": { + "globals": { + "Element": true, + "MSStream": true, + "MutationObserver": true, + "addEventListener": true, + "clearTimeout": true, + "console.error": true, + "console.warn": true, + "define": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.maxTouchPoints": true, + "navigator.msMaxTouchPoints": true, + "navigator.userAgent": true, + "performance": true, + "requestAnimationFrame": true, + "setTimeout": true + }, + "packages": { + "react": true, + "react-dom": true, + "react-tippy>popper.js": true + } + }, + "react-tippy>popper.js": { + "globals": { + "MSInputMethodContext": true, + "Node.DOCUMENT_POSITION_FOLLOWING": true, + "cancelAnimationFrame": true, + "console.warn": true, + "define": true, + "devicePixelRatio": true, + "document": true, + "getComputedStyle": true, + "innerHeight": true, + "innerWidth": true, + "navigator.userAgent": true, + "requestAnimationFrame": true, + "setTimeout": true + } + }, + "react-toggle-button": { + "globals": { + "clearTimeout": true, + "console.warn": true, + "define": true, + "performance": true, + "setTimeout": true + }, + "packages": { + "react": true + } + }, + "readable-stream": { + "packages": { + "browserify>browser-resolve": true, + "browserify>events": true, + "browserify>process": true, + "browserify>timers-browserify": true, + "pumpify>inherits": true, + "readable-stream>core-util-is": true, + "readable-stream>isarray": true, + "readable-stream>process-nextick-args": true, + "readable-stream>safe-buffer": true, + "readable-stream>string_decoder": true, + "readable-stream>util-deprecate": true + } + }, + "readable-stream>core-util-is": { + "packages": { + "browserify>insert-module-globals>is-buffer": true + } + }, + "readable-stream>process-nextick-args": { + "packages": { + "browserify>process": true + } + }, + "readable-stream>safe-buffer": { + "packages": { + "browserify>buffer": true + } + }, + "readable-stream>string_decoder": { + "packages": { + "readable-stream>safe-buffer": true + } + }, + "readable-stream>util-deprecate": { + "globals": { + "console.trace": true, + "console.warn": true, + "localStorage": true + } + }, + "redux": { + "globals": { + "console": true + }, + "packages": { + "@babel/runtime": true + } + }, + "semver": { + "globals": { + "console.error": true + }, + "packages": { + "browserify>process": true, + "browserify>util": true, + "semver>lru-cache": true + } + }, + "semver>lru-cache": { + "packages": { + "semver>lru-cache>yallist": true + } + }, + "sinon>nise>path-to-regexp": { + "packages": { + "sinon>nise>path-to-regexp>isarray": true + } + }, + "string.prototype.matchall>call-bind": { + "packages": { + "browserify>has>function-bind": true, + "string.prototype.matchall>get-intrinsic": true + } + }, + "string.prototype.matchall>es-abstract>is-regex": { + "packages": { + "koa>is-generator-function>has-tostringtag": true, + "string.prototype.matchall>call-bind": true + } + }, + "string.prototype.matchall>get-intrinsic": { + "globals": { + "AggregateError": true, + "FinalizationRegistry": true, + "WeakRef": true + }, + "packages": { + "browserify>has": true, + "browserify>has>function-bind": true, + "string.prototype.matchall>es-abstract>has-proto": true, + "string.prototype.matchall>has-symbols": true + } + }, + "string.prototype.matchall>regexp.prototype.flags": { + "packages": { + "globalthis>define-properties": true, + "string.prototype.matchall>call-bind": true, + "string.prototype.matchall>regexp.prototype.flags>functions-have-names": true + } + }, + "uuid": { + "globals": { + "crypto": true, + "msCrypto": true + } + }, + "vinyl>clone": { + "packages": { + "browserify>buffer": true + } + }, + "wait-on>rxjs>tslib": { + "globals": { + "define": true + } + }, + "web3": { + "globals": { + "XMLHttpRequest": true + } + }, + "web3-stream-provider": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>util": true, + "readable-stream": true, + "web3-stream-provider>uuid": true + } + }, + "web3-stream-provider>uuid": { + "globals": { + "crypto": true, + "msCrypto": true + } + }, + "webextension-polyfill": { + "globals": { + "browser": true, + "chrome": true, + "console.error": true, + "console.warn": true, + "define": true + } + }, + "webpack>events": { + "globals": { + "console": true + } + } + } +} \ No newline at end of file diff --git a/lavamoat/build-system/policy.json b/lavamoat/build-system/policy.json index e41d0153c..89e2820e1 100644 --- a/lavamoat/build-system/policy.json +++ b/lavamoat/build-system/policy.json @@ -980,6 +980,7 @@ "packages": { "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>are-we-there-yet": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge": true, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>console-control-strings": true, "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, "nyc>yargs>set-blocking": true } @@ -1008,6 +1009,9 @@ "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>aproba": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>string-width": true, "@lavamoat/allow-scripts>@npmcli/run-script>node-gyp>npmlog>gauge>strip-ansi": true, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>console-control-strings": true, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>has-unicode": true, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>wide-align": true, "@storybook/react>@storybook/node-logger>npmlog>console-control-strings": true, "@storybook/react>@storybook/node-logger>npmlog>gauge>has-unicode": true, "@storybook/react>@storybook/node-logger>npmlog>gauge>wide-align": true, @@ -1133,11 +1137,33 @@ "@metamask/jazzicon>color>color-convert>color-name": true } }, + "@sentry/cli>mkdirp": { + "builtin": { + "fs": true, + "path.dirname": true, + "path.resolve": true + } + }, "@storybook/addon-knobs>qs": { "packages": { "string.prototype.matchall>side-channel": true } }, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>has-unicode": { + "builtin": { + "os.type": true + }, + "globals": { + "process.env.LANG": true, + "process.env.LC_ALL": true, + "process.env.LC_CTYPE": true + } + }, + "@storybook/addon-mdx-gfm>@storybook/node-logger>npmlog>gauge>wide-align": { + "packages": { + "yargs>string-width": true + } + }, "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": { "builtin": { "os.homedir": true @@ -1302,7 +1328,7 @@ "@typescript-eslint/eslint-plugin>tsutils": true, "@typescript-eslint/parser>@typescript-eslint/scope-manager>@typescript-eslint/visitor-keys": true, "@typescript-eslint/parser>@typescript-eslint/types": true, - "eslint>is-glob": true, + "del>is-glob": true, "globby": true, "nock>debug": true, "semver": true, @@ -1520,6 +1546,7 @@ "brfs>static-module": { "packages": { "brfs>static-module>acorn-node": true, + "brfs>static-module>escodegen": true, "brfs>static-module>magic-string": true, "brfs>static-module>merge-source-map": true, "brfs>static-module>object-inspect": true, @@ -1530,7 +1557,6 @@ "browserify>concat-stream": true, "browserify>duplexer2": true, "browserify>has": true, - "jsdom>escodegen": true, "nyc>convert-source-map": true, "readable-stream": true } @@ -1547,6 +1573,16 @@ "define": true } }, + "brfs>static-module>escodegen": { + "globals": { + "sourceMap.SourceNode": true + }, + "packages": { + "brfs>static-module>escodegen>estraverse": true, + "brfs>static-module>escodegen>source-map": true, + "eslint>esutils": true + } + }, "brfs>static-module>magic-string": { "globals": { "Buffer": true, @@ -1621,7 +1657,17 @@ }, "brfs>static-module>static-eval": { "packages": { - "jsdom>escodegen": true + "brfs>static-module>static-eval>escodegen": true + } + }, + "brfs>static-module>static-eval>escodegen": { + "globals": { + "sourceMap.SourceNode": true + }, + "packages": { + "brfs>static-module>static-eval>escodegen>estraverse": true, + "brfs>static-module>static-eval>escodegen>source-map": true, + "eslint>esutils": true } }, "brfs>static-module>through2": { @@ -2033,9 +2079,9 @@ "chokidar>fsevents": true, "chokidar>is-binary-path": true, "chokidar>normalize-path": true, + "del>is-glob": true, "depcheck>readdirp": true, "eslint>glob-parent": true, - "eslint>is-glob": true, "watchify>anymatch": true } }, @@ -2127,103 +2173,65 @@ }, "del": { "builtin": { - "path.resolve": true + "path.resolve": true, + "util.promisify": true + }, + "globals": { + "process.cwd": true, + "process.platform": true }, "packages": { - "del>globby": true, + "del>graceful-fs": true, + "del>is-glob": true, "del>is-path-cwd": true, - "del>is-path-in-cwd": true, + "del>is-path-inside": true, "del>p-map": true, - "del>pify": true, - "del>rimraf": true + "del>slash": true, + "globby": true, + "nyc>rimraf": true } }, - "del>globby": { - "packages": { - "del>globby>array-union": true, - "del>globby>pify": true, - "del>globby>pinkie-promise": true, - "del>rimraf>glob": true, - "react>object-assign": true - } - }, - "del>globby>array-union": { - "packages": { - "del>globby>array-union>array-uniq": true - } - }, - "del>globby>pinkie-promise": { - "packages": { - "del>globby>pinkie-promise>pinkie": true - } - }, - "del>globby>pinkie-promise>pinkie": { + "del>graceful-fs": { + "builtin": { + "assert.equal": true, + "constants.O_SYMLINK": true, + "constants.O_WRONLY": true, + "constants.hasOwnProperty": true, + "fs": true, + "stream.Stream.call": true, + "util": true + }, "globals": { + "clearTimeout": true, + "console.error": true, "process": true, - "setImmediate": true, "setTimeout": true } }, + "del>is-glob": { + "packages": { + "del>is-glob>is-extglob": true + } + }, "del>is-path-cwd": { "builtin": { "path.resolve": true }, "globals": { - "process.cwd": true - } - }, - "del>is-path-in-cwd": { - "globals": { - "process.cwd": true - }, - "packages": { - "del>is-path-in-cwd>is-path-inside": true - } - }, - "del>is-path-in-cwd>is-path-inside": { - "builtin": { - "path.resolve": true - }, - "packages": { - "serve-handler>path-is-inside": true - } - }, - "del>rimraf": { - "builtin": { - "assert": true, - "fs": true, - "path.join": true - }, - "globals": { - "process.platform": true, - "setTimeout": true - }, - "packages": { - "del>rimraf>glob": true - } - }, - "del>rimraf>glob": { - "builtin": { - "assert": true, - "events.EventEmitter": true, - "fs": true, - "path.join": true, - "path.resolve": true, - "util": true - }, - "globals": { - "console.error": true, "process.cwd": true, - "process.nextTick": true, "process.platform": true - }, + } + }, + "del>is-path-inside": { + "builtin": { + "path.relative": true, + "path.resolve": true, + "path.sep": true + } + }, + "del>p-map": { "packages": { - "eslint>minimatch": true, - "gulp-watch>path-is-absolute": true, - "nyc>glob>fs.realpath": true, - "nyc>glob>inflight": true, - "pump>once": true, - "pumpify>inherits": true + "nyc>p-map>aggregate-error": true } }, "depcheck>@babel/traverse": { @@ -2401,6 +2409,8 @@ "process": true }, "packages": { + "del>is-glob": true, + "del>is-path-inside": true, "eslint>@eslint-community/eslint-utils": true, "eslint>@eslint-community/regexpp": true, "eslint>@eslint/eslintrc": true, @@ -2420,8 +2430,6 @@ "eslint>globals": true, "eslint>grapheme-splitter": true, "eslint>imurmurhash": true, - "eslint>is-glob": true, - "eslint>is-path-inside": true, "eslint>js-sdsl": true, "eslint>json-stable-stringify-without-jsonify": true, "eslint>levn": true, @@ -2477,9 +2485,9 @@ }, "packages": { "brfs>resolve": true, + "del>is-glob": true, "eslint-import-resolver-typescript>glob": true, "eslint-plugin-import>tsconfig-paths": true, - "eslint>is-glob": true, "nock>debug": true } }, @@ -2519,6 +2527,7 @@ }, "packages": { "browserify>has": true, + "del>is-glob": true, "depcheck>is-core-module": true, "eslint": true, "eslint-plugin-import>array.prototype.flat": true, @@ -2528,7 +2537,6 @@ "eslint-plugin-import>tsconfig-paths": true, "eslint-plugin-react>array-includes": true, "eslint-plugin-react>object.values": true, - "eslint>is-glob": true, "eslint>minimatch": true, "typescript": true } @@ -3171,7 +3179,7 @@ "path.posix.dirname": true }, "packages": { - "eslint>is-glob": true + "del>is-glob": true } }, "eslint>import-fresh": { @@ -3200,18 +3208,6 @@ "path.resolve": true } }, - "eslint>is-glob": { - "packages": { - "eslint>is-glob>is-extglob": true - } - }, - "eslint>is-path-inside": { - "builtin": { - "path.relative": true, - "path.resolve": true, - "path.sep": true - } - }, "eslint>levn": { "packages": { "eslint>levn>prelude-ls": true, @@ -3330,9 +3326,9 @@ "setTimeout": true }, "packages": { + "del>graceful-fs": true, "fs-extra>jsonfile": true, - "fs-extra>universalify": true, - "webpack>graceful-fs": true + "fs-extra>universalify": true } }, "fs-extra>jsonfile": { @@ -3343,7 +3339,19 @@ "Buffer.isBuffer": true }, "packages": { - "webpack>graceful-fs": true + "del>graceful-fs": true + } + }, + "gh-pages>globby>pinkie-promise": { + "packages": { + "gh-pages>globby>pinkie-promise>pinkie": true + } + }, + "gh-pages>globby>pinkie-promise>pinkie": { + "globals": { + "process": true, + "setImmediate": true, + "setTimeout": true } }, "globalthis": { @@ -3380,12 +3388,12 @@ "process.cwd": true }, "packages": { + "del>slash": true, "fast-glob": true, "globby>array-union": true, "globby>dir-glob": true, "globby>ignore": true, - "globby>merge2": true, - "globby>slash": true + "globby>merge2": true } }, "globby>dir-glob": { @@ -3919,6 +3927,7 @@ "Buffer.from": true }, "packages": { + "del>graceful-fs": true, "gulp-sourcemaps>@gulp-sourcemaps/identity-map": true, "gulp-sourcemaps>@gulp-sourcemaps/map-sources": true, "gulp-sourcemaps>acorn": true, @@ -3928,8 +3937,7 @@ "gulp-sourcemaps>source-map": true, "gulp-sourcemaps>strip-bom-string": true, "gulp-sourcemaps>through2": true, - "nyc>convert-source-map": true, - "webpack>graceful-fs": true + "nyc>convert-source-map": true } }, "gulp-sourcemaps>@gulp-sourcemaps/identity-map": { @@ -4405,6 +4413,7 @@ }, "packages": { "chokidar>normalize-path": true, + "del>is-glob": true, "eslint>glob-parent": true, "eslint>is-glob": true, "gulp-watch>chokidar>anymatch": true, @@ -4886,9 +4895,20 @@ }, "packages": { "@storybook/core>@storybook/core-server>x-default-browser>default-browser-id>untildify>os-homedir": true, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": true, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": true } }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-homedir": { + "builtin": { + "os.homedir": true + }, + "globals": { + "process.env": true, + "process.getuid": true, + "process.platform": true + } + }, "gulp-watch>chokidar>fsevents>node-pre-gyp>nopt>osenv>os-tmpdir": { "globals": { "process.env.SystemRoot": true, @@ -4910,9 +4930,34 @@ "setTimeout": true }, "packages": { + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": true, "nyc>glob": true } }, + "gulp-watch>chokidar>fsevents>node-pre-gyp>rimraf>glob": { + "builtin": { + "assert": true, + "events.EventEmitter": true, + "fs": true, + "path.join": true, + "path.resolve": true, + "util": true + }, + "globals": { + "console.error": true, + "process.cwd": true, + "process.nextTick": true, + "process.platform": true + }, + "packages": { + "eslint>minimatch": true, + "gulp-watch>path-is-absolute": true, + "nyc>glob>fs.realpath": true, + "nyc>glob>inflight": true, + "pump>once": true, + "pumpify>inherits": true + } + }, "gulp-watch>chokidar>fsevents>node-pre-gyp>semver": { "globals": { "console": true, @@ -4937,9 +4982,9 @@ "setImmediate": true }, "packages": { + "del>graceful-fs": true, "gulp-watch>chokidar>readdirp>micromatch": true, - "readable-stream": true, - "webpack>graceful-fs": true + "readable-stream": true } }, "gulp-watch>chokidar>readdirp>micromatch": { @@ -5097,12 +5142,12 @@ "process.cwd": true }, "packages": { - "del>globby>pinkie-promise": true, + "del>graceful-fs": true, + "gh-pages>globby>pinkie-promise": true, "gulp-watch>vinyl-file>pify": true, "gulp-watch>vinyl-file>strip-bom": true, "gulp-watch>vinyl-file>strip-bom-stream": true, - "gulp-watch>vinyl-file>vinyl": true, - "webpack>graceful-fs": true + "gulp-watch>vinyl-file>vinyl": true } }, "gulp-watch>vinyl-file>strip-bom": { @@ -5463,8 +5508,8 @@ }, "packages": { "chokidar>normalize-path": true, + "del>is-glob": true, "eslint>glob-parent": true, - "eslint>is-glob": true, "gulp-watch>chokidar>async-each": true, "gulp-watch>path-is-absolute": true, "gulp>glob-watcher>anymatch": true, @@ -5563,9 +5608,9 @@ "setImmediate": true }, "packages": { + "del>graceful-fs": true, "gulp>glob-watcher>anymatch>micromatch": true, - "readable-stream": true, - "webpack>graceful-fs": true + "readable-stream": true } }, "gulp>glob-watcher>chokidar>upath": { @@ -5787,6 +5832,7 @@ "process.nextTick": true }, "packages": { + "del>graceful-fs": true, "gulp>vinyl-fs>fs-mkdirp-stream": true, "gulp>vinyl-fs>glob-stream": true, "gulp>vinyl-fs>is-valid-glob": true, @@ -5802,8 +5848,7 @@ "gulp>vinyl-fs>value-or-function": true, "gulp>vinyl-fs>vinyl-sourcemap": true, "readable-stream": true, - "vinyl": true, - "webpack>graceful-fs": true + "vinyl": true } }, "gulp>vinyl-fs>fs-mkdirp-stream": { @@ -5815,8 +5860,8 @@ "process.umask": true }, "packages": { - "gulp>vinyl-fs>fs-mkdirp-stream>through2": true, - "webpack>graceful-fs": true + "del>graceful-fs": true, + "gulp>vinyl-fs>fs-mkdirp-stream>through2": true } }, "gulp>vinyl-fs>fs-mkdirp-stream>through2": { @@ -6080,13 +6125,13 @@ "Buffer": true }, "packages": { + "del>graceful-fs": true, "gulp>vinyl-fs>remove-bom-buffer": true, "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": true, "gulp>vinyl-fs>vinyl-sourcemap>normalize-path": true, "gulp>vinyl-fs>vinyl-sourcemap>now-and-later": true, "nyc>convert-source-map": true, - "vinyl": true, - "webpack>graceful-fs": true + "vinyl": true } }, "gulp>vinyl-fs>vinyl-sourcemap>append-buffer": { @@ -6120,16 +6165,6 @@ "process": true } }, - "jsdom>escodegen": { - "globals": { - "sourceMap.SourceNode": true - }, - "packages": { - "eslint>esutils": true, - "jsdom>escodegen>estraverse": true, - "jsdom>escodegen>source-map": true - } - }, "koa>is-generator-function>has-tostringtag": { "packages": { "string.prototype.matchall>has-symbols": true @@ -6497,27 +6532,8 @@ }, "mocha>log-symbols": { "packages": { - "madge>ora>is-unicode-supported": true, - "mocha>log-symbols>chalk": true - } - }, - "mocha>log-symbols>chalk": { - "packages": { - "chalk>ansi-styles": true, - "mocha>log-symbols>chalk>supports-color": true - } - }, - "mocha>log-symbols>chalk>supports-color": { - "builtin": { - "os.release": true, - "tty.isatty": true - }, - "globals": { - "process.env": true, - "process.platform": true - }, - "packages": { - "sinon>supports-color>has-flag": true + "chalk": true, + "madge>ora>is-unicode-supported": true } }, "mocha>minimatch>brace-expansion": { @@ -6638,6 +6654,17 @@ "pump>once>wrappy": true } }, + "nyc>p-map>aggregate-error": { + "packages": { + "@testing-library/jest-dom>redent>indent-string": true, + "nyc>p-map>aggregate-error>clean-stack": true + } + }, + "nyc>p-map>aggregate-error>clean-stack": { + "builtin": { + "os.homedir": true + } + }, "nyc>resolve-from": { "builtin": { "fs.realpathSync": true, @@ -7759,14 +7786,6 @@ "semver>lru-cache>yallist": true } }, - "serve-handler>path-is-inside": { - "builtin": { - "path.sep": true - }, - "globals": { - "process.platform": true - } - }, "sinon>supports-color>has-flag": { "globals": { "process.argv": true @@ -7939,10 +7958,11 @@ "process.stdout.isTTY": true }, "packages": { + "chalk": true, + "del>slash": true, "eslint>imurmurhash": true, "globby": true, "globby>ignore": true, - "globby>slash": true, "lodash": true, "mocha>log-symbols": true, "nock>debug": true, @@ -7951,7 +7971,6 @@ "stylelint>@stylelint/postcss-markdown": true, "stylelint>autoprefixer": true, "stylelint>balanced-match": true, - "stylelint>chalk": true, "stylelint>cosmiconfig": true, "stylelint>execall": true, "stylelint>file-entry-cache": true, @@ -8126,25 +8145,6 @@ "stylelint>autoprefixer>postcss>source-map": true } }, - "stylelint>chalk": { - "packages": { - "chalk>ansi-styles": true, - "stylelint>chalk>supports-color": true - } - }, - "stylelint>chalk>supports-color": { - "builtin": { - "os.release": true, - "tty.isatty": true - }, - "globals": { - "process.env": true, - "process.platform": true - }, - "packages": { - "sinon>supports-color>has-flag": true - } - }, "stylelint>cosmiconfig": { "builtin": { "fs": true, @@ -8246,14 +8246,7 @@ "path.dirname": true }, "packages": { - "stylelint>file-entry-cache>flat-cache>write>mkdirp": true - } - }, - "stylelint>file-entry-cache>flat-cache>write>mkdirp": { - "builtin": { - "fs": true, - "path.dirname": true, - "path.resolve": true + "@sentry/cli>mkdirp": true } }, "stylelint>global-modules": { @@ -9029,23 +9022,6 @@ "webpack>eslint-scope>estraverse": true } }, - "webpack>graceful-fs": { - "builtin": { - "assert.equal": true, - "constants.O_SYMLINK": true, - "constants.O_WRONLY": true, - "constants.hasOwnProperty": true, - "fs": true, - "stream.Stream.call": true, - "util": true - }, - "globals": { - "clearTimeout": true, - "console.error": true, - "process": true, - "setTimeout": true - } - }, "webpack>json-parse-even-better-errors": { "globals": { "Buffer.isBuffer": true diff --git a/package.json b/package.json index 4fd23a31f..43e66d32a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "start": "yarn build:dev dev --apply-lavamoat=false --snow=false", "start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false", "start:flask": "yarn start --build-type flask", + "start:mmi": "yarn start --build-type mmi", "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "dist": "yarn build dist", "build": "yarn lavamoat:build", @@ -100,6 +101,7 @@ "resolutions": { "@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch", + "@metamask/approval-controller": "^3.0.0", "@types/react": "^16.9.53", "analytics-node/axios": "^0.21.2", "ganache-core/lodash": "^4.17.21", @@ -181,7 +183,7 @@ "sass@^1.32.4": "patch:sass@npm%3A1.35.2#./.yarn/patches/sass-npm-1.35.2-6df4e15d13.patch", "sass@^1.26.3": "patch:sass@npm%3A1.35.2#./.yarn/patches/sass-npm-1.35.2-6df4e15d13.patch", "sass@^1.29.0": "patch:sass@npm%3A1.35.2#./.yarn/patches/sass-npm-1.35.2-6df4e15d13.patch", - "squirrelly@^8.0.8": "patch:squirrelly@npm%3A8.0.8#./.yarn/patches/squirrelly-npm-8.0.8-1d17420d8d.patch", + "squirrelly@^9.0.0": "patch:squirrelly@npm%3A9.0.0#./.yarn/patches/squirrelly-npm-9.0.0-3cf710c7bb.patch", "stylelint@^13.6.1": "patch:stylelint@npm%3A13.6.1#./.yarn/patches/stylelint-npm-13.6.1-47aaddf62b.patch", "luxon@^3.0.1": "patch:luxon@npm%3A3.2.1#./.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch", "luxon@^3.2.1": "patch:luxon@npm%3A3.2.1#./.yarn/patches/luxon-npm-3.2.1-56f8d97395.patch", @@ -195,8 +197,7 @@ "request@^2.83.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.88.2": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch", - "@metamask/assets-controllers@^6.0.0": "patch:@metamask/assets-controllers@npm%3A6.0.0#./.yarn/patches/@metamask-assets-controllers-npm-6.0.0-0cb763bd07.patch", - "@metamask/signature-controller@^2.0.0": "patch:@metamask/signature-controller@npm%3A2.0.0#./.yarn/patches/@metamask-signature-controller-npm-2.0.0-f441f2596e.patch" + "@metamask/signature-controller@^3.0.0": "patch:@metamask/signature-controller@npm%3A3.0.0#./.yarn/patches/@metamask-signature-controller-npm-3.0.0-8771b6885e.patch" }, "dependencies": { "@actions/core": "^1.10.0", @@ -217,19 +218,25 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@lavamoat/snow": "^1.5.0", "@material-ui/core": "^4.11.0", + "@metamask-institutional/custody-controller": "0.2.6", + "@metamask-institutional/custody-keyring": "0.0.23", "@metamask-institutional/extension": "^0.1.3", + "@metamask-institutional/institutional-features": "^1.1.8", "@metamask-institutional/portfolio-dashboard": "^1.1.3", - "@metamask/address-book-controller": "^2.0.0", - "@metamask/announcement-controller": "^3.0.0", - "@metamask/approval-controller": "^2.1.0", - "@metamask/assets-controllers": "^7.0.0", - "@metamask/base-controller": "^2.0.0", + "@metamask-institutional/rpc-allowlist": "^1.0.0", + "@metamask-institutional/sdk": "^0.1.17", + "@metamask-institutional/transaction-update": "^0.1.21", + "@metamask/address-book-controller": "^3.0.0", + "@metamask/announcement-controller": "^4.0.0", + "@metamask/approval-controller": "^3.1.0", + "@metamask/assets-controllers": "^9.0.0", + "@metamask/base-controller": "^3.0.0", "@metamask/browser-passworder": "^4.1.0", "@metamask/contract-metadata": "^2.3.1", - "@metamask/controller-utils": "^3.3.0", + "@metamask/controller-utils": "^4.0.0", "@metamask/design-tokens": "^1.9.0", "@metamask/desktop": "^0.3.0", - "@metamask/eth-json-rpc-infura": "^8.0.0", + "@metamask/eth-json-rpc-infura": "^8.1.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", "@metamask/eth-json-rpc-provider": "^1.0.0", "@metamask/eth-keyring-controller": "^10.0.1", @@ -237,32 +244,32 @@ "@metamask/eth-token-tracker": "^4.0.0", "@metamask/eth-trezor-keyring": "^1.0.0", "@metamask/etherscan-link": "^2.2.0", - "@metamask/gas-fee-controller": "^5.0.0", + "@metamask/gas-fee-controller": "^6.0.0", "@metamask/jazzicon": "^2.0.0", "@metamask/key-tree": "^7.0.0", "@metamask/logo": "^3.1.1", - "@metamask/message-manager": "^5.0.0", + "@metamask/message-manager": "^6.0.0", "@metamask/metamask-eth-abis": "^3.0.0", - "@metamask/notification-controller": "^2.0.0", + "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", - "@metamask/permission-controller": "^3.2.0", + "@metamask/permission-controller": "^4.0.0", "@metamask/phishing-controller": "^3.0.0", "@metamask/post-message-stream": "^6.0.0", "@metamask/providers": "^10.2.1", - "@metamask/rate-limit-controller": "^2.0.0", + "@metamask/rate-limit-controller": "^3.0.0", "@metamask/rpc-methods": "^0.32.2", - "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.33.1-flask.1", + "@metamask/rpc-methods-flask": "npm:@metamask/rpc-methods@0.34.0-flask.1", "@metamask/safe-event-emitter": "^2.0.0", "@metamask/scure-bip39": "^2.0.3", - "@metamask/signature-controller": "^2.0.0", + "@metamask/signature-controller": "^3.0.0", "@metamask/slip44": "^3.0.0", "@metamask/smart-transactions-controller": "^3.1.0", "@metamask/snaps-controllers": "^0.32.2", - "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.33.1-flask.1", + "@metamask/snaps-controllers-flask": "npm:@metamask/snaps-controllers@0.34.0-flask.1", "@metamask/snaps-ui": "^0.32.2", - "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.33.1-flask.1", + "@metamask/snaps-ui-flask": "npm:@metamask/snaps-ui@0.34.0-flask.1", "@metamask/snaps-utils": "^0.32.2", - "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.33.1-flask.1", + "@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/utils": "^5.0.0", @@ -379,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", @@ -435,7 +442,7 @@ "brfs": "^2.0.2", "browser-util-inspect": "^0.2.0", "browserify": "^16.5.1", - "chalk": "^3.0.0", + "chalk": "^4.1.2", "chokidar": "^3.5.3", "chromedriver": "^111.0.0", "concurrently": "^7.6.0", @@ -444,9 +451,9 @@ "css-loader": "^2.1.1", "css-to-xpath": "^0.1.0", "csstype": "^3.0.11", - "del": "^3.0.0", + "del": "^6.1.1", "depcheck": "^1.4.3", - "dependency-tree": "^8.1.2", + "dependency-tree": "^10.0.9", "duplexify": "^4.1.1", "eslint": "^8.36.0", "eslint-config-prettier": "^8.5.0", @@ -466,7 +473,7 @@ "fs-extra": "^8.1.0", "ganache": "^v7.0.4", "geckodriver": "^3.2.0", - "gh-pages": "^3.2.3", + "gh-pages": "^5.0.0", "globby": "^11.0.4", "gulp": "^4.0.2", "gulp-autoprefixer": "^8.0.0", @@ -499,7 +506,7 @@ "lavamoat-viz": "^6.0.9", "lockfile-lint": "^4.9.6", "loose-envify": "^1.4.0", - "madge": "^5.0.1", + "madge": "^6.1.0", "mocha": "^9.2.2", "mockttp": "^2.6.0", "nock": "^13.2.9", @@ -526,7 +533,7 @@ "sinon": "^9.0.0", "source-map": "^0.7.2", "source-map-explorer": "^2.4.2", - "squirrelly": "^8.0.8", + "squirrelly": "^9.0.0", "storybook": "^7.0.11", "storybook-dark-mode": "^3.0.0", "stream-browserify": "^3.0.0", @@ -550,7 +557,7 @@ "yargs": "^17.0.1" }, "engines": { - "node": "^16.0.0", + "node": "^16.20.0", "yarn": "^3.2.4" }, "lavamoat": { diff --git a/shared/constants/gas.ts b/shared/constants/gas.ts index cf2f911bc..4753f8613 100644 --- a/shared/constants/gas.ts +++ b/shared/constants/gas.ts @@ -55,6 +55,7 @@ export enum PriorityLevels { high = 'high', custom = 'custom', dAppSuggested = 'dappSuggested', + dappSuggestedHigh = 'dappSuggestedHigh', } /** diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 2fdd9e47d..453ba97ea 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -656,15 +656,6 @@ export enum MetaMetricsEventKeyType { Srp = 'srp', } -// NOTE: This doesn't seem to be used at all -export enum MetaMetricsEventOnrampProviderType { - Coinbase = 'coinbase', - Moonpay = 'moonpay', - SelfDeposit = 'direct_deposit', - Transak = 'transak', - Wyre = 'wyre', -} - export enum MetaMetricsNetworkEventSource { CustomNetworkForm = 'custom_network_form', PopularNetworkList = 'popular_network_list', diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 9b20a2003..7e7d26d7b 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -95,7 +95,7 @@ export const NETWORK_TYPES = { MAINNET: 'mainnet', RPC: 'rpc', SEPOLIA: 'sepolia', - LINEA_TESTNET: 'lineatestnet', + LINEA_GOERLI: 'linea-goerli', } as const; /** @@ -121,7 +121,7 @@ export const NETWORK_IDS = { GOERLI: '5', LOCALHOST: '1337', SEPOLIA: '11155111', - LINEA_TESTNET: '59140', + LINEA_GOERLI: '59140', } as const; /** @@ -147,11 +147,12 @@ export const CHAIN_IDS = { HARMONY: '0x63564c40', PALM: '0x2a15c308d', SEPOLIA: '0xaa36a7', - LINEA_TESTNET: '0xe704', + LINEA_GOERLI: '0xe704', AURORA: '0x4e454152', MOONBEAM: '0x504', MOONBEAM_TESTNET: '0x507', MOONRIVER: '0x505', + CRONOS: '0x19', } as const; /** @@ -163,7 +164,7 @@ export const MAX_SAFE_CHAIN_ID = 4503599627370476; export const MAINNET_DISPLAY_NAME = 'Ethereum Mainnet'; export const GOERLI_DISPLAY_NAME = 'Goerli'; export const SEPOLIA_DISPLAY_NAME = 'Sepolia'; -export const LINEA_TESTNET_DISPLAY_NAME = 'Linea Goerli test network'; +export const LINEA_GOERLI_DISPLAY_NAME = 'Linea Goerli'; export const LOCALHOST_DISPLAY_NAME = 'Localhost 8545'; export const BSC_DISPLAY_NAME = 'Binance Smart Chain'; export const POLYGON_DISPLAY_NAME = 'Polygon'; @@ -193,7 +194,9 @@ export const MAINNET_RPC_URL = getRpcUrl({ }); export const GOERLI_RPC_URL = getRpcUrl({ network: NETWORK_TYPES.GOERLI }); export const SEPOLIA_RPC_URL = getRpcUrl({ network: NETWORK_TYPES.SEPOLIA }); -export const LINEA_TESTNET_RPC_URL = `https://linea-goerli.infura.io/v3/${infuraProjectId}`; +export const LINEA_GOERLI_RPC_URL = getRpcUrl({ + network: NETWORK_TYPES.LINEA_GOERLI, +}); export const LOCALHOST_RPC_URL = 'http://localhost:8545'; /** @@ -219,9 +222,14 @@ export const CURRENCY_SYMBOLS = { USDT: 'USDT', WETH: 'WETH', OPTIMISM: 'OP', + CRONOS: 'CRO', + GLIMMER: 'GLMR', + MOONRIVER: 'MOVR', + ONE: 'ONE', } as const; export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.png'; +export const LINEA_GOERLI_TOKEN_IMAGE_URL = './images/linea-logo-testnet.png'; export const TEST_ETH_TOKEN_IMAGE_URL = './images/black-eth-logo.svg'; export const BNB_TOKEN_IMAGE_URL = './images/bnb.png'; export const MATIC_TOKEN_IMAGE_URL = './images/matic-token.png'; @@ -238,12 +246,13 @@ export const INFURA_PROVIDER_TYPES = [ NETWORK_TYPES.MAINNET, NETWORK_TYPES.GOERLI, NETWORK_TYPES.SEPOLIA, + NETWORK_TYPES.LINEA_GOERLI, ] as const; export const TEST_CHAINS = [ CHAIN_IDS.GOERLI, CHAIN_IDS.SEPOLIA, - CHAIN_IDS.LINEA_TESTNET, + CHAIN_IDS.LINEA_GOERLI, CHAIN_IDS.LOCALHOST, ]; @@ -251,10 +260,7 @@ const typedCapitalize = (k: K): Capitalize => capitalize(k) as Capitalize; export const TEST_NETWORK_TICKER_MAP: { - [K in Exclude< - NetworkType, - 'localhost' | 'mainnet' | 'rpc' - >]: `${Capitalize}${typeof CURRENCY_SYMBOLS.ETH}`; + [K in Exclude]: string; } = { [NETWORK_TYPES.GOERLI]: `${typedCapitalize(NETWORK_TYPES.GOERLI)}${ CURRENCY_SYMBOLS.ETH @@ -262,10 +268,7 @@ export const TEST_NETWORK_TICKER_MAP: { [NETWORK_TYPES.SEPOLIA]: `${typedCapitalize(NETWORK_TYPES.SEPOLIA)}${ CURRENCY_SYMBOLS.ETH }`, - [NETWORK_TYPES.LINEA_TESTNET]: - `Linea${CURRENCY_SYMBOLS.ETH}` as `${Capitalize< - typeof NETWORK_TYPES.LINEA_TESTNET - >}${typeof CURRENCY_SYMBOLS.ETH}`, + [NETWORK_TYPES.LINEA_GOERLI]: `Linea${CURRENCY_SYMBOLS.ETH}`, }; /** @@ -284,11 +287,11 @@ export const BUILT_IN_NETWORKS = { ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], blockExplorerUrl: `https://${NETWORK_TYPES.SEPOLIA}.etherscan.io`, }, - [NETWORK_TYPES.LINEA_TESTNET]: { - networkId: NETWORK_IDS.LINEA_TESTNET, - chainId: CHAIN_IDS.LINEA_TESTNET, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_TESTNET], - blockExplorerUrl: 'https://explorer.goerli.linea.build', + [NETWORK_TYPES.LINEA_GOERLI]: { + networkId: NETWORK_IDS.LINEA_GOERLI, + chainId: CHAIN_IDS.LINEA_GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_GOERLI], + blockExplorerUrl: 'https://goerli.lineascan.build', }, [NETWORK_TYPES.MAINNET]: { networkId: NETWORK_IDS.MAINNET, @@ -312,18 +315,18 @@ export const NETWORK_TO_NAME_MAP = { [NETWORK_TYPES.MAINNET]: MAINNET_DISPLAY_NAME, [NETWORK_TYPES.GOERLI]: GOERLI_DISPLAY_NAME, [NETWORK_TYPES.SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [NETWORK_TYPES.LINEA_TESTNET]: LINEA_TESTNET_DISPLAY_NAME, + [NETWORK_TYPES.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, [NETWORK_TYPES.LOCALHOST]: LOCALHOST_DISPLAY_NAME, [NETWORK_IDS.GOERLI]: GOERLI_DISPLAY_NAME, [NETWORK_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [NETWORK_IDS.LINEA_TESTNET]: LINEA_TESTNET_DISPLAY_NAME, + [NETWORK_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, [NETWORK_IDS.MAINNET]: MAINNET_DISPLAY_NAME, [NETWORK_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, [CHAIN_IDS.GOERLI]: GOERLI_DISPLAY_NAME, [CHAIN_IDS.SEPOLIA]: SEPOLIA_DISPLAY_NAME, - [CHAIN_IDS.LINEA_TESTNET]: LINEA_TESTNET_DISPLAY_NAME, + [CHAIN_IDS.LINEA_GOERLI]: LINEA_GOERLI_DISPLAY_NAME, [CHAIN_IDS.MAINNET]: MAINNET_DISPLAY_NAME, [CHAIN_IDS.LOCALHOST]: LOCALHOST_DISPLAY_NAME, } as const; @@ -332,20 +335,21 @@ export const CHAIN_ID_TO_TYPE_MAP = { [CHAIN_IDS.MAINNET]: NETWORK_TYPES.MAINNET, [CHAIN_IDS.GOERLI]: NETWORK_TYPES.GOERLI, [CHAIN_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, - [CHAIN_IDS.LINEA_TESTNET]: NETWORK_TYPES.LINEA_TESTNET, + [CHAIN_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, [CHAIN_IDS.LOCALHOST]: NETWORK_TYPES.LOCALHOST, } as const; export const CHAIN_ID_TO_RPC_URL_MAP = { [CHAIN_IDS.GOERLI]: GOERLI_RPC_URL, [CHAIN_IDS.SEPOLIA]: SEPOLIA_RPC_URL, - [CHAIN_IDS.LINEA_TESTNET]: LINEA_TESTNET_RPC_URL, + [CHAIN_IDS.LINEA_GOERLI]: LINEA_GOERLI_RPC_URL, [CHAIN_IDS.MAINNET]: MAINNET_RPC_URL, [CHAIN_IDS.LOCALHOST]: LOCALHOST_RPC_URL, } as const; export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { [CHAIN_IDS.MAINNET]: ETH_TOKEN_IMAGE_URL, + [CHAIN_IDS.LINEA_GOERLI]: LINEA_GOERLI_TOKEN_IMAGE_URL, [CHAIN_IDS.AVALANCHE]: AVAX_TOKEN_IMAGE_URL, [CHAIN_IDS.BSC]: BNB_TOKEN_IMAGE_URL, [CHAIN_IDS.POLYGON]: MATIC_TOKEN_IMAGE_URL, @@ -361,7 +365,7 @@ export const CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP = { export const NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP = { [NETWORK_IDS.GOERLI]: NETWORK_TYPES.GOERLI, [NETWORK_IDS.SEPOLIA]: NETWORK_TYPES.SEPOLIA, - [NETWORK_IDS.LINEA_TESTNET]: NETWORK_TYPES.LINEA_TESTNET, + [NETWORK_IDS.LINEA_GOERLI]: NETWORK_TYPES.LINEA_GOERLI, [NETWORK_IDS.MAINNET]: NETWORK_NAMES.HOMESTEAD, } as const; @@ -369,7 +373,7 @@ export const CHAIN_ID_TO_NETWORK_ID_MAP = { [CHAIN_IDS.MAINNET]: NETWORK_IDS.MAINNET, [CHAIN_IDS.GOERLI]: NETWORK_IDS.GOERLI, [CHAIN_IDS.SEPOLIA]: NETWORK_IDS.SEPOLIA, - [CHAIN_IDS.LINEA_TESTNET]: NETWORK_IDS.LINEA_TESTNET, + [CHAIN_IDS.LINEA_GOERLI]: NETWORK_IDS.LINEA_GOERLI, [CHAIN_IDS.LOCALHOST]: NETWORK_IDS.LOCALHOST, } as const; @@ -411,10 +415,10 @@ export const ETHERSCAN_SUPPORTED_NETWORKS = { }`, networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.SEPOLIA], }, - [CHAIN_IDS.LINEA_TESTNET]: { - domain: 'linea.build', - subdomain: 'explorer.goerli', - networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_TESTNET], + [CHAIN_IDS.LINEA_GOERLI]: { + domain: 'lineascan.build', + subdomain: 'goerli', + networkId: CHAIN_ID_TO_NETWORK_ID_MAP[CHAIN_IDS.LINEA_GOERLI], }, [CHAIN_IDS.BSC]: { domain: 'bscscan.com', @@ -508,18 +512,13 @@ export const BUYABLE_CHAINS_MAP: { [K in Exclude< ChainId, | typeof CHAIN_IDS.LOCALHOST - | typeof CHAIN_IDS.PALM - | typeof CHAIN_IDS.HARMONY | typeof CHAIN_IDS.OPTIMISM_TESTNET | typeof CHAIN_IDS.BSC_TESTNET | typeof CHAIN_IDS.POLYGON_TESTNET | typeof CHAIN_IDS.AVALANCHE_TESTNET | typeof CHAIN_IDS.FANTOM_TESTNET - | typeof CHAIN_IDS.MOONBEAM | typeof CHAIN_IDS.MOONBEAM_TESTNET - | typeof CHAIN_IDS.MOONRIVER - | typeof CHAIN_IDS.AURORA - | typeof CHAIN_IDS.LINEA_TESTNET + | typeof CHAIN_IDS.LINEA_GOERLI | typeof CHAIN_IDS.GOERLI >]: BuyableChainSettings; } = { @@ -559,6 +558,30 @@ export const BUYABLE_CHAINS_MAP: { nativeCurrency: CURRENCY_SYMBOLS.ARBITRUM, network: 'arbitrum', }, + [CHAIN_IDS.CRONOS]: { + nativeCurrency: CURRENCY_SYMBOLS.CRONOS, + network: 'cronos', + }, + [CHAIN_IDS.MOONBEAM]: { + nativeCurrency: CURRENCY_SYMBOLS.GLIMMER, + network: 'moonbeam', + }, + [CHAIN_IDS.MOONRIVER]: { + nativeCurrency: CURRENCY_SYMBOLS.MOONRIVER, + network: 'moonriver', + }, + [CHAIN_IDS.AURORA]: { + nativeCurrency: CURRENCY_SYMBOLS.AURORA_ETH, + network: 'aurora', + }, + [CHAIN_IDS.HARMONY]: { + nativeCurrency: CURRENCY_SYMBOLS.ONE, + network: 'harmony', + }, + [CHAIN_IDS.PALM]: { + nativeCurrency: CURRENCY_SYMBOLS.PALM, + network: 'palm', + }, }; export const FEATURED_RPCS: RPCDefinition[] = [ @@ -664,9 +687,6 @@ export const FEATURED_RPCS: RPCDefinition[] = [ }, ]; -export const SHOULD_SHOW_LINEA_TESTNET_NETWORK = - new Date().getTime() > Date.UTC(2023, 2, 28, 8); - /** * Represents the availability state of the currently selected network. */ diff --git a/shared/constants/permissions.test.js b/shared/constants/permissions.test.js index fb68e5342..031c41c0a 100644 --- a/shared/constants/permissions.test.js +++ b/shared/constants/permissions.test.js @@ -14,8 +14,8 @@ describe('EndowmentPermissions', () => { [ 'endowment:long-running', ...Object.keys(endowmentPermissionBuilders).filter( - (targetKey) => - !Object.keys(ExcludedSnapEndowments).includes(targetKey), + (targetName) => + !Object.keys(ExcludedSnapEndowments).includes(targetName), ), ].sort(), ); @@ -28,8 +28,8 @@ describe('RestrictedMethods', () => { [ 'eth_accounts', ...Object.keys(restrictedMethodPermissionBuilders).filter( - (targetKey) => - !Object.keys(ExcludedSnapPermissions).includes(targetKey), + (targetName) => + !Object.keys(ExcludedSnapPermissions).includes(targetName), ), ].sort(), ); diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index b1686be9b..093ef470b 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -15,6 +15,10 @@ export const QUOTES_NOT_AVAILABLE_ERROR = 'quotes-not-avilable'; export const CONTRACT_DATA_DISABLED_ERROR = 'contract-data-disabled'; export const OFFLINE_FOR_MAINTENANCE = 'offline-for-maintenance'; export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict'; +export const SLIPPAGE_OVER_LIMIT_ERROR = 'slippage-over-limit'; +export const SLIPPAGE_VERY_HIGH_ERROR = 'slippage-very-high'; +export const SLIPPAGE_TOO_LOW_ERROR = 'slippage-too-low'; +export const SLIPPAGE_NEGATIVE_ERROR = 'slippage-negative'; // An address that the metaswap-api recognizes as the default token for the current network, // in place of the token address that ERC-20 tokens have diff --git a/shared/constants/terms.js b/shared/constants/terms.js index 5ced85fb5..40521fa59 100644 --- a/shared/constants/terms.js +++ b/shared/constants/terms.js @@ -1 +1,2 @@ +export const TERMS_OF_USE_LINK = 'https://consensys.net/terms-of-use/'; export const TERMS_OF_USE_LAST_UPDATED = '2023-03-25'; diff --git a/shared/constants/transaction.ts b/shared/constants/transaction.ts index dad1a72e1..1d7021402 100644 --- a/shared/constants/transaction.ts +++ b/shared/constants/transaction.ts @@ -165,6 +165,18 @@ export const IN_PROGRESS_TRANSACTION_STATUSES = [ TransactionStatus.pending, ]; +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +/** + * Status for finalized transactions. + */ +export const FINALIZED_TRANSACTION_STATUSES = [ + TransactionStatus.rejected, + TransactionStatus.failed, + TransactionStatus.dropped, + TransactionStatus.confirmed, +]; +///: END:ONLY_INCLUDE_IN + /** * Transaction Group Status is a MetaMask construct to track the status of groups * of transactions. diff --git a/shared/modules/updateTxData.js b/shared/modules/updateTxData.js new file mode 100644 index 000000000..02d6cd8b1 --- /dev/null +++ b/shared/modules/updateTxData.js @@ -0,0 +1,59 @@ +import { TransactionType } from '../constants/transaction'; + +export default function updateTxData({ + txData, + maxFeePerGas, + customTokenAmount, + dappProposedTokenAmount, + currentTokenBalance, + maxPriorityFeePerGas, + baseFeePerGas, + addToAddressBookIfNew, + toAccounts, + toAddress, + name, +}) { + if (txData.type === TransactionType.simpleSend) { + addToAddressBookIfNew(toAddress, toAccounts); + } + + if (baseFeePerGas) { + txData.estimatedBaseFee = baseFeePerGas; + } + + if (name) { + txData.contractMethodName = name; + } + + if (dappProposedTokenAmount) { + txData.dappProposedTokenAmount = dappProposedTokenAmount; + txData.originalApprovalAmount = dappProposedTokenAmount; + } + + if (customTokenAmount) { + txData.customTokenAmount = customTokenAmount; + txData.finalApprovalAmount = customTokenAmount; + } else if (dappProposedTokenAmount !== undefined) { + txData.finalApprovalAmount = dappProposedTokenAmount; + } + + if (currentTokenBalance) { + txData.currentTokenBalance = currentTokenBalance; + } + + if (maxFeePerGas) { + txData.txParams = { + ...txData.txParams, + maxFeePerGas, + }; + } + + if (maxPriorityFeePerGas) { + txData.txParams = { + ...txData.txParams, + maxPriorityFeePerGas, + }; + } + + return txData; +} diff --git a/shared/modules/updateTxData.test.js b/shared/modules/updateTxData.test.js new file mode 100644 index 000000000..f0c1a20f0 --- /dev/null +++ b/shared/modules/updateTxData.test.js @@ -0,0 +1,102 @@ +import { TransactionType } from '../constants/transaction'; +import updateTxData from './updateTxData'; + +describe('updateTxData', () => { + const mockAddToAddressBookIfNew = jest.fn(); + + afterEach(() => { + mockAddToAddressBookIfNew.mockClear(); + }); + + it('should add to address book if txData type is simpleSend', () => { + const txData = { + type: TransactionType.simpleSend, + }; + updateTxData({ + txData, + addToAddressBookIfNew: mockAddToAddressBookIfNew, + toAccounts: 'mockToAccounts', + toAddress: 'mockToAddress', + }); + expect(mockAddToAddressBookIfNew).toHaveBeenCalledWith( + 'mockToAddress', + 'mockToAccounts', + ); + }); + + it('should update estimatedBaseFee if baseFeePerGas is provided', () => { + const txData = {}; + const result = updateTxData({ + txData, + baseFeePerGas: 'mockBaseFeePerGas', + }); + expect(result.estimatedBaseFee).toBe('mockBaseFeePerGas'); + }); + + it('should update contractMethodName if name is provided', () => { + const txData = {}; + const result = updateTxData({ + txData, + name: 'mockName', + }); + expect(result.contractMethodName).toBe('mockName'); + }); + + it('should update dappProposedTokenAmount and originalApprovalAmount if dappProposedTokenAmount is provided', () => { + const txData = {}; + const result = updateTxData({ + txData, + dappProposedTokenAmount: 'mockDappProposedTokenAmount', + }); + expect(result.dappProposedTokenAmount).toBe('mockDappProposedTokenAmount'); + expect(result.originalApprovalAmount).toBe('mockDappProposedTokenAmount'); + }); + + it('should update customTokenAmount and finalApprovalAmount if customTokenAmount is provided', () => { + const txData = {}; + const result = updateTxData({ + txData, + customTokenAmount: 'mockCustomTokenAmount', + }); + expect(result.customTokenAmount).toBe('mockCustomTokenAmount'); + expect(result.finalApprovalAmount).toBe('mockCustomTokenAmount'); + }); + + it('should update finalApprovalAmount if dappProposedTokenAmount is provided but customTokenAmount is not', () => { + const txData = {}; + const result = updateTxData({ + txData, + dappProposedTokenAmount: 'mockDappProposedTokenAmount', + }); + expect(result.finalApprovalAmount).toBe('mockDappProposedTokenAmount'); + }); + + it('should update currentTokenBalance if currentTokenBalance is provided', () => { + const txData = {}; + const result = updateTxData({ + txData, + currentTokenBalance: 'mockCurrentTokenBalance', + }); + expect(result.currentTokenBalance).toBe('mockCurrentTokenBalance'); + }); + + it('should update maxFeePerGas in txParams if maxFeePerGas is provided', () => { + const txData = { txParams: {} }; + const result = updateTxData({ + txData, + maxFeePerGas: 'mockMaxFeePerGas', + }); + expect(result.txParams.maxFeePerGas).toBe('mockMaxFeePerGas'); + }); + + it('should update maxPriorityFeePerGas in txParams if maxPriorityFeePerGas is provided', () => { + const txData = { txParams: {} }; + const result = updateTxData({ + txData, + maxPriorityFeePerGas: 'mockMaxPriorityFeePerGas', + }); + expect(result.txParams.maxPriorityFeePerGas).toBe( + 'mockMaxPriorityFeePerGas', + ); + }); +}); diff --git a/shared/notifications/index.js b/shared/notifications/index.js index 9bf285957..a5a689f3a 100644 --- a/shared/notifications/index.js +++ b/shared/notifications/index.js @@ -106,6 +106,14 @@ export const UI_NOTIFICATIONS = { id: 20, date: null, }, + 21: { + id: 21, + date: null, + image: { + src: 'images/swaps-redesign.svg', + width: '100%', + }, + }, }; export const getTranslatedUINotifications = (t, locale) => { @@ -294,5 +302,16 @@ export const getTranslatedUINotifications = (t, locale) => { ) : '', }, + 21: { + ...UI_NOTIFICATIONS[21], + title: t('notifications21Title'), + description: t('notifications21Description'), + actionText: t('notifications21ActionText'), + date: UI_NOTIFICATIONS[21].date + ? new Intl.DateTimeFormat(formattedLocale).format( + new Date(UI_NOTIFICATIONS[21].date), + ) + : '', + }, }; }; diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 5bbb6386f..dc9b7e3e0 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -165,7 +165,13 @@ } }, "frequentRpcListDetail": [], - "subjectMetadata": {}, + "subjectMetadata": { + "npm:@metamask/test-snap-bip44": { + "name": "@metamask/test-snap-bip44", + "version": "1.2.3", + "subjectType": "snap" + } + }, "notifications": { "test": { "id": "test", @@ -316,7 +322,7 @@ }, "allNftContracts": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { - "1": [ + "0x1": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "description": null, @@ -329,7 +335,7 @@ "externalLink": null } ], - "137": [ + "0x89": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "description": null, @@ -342,7 +348,7 @@ "externalLink": null } ], - "11155111": [ + "0xaa36a7": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "description": null, @@ -355,7 +361,7 @@ "externalLink": null } ], - "5": [ + "0x5": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "description": null, @@ -379,7 +385,7 @@ "externalLink": null } ], - "153": [ + "0x99": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "description": null, @@ -396,7 +402,7 @@ }, "allNfts": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { - "1": [ + "0x1": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "tokenId": "1", @@ -406,7 +412,7 @@ "standard": "ERC721" } ], - "137": [ + "0x89": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "tokenId": "1", @@ -416,7 +422,7 @@ "standard": "ERC721" } ], - "11155111": [ + "0xaa36a7": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "tokenId": "1", @@ -426,7 +432,7 @@ "standard": "ERC721" } ], - "5": [ + "0x5": [ { "address": "0x495f947276749Ce646f68AC8c248420045cb7b5e", "tokenId": "58076532811975507823669075598676816378162417803895263482849101575514658701313", @@ -537,7 +543,7 @@ "standard": "ERC721" } ], - "153": [ + "0x99": [ { "address": "0xDc7382Eb0Bc9C352A4CbA23c909bDA01e0206414", "tokenId": "1", diff --git a/test/e2e/benchmark.js b/test/e2e/benchmark.js index df44a6dce..fc7d701a6 100755 --- a/test/e2e/benchmark.js +++ b/test/e2e/benchmark.js @@ -26,7 +26,7 @@ async function measurePage(pageName) { await driver.navigate(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.findElement('.selected-account__name'); + await driver.findElement('[data-testid="account-menu-icon"]'); await driver.navigate(pageName); await driver.delay(1000); metrics = await driver.collectMetrics(); diff --git a/test/e2e/fixture-builder.js b/test/e2e/fixture-builder.js index 051d3f02c..428814562 100644 --- a/test/e2e/fixture-builder.js +++ b/test/e2e/fixture-builder.js @@ -3,6 +3,7 @@ const { SnapCaveatType, } = require('@metamask/snaps-utils'); const { merge } = require('lodash'); +const { toHex } = require('@metamask/controller-utils'); const { CHAIN_IDS } = require('../../shared/constants/network'); const { ACTION_QUEUE_METRICS_E2E_TEST, @@ -135,6 +136,11 @@ function defaultFixture() { id: 19, isShown: true, }, + 21: { + date: null, + id: 21, + isShown: true, + }, }, }, AppStateController: { @@ -158,6 +164,7 @@ function defaultFixture() { [CHAIN_IDS.GOERLI]: true, [CHAIN_IDS.LOCALHOST]: true, }, + snapsInstallPrivacyWarningShown: true, }, CachedBalancesController: { cachedBalances: { @@ -182,6 +189,7 @@ function defaultFixture() { [CHAIN_IDS.MAINNET]: null, [CHAIN_IDS.GOERLI]: null, [CHAIN_IDS.SEPOLIA]: null, + [CHAIN_IDS.LINEA_GOERLI]: null, }, }, KeyringController: { @@ -289,7 +297,6 @@ function defaultFixture() { allTokens: {}, detectedTokens: [], ignoredTokens: [], - suggestedAssets: [], tokens: [], }, TransactionController: { @@ -393,7 +400,6 @@ function onboardingFixture() { allTokens: {}, detectedTokens: [], ignoredTokens: [], - suggestedAssets: [], tokens: [], }, config: {}, @@ -520,7 +526,7 @@ class FixtureBuilder { return this.withNftController({ allNftContracts: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { - 1337: [ + [toHex(1337)]: [ { address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.ERC1155}`, }, @@ -529,7 +535,7 @@ class FixtureBuilder { }, allNfts: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { - 1337: [ + [toHex(1337)]: [ { address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.ERC1155}`, tokenId: '1', @@ -552,10 +558,10 @@ class FixtureBuilder { return this.withNftController({ allNftContracts: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { - 1337: [ + [toHex(1337)]: [ { address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.NFTS}`, - name: 'TestDappCollectibles', + name: 'TestDappNFTs', symbol: 'TDC', }, ], @@ -563,15 +569,15 @@ class FixtureBuilder { }, allNfts: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': { - 1337: [ + [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', }, @@ -723,7 +729,7 @@ class FixtureBuilder { ignoredTokens: [], detectedTokens: [], allTokens: { - '0x539': { + [toHex(1337)]: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': [ { address: `__FIXTURE_SUBSTITUTION__CONTRACT${SMART_CONTRACTS.HST}`, @@ -739,7 +745,6 @@ class FixtureBuilder { }, allIgnoredTokens: {}, allDetectedTokens: {}, - suggestedAssets: [], }); return this; } diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index aaa2d8b16..f70d9d51a 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -225,6 +225,7 @@ const importSRPOnboardingFlow = async (driver, seedPhrase, password) => { // metrics await driver.clickElement('[data-testid="metametrics-no-thanks"]'); + await driver.waitForSelector('.import-srp__actions'); // import with recovery phrase await driver.pasteIntoField( '[data-testid="import-srp__srp-word-0"]', @@ -488,6 +489,7 @@ const sendTransaction = async (driver, recipientAddress, quantity) => { await driver.clickElement('[data-testid="page-container-footer-next"]'); await driver.clickElement('[data-testid="page-container-footer-next"]'); await driver.clickElement('[data-testid="home__activity-tab"]'); + await driver.waitForElementNotPresent('.transaction-list-item--unconfirmed'); await driver.findElement('.transaction-list-item'); }; @@ -496,12 +498,12 @@ const findAnotherAccountFromAccountList = async ( itemNumber, accountName, ) => { - await driver.clickElement('.account-menu__icon'); - const accountMenuItemSelector = `.account-menu__account:nth-child(${itemNumber})`; - const fourthAccountName = await driver.findElement( - `${accountMenuItemSelector} .account-menu__name`, + await driver.clickElement('[data-testid="account-menu-icon"]'); + const accountMenuItemSelector = `.multichain-account-list-item:nth-child(${itemNumber})`; + const acctName = await driver.findElement( + `${accountMenuItemSelector} .multichain-account-list-item__account-name__button`, ); - assert.equal(await fourthAccountName.getText(), accountName); + assert.equal(await acctName.getText(), accountName); return accountMenuItemSelector; }; @@ -511,12 +513,56 @@ const TEST_SEED_PHRASE = const TEST_SEED_PHRASE_TWO = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'; +// Usually happens when onboarded to make sure the state is retrieved from metamaskState properly +const assertAccountBalanceForDOM = async (driver, ganacheServer) => { + const balance = await ganacheServer.getBalance(); + const balanceElement = await driver.findElement( + '[data-testid="eth-overview__primary-currency"]', + ); + assert.equal(`${balance}\nETH`, await balanceElement.getText()); +}; + +// Usually happens after txn is made +const locateAccountBalanceDOM = async (driver, ganacheServer) => { + const balance = await ganacheServer.getBalance(); + await driver.waitForSelector({ + css: '[data-testid="eth-overview__primary-currency"]', + text: `${balance} ETH`, + }); +}; + +const restartServiceWorker = async (driver) => { + const serviceWorkerElements = await driver.findElements({ + text: 'terminate', + tag: 'span', + }); + // 1st one is app-init.js; while 2nd one is service-worker.js + await serviceWorkerElements[1].click(); +}; + +async function waitForAccountRendered(driver) { + await driver.waitForSelector( + '[data-testid="eth-overview__primary-currency"]', + ); +} + +const login = async (driver) => { + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); +}; + +const logInWithBalanceValidation = async (driver, ganacheServer) => { + await login(driver); + await assertAccountBalanceForDOM(driver, ganacheServer); +}; + module.exports = { DAPP_URL, DAPP_ONE_URL, SERVICE_WORKER_URL, TEST_SEED_PHRASE, TEST_SEED_PHRASE_TWO, + PRIVATE_KEY, getWindowHandles, convertToHexValue, tinyDelayMs, @@ -537,4 +583,10 @@ module.exports = { defaultGanacheOptions, sendTransaction, findAnotherAccountFromAccountList, + login, + logInWithBalanceValidation, + assertAccountBalanceForDOM, + locateAccountBalanceDOM, + restartServiceWorker, + waitForAccountRendered, }; diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 572e09076..e62958513 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -128,13 +128,13 @@ describe('MetaMask', function () { describe('Import Secret Recovery Phrase', function () { it('logs out of the vault', async function () { - await driver.clickElement('.account-menu__icon'); + await driver.clickElement('[data-testid="account-options-menu-button"]'); await driver.delay(regularDelayMs); const lockButton = await driver.findClickableElement( - '.account-menu__lock-button', + '[data-testid="global-menu-lock"]', ); - assert.equal(await lockButton.getText(), 'Lock'); + assert.equal(await lockButton.getText(), 'Lock MetaMask'); await lockButton.click(); await driver.delay(regularDelayMs); }); @@ -163,7 +163,7 @@ describe('MetaMask', function () { it('balance renders', async function () { await driver.waitForSelector({ - css: '[data-testid="wallet-balance"] .list-item__heading', + css: '[data-testid="eth-overview__primary-currency"] .currency-display-component__text', text: '1000', }); await driver.delay(regularDelayMs); @@ -246,7 +246,7 @@ describe('MetaMask', function () { it('clicks on the import tokens button', async function () { await driver.clickElement(`[data-testid="home__asset-tab"]`); - await driver.clickElement({ text: 'import tokens', tag: 'a' }); + await driver.clickElement({ text: 'Import tokens', tag: 'button' }); await driver.delay(regularDelayMs); }); @@ -282,7 +282,7 @@ describe('MetaMask', function () { await driver.delay(regularDelayMs); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); @@ -433,7 +433,7 @@ describe('MetaMask', function () { }); await driver.waitForSelector({ - css: '.asset-list-item__token-button', + css: '[data-testid="multichain-token-list-item-value"]', text: '7.5 TST', }); diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index b7e4e6e0c..29035f46c 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -99,7 +99,7 @@ async function setupMocking(server, testSpecificMock) { }); await server - .forGet('https://gas-api.metaswap.codefi.network/networks/1/gasPrices') + .forGet('https://gas-api.metaswap.codefi.network/networks/1337/gasPrices') .thenCallback(() => { return { statusCode: 200, @@ -130,7 +130,7 @@ async function setupMocking(server, testSpecificMock) { await server .forGet( - 'https://gas-api.metaswap.codefi.network/networks/1/suggestedGasFees', + 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', ) .thenCallback(() => { return { @@ -343,7 +343,7 @@ async function setupMocking(server, testSpecificMock) { }); await server - .forGet('https://token-api.metaswap.codefi.network/token/0x539') + .forGet('https://token-api.metaswap.codefi.network/token/1337') .thenCallback(() => { return { statusCode: 200, diff --git a/test/e2e/mv3/dapp-interactions.spec.js b/test/e2e/mv3/dapp-interactions.spec.js deleted file mode 100644 index 96dee4d8a..000000000 --- a/test/e2e/mv3/dapp-interactions.spec.js +++ /dev/null @@ -1,72 +0,0 @@ -const { strict: assert } = require('assert'); -const { - convertToHexValue, - withFixtures, - openDapp, - SERVICE_WORKER_URL, -} = require('../helpers'); -const FixtureBuilder = require('../fixture-builder'); - -describe('MV3 - Dapp interactions', function () { - let windowHandles; - const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), - }, - ], - concurrent: { port: 8546, chainId: 1338 }, - }; - it('should continue to support dapp interactions after service worker re-start', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .build(), - ganacheOptions: { - ...ganacheOptions, - }, - title: this.test.title, - }, - async ({ driver }) => { - await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - - await openDapp(driver); - - // Terminate Service Worker - await driver.openNewPage(SERVICE_WORKER_URL); - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - // Trigger Notification - windowHandles = await driver.getAllWindowHandles(); - await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); - await driver.clickElement('#addEthereumChain'); - await driver.waitUntilXWindowHandles(4); - await driver.switchToWindowWithTitle( - 'MetaMask Notification', - windowHandles, - ); - - const notification = await driver.isElementPresent({ - text: 'Allow this site to add a network?', - tag: 'h3', - }); - - assert.ok(notification, 'Dapp action does not appear in Metamask'); - }, - ); - }); -}); diff --git a/test/e2e/mv3/phishing-warning-sw-restart.spec.js b/test/e2e/mv3/phishing-warning-sw-restart.spec.js index 4557a0e57..de01139fe 100644 --- a/test/e2e/mv3/phishing-warning-sw-restart.spec.js +++ b/test/e2e/mv3/phishing-warning-sw-restart.spec.js @@ -2,14 +2,21 @@ const { strict: assert } = require('assert'); const { withFixtures, mockPhishingDetection, - SERVICE_WORKER_URL, openDapp, defaultGanacheOptions, + assertAccountBalanceForDOM, + restartServiceWorker, + SERVICE_WORKER_URL, + regularDelayMs, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Phishing warning page', function () { + const driverOptions = { openDevToolsForTabs: true }; + it('should restore the transaction when service worker restarts', async function () { + let windowHandles; + await withFixtures( { dapp: true, @@ -17,35 +24,42 @@ describe('Phishing warning page', function () { ganacheOptions: defaultGanacheOptions, title: this.test.title, testSpecificMock: mockPhishingDetection, + driverOptions, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); // log in wallet await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - // Restart service worker - await driver.openNewPage(SERVICE_WORKER_URL); - - await driver.clickElement({ - text: 'Service workers', - tag: 'button', - }); - - await driver.clickElement({ - text: 'terminate', - tag: 'span', - }); - - // Open the dapp site and extension detect it as phishing warning page + // DAPP is detected as phishing page await openDapp(driver); - - await driver.switchToWindowWithTitle('MetaMask Phishing Detection'); const phishingPageHeader = await driver.findElements({ text: 'Deceptive site ahead', tag: 'h1', }); assert.ok(phishingPageHeader.length, 1); + + // Restart service worker + await driver.openNewPage(SERVICE_WORKER_URL); + await restartServiceWorker(driver); + + await driver.delay(regularDelayMs); + // wait until extension is reloaded + windowHandles = await driver.getAllWindowHandles(); + const extension = windowHandles[0]; + await driver.switchToWindow(extension); + await assertAccountBalanceForDOM(driver, ganacheServer); + + // Open the dapp site and extension detect it as phishing warning page + await openDapp(driver); + // - extension, dapp, service worker and new dapp + await driver.waitUntilXWindowHandles(4); + const newPhishingPageHeader = await driver.findElements({ + text: 'Deceptive site ahead', + tag: 'h1', + }); + assert.ok(newPhishingPageHeader.length, 1); }, ); }); diff --git a/test/e2e/nft/erc721-interaction.spec.js b/test/e2e/nft/erc721-interaction.spec.js index d0603225a..e886be2e9 100644 --- a/test/e2e/nft/erc721-interaction.spec.js +++ b/test/e2e/nft/erc721-interaction.spec.js @@ -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( { @@ -50,8 +258,8 @@ describe('ERC721 NFTs testdapp interaction', function () { // Confirm transfer await driver.waitForSelector({ - css: '.confirm-page-container-summary__title', - text: 'TestDappCollectibles', + css: '.mm-text--heading-md', + 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); }, diff --git a/test/e2e/nft/import-erc1155.spec.js b/test/e2e/nft/import-erc1155.spec.js index 95992d770..598b70182 100644 --- a/test/e2e/nft/import-erc1155.spec.js +++ b/test/e2e/nft/import-erc1155.spec.js @@ -56,7 +56,7 @@ describe('Import ERC1155 NFT', function () { assert.equal(await importedERC1155.isDisplayed(), true); const importedERC1155Image = await driver.findVisibleElement( - '.nfts-items__item img', + '.nft-item__item', ); assert.equal(await importedERC1155Image.isDisplayed(), true); }, diff --git a/test/e2e/nft/import-nft.spec.js b/test/e2e/nft/import-nft.spec.js index b0985e7dd..60e375b5c 100644 --- a/test/e2e/nft/import-nft.spec.js +++ b/test/e2e/nft/import-nft.spec.js @@ -51,10 +51,10 @@ 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( - '.nfts-items__item-image', + '.nft-item__item-image', ); assert.equal(await importedNft.isDisplayed(), true); assert.equal(await importedNftImage.isDisplayed(), true); diff --git a/test/e2e/nft/remove-erc1155.spec.js b/test/e2e/nft/remove-erc1155.spec.js index b2b52812c..90f0be7db 100644 --- a/test/e2e/nft/remove-erc1155.spec.js +++ b/test/e2e/nft/remove-erc1155.spec.js @@ -32,7 +32,7 @@ describe('Remove ERC1155 NFT', function () { // Open the details page and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); const importedNftImage = await driver.findVisibleElement( - '.nfts-items__item img', + '.nft-item__item', ); await importedNftImage.click(); await driver.clickElement('[data-testid="nft-options__button"]'); diff --git a/test/e2e/nft/remove-nft.spec.js b/test/e2e/nft/remove-nft.spec.js index 3999d16fb..6edf681ef 100644 --- a/test/e2e/nft/remove-nft.spec.js +++ b/test/e2e/nft/remove-nft.spec.js @@ -31,7 +31,7 @@ describe('Remove NFT', function () { // Open the details and click remove nft button await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement('.nfts-items__item-image'); + await driver.clickElement('.nft-item__item-image'); await driver.clickElement('[data-testid="nft-options__button"]'); await driver.clickElement('[data-testid="nft-item-remove"]'); diff --git a/test/e2e/nft/send-nft.spec.js b/test/e2e/nft/send-nft.spec.js index 2c58af6c5..e08d12eb1 100644 --- a/test/e2e/nft/send-nft.spec.js +++ b/test/e2e/nft/send-nft.spec.js @@ -31,10 +31,10 @@ describe('Send NFT', function () { // Fill the send NFT form and confirm the transaction await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement('.nfts-items__item-image'); + await driver.clickElement('.nft-item__item-image'); await driver.clickElement({ text: 'Send', tag: 'button' }); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0xc427D562164062a23a5cFf596A4a3208e72Acd28', ); await driver.clickElement({ text: 'Next', tag: 'button' }); @@ -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); diff --git a/test/e2e/nft/view-erc1155-details.spec.js b/test/e2e/nft/view-erc1155-details.spec.js index 3a1167728..65bf128b8 100644 --- a/test/e2e/nft/view-erc1155-details.spec.js +++ b/test/e2e/nft/view-erc1155-details.spec.js @@ -34,7 +34,7 @@ describe('View ERC1155 NFT details', function () { // Click to open the NFT details page and check displayed account await driver.clickElement('[data-testid="home__nfts-tab"]'); const importedNftImage = await driver.findVisibleElement( - '.nfts-items__item img', + '.nft-item__item', ); await importedNftImage.click(); const detailsPageAccount = await driver.findElement( @@ -54,7 +54,7 @@ describe('View ERC1155 NFT details', function () { 'This is a collection of Rock NFTs.', ); - const nftImage = await driver.findElement('.nft-details__image'); + const nftImage = await driver.findElement('.nft-item__item-image'); assert.equal(await nftImage.isDisplayed(), true); const nftImageSource = await driver.findElement( diff --git a/test/e2e/nft/view-nft-details.spec.js b/test/e2e/nft/view-nft-details.spec.js index 7ca84b14b..2c3169dbe 100644 --- a/test/e2e/nft/view-nft-details.spec.js +++ b/test/e2e/nft/view-nft-details.spec.js @@ -33,27 +33,27 @@ describe('View NFT details', function () { // Click to open the NFT details page and check title await driver.clickElement('[data-testid="home__nfts-tab"]'); - await driver.clickElement('.nfts-items__item-image'); + await driver.clickElement('.nft-item__item-image'); 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-details__image'); + const nftImage = await driver.findElement('.nft-item__item-image'); assert.equal(await nftImage.isDisplayed(), true); const nftImageSource = await driver.findElement( diff --git a/test/e2e/seeder/ganache-seeder.js b/test/e2e/seeder/ganache-seeder.js index 5a68134ef..73b554236 100644 --- a/test/e2e/seeder/ganache-seeder.js +++ b/test/e2e/seeder/ganache-seeder.js @@ -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(); diff --git a/test/e2e/seeder/smart-contracts.js b/test/e2e/seeder/smart-contracts.js index 48d472b4e..365173a66 100644 --- a/test/e2e/seeder/smart-contracts.js +++ b/test/e2e/seeder/smart-contracts.js @@ -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 = { diff --git a/test/e2e/snaps/test-snap-bip-32.spec.js b/test/e2e/snaps/test-snap-bip-32.spec.js index b477fade4..0cd744f2c 100644 --- a/test/e2e/snaps/test-snap-bip-32.spec.js +++ b/test/e2e/snaps/test-snap-bip-32.spec.js @@ -52,10 +52,12 @@ describe('Test Snap bip-32', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement('[data-testid="snap-install-scroll"]'); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); @@ -71,9 +73,9 @@ describe('Test Snap bip-32', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-bip-44.spec.js b/test/e2e/snaps/test-snap-bip-44.spec.js index cbee5ff15..5df417861 100644 --- a/test/e2e/snaps/test-snap-bip-44.spec.js +++ b/test/e2e/snaps/test-snap-bip-44.spec.js @@ -52,9 +52,9 @@ describe('Test Snap bip-44', function () { text: 'Connect', tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); @@ -65,9 +65,9 @@ describe('Test Snap bip-44', function () { text: 'Confirm', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-cronjob.spec.js b/test/e2e/snaps/test-snap-cronjob.spec.js index 78a15490a..7fb31e361 100644 --- a/test/e2e/snaps/test-snap-cronjob.spec.js +++ b/test/e2e/snaps/test-snap-cronjob.spec.js @@ -52,17 +52,17 @@ describe('Test Snap Cronjob', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -90,7 +90,7 @@ describe('Test Snap Cronjob', function () { // try to click on the Ok button and pass test if it works await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); }, diff --git a/test/e2e/snaps/test-snap-dialog.spec.js b/test/e2e/snaps/test-snap-dialog.spec.js index f2b51321b..28825c0e2 100644 --- a/test/e2e/snaps/test-snap-dialog.spec.js +++ b/test/e2e/snaps/test-snap-dialog.spec.js @@ -52,17 +52,17 @@ describe('Test Snap Dialog', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -95,7 +95,7 @@ describe('Test Snap Dialog', function () { // click ok button await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-error.spec.js b/test/e2e/snaps/test-snap-error.spec.js index 3e116c6e3..c8e17efdc 100644 --- a/test/e2e/snaps/test-snap-error.spec.js +++ b/test/e2e/snaps/test-snap-error.spec.js @@ -53,17 +53,17 @@ describe('Test Snap Error', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-ethprovider.spec.js b/test/e2e/snaps/test-snap-ethprovider.spec.js index bd18fede1..694c25aef 100644 --- a/test/e2e/snaps/test-snap-ethprovider.spec.js +++ b/test/e2e/snaps/test-snap-ethprovider.spec.js @@ -51,17 +51,17 @@ describe('Test Snap ethereum_provider', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-getentropy.spec.js b/test/e2e/snaps/test-snap-getentropy.spec.js index c29da48d1..8b5586d24 100644 --- a/test/e2e/snaps/test-snap-getentropy.spec.js +++ b/test/e2e/snaps/test-snap-getentropy.spec.js @@ -51,17 +51,17 @@ describe('Test Snap getEntropy', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-installed.spec.js b/test/e2e/snaps/test-snap-installed.spec.js index d14852a52..71614a6f0 100644 --- a/test/e2e/snaps/test-snap-installed.spec.js +++ b/test/e2e/snaps/test-snap-installed.spec.js @@ -51,17 +51,17 @@ describe('Test Snap Installed', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -90,17 +90,17 @@ describe('Test Snap Installed', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-management.spec.js b/test/e2e/snaps/test-snap-management.spec.js index 98503cadc..df520d30a 100644 --- a/test/e2e/snaps/test-snap-management.spec.js +++ b/test/e2e/snaps/test-snap-management.spec.js @@ -54,17 +54,17 @@ describe('Test Snap Management', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -73,15 +73,13 @@ describe('Test Snap Management', function () { await driver.switchToWindow(extensionPage); await driver.delay(1000); - // click on the account menu icon - await driver.clickElement('.account-menu__icon'); - await driver.delay(1000); + // click on the global action menu + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); // try to click on the notification item - await driver.clickElement({ - text: 'Settings', - tag: 'div', - }); + await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.delay(1000); // try to click on the snaps item @@ -129,10 +127,14 @@ describe('Test Snap Management', function () { // check to see that there is one notification await driver.switchToWindow(extensionPage); await driver.delay(1000); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); const notificationResult = await driver.findElement( - '.account-menu__icon__notification-count', + '[data-testid="global-menu-notification-count"]', ); assert.equal(await notificationResult.getText(), '1'); + await driver.clickElement('.menu__background'); // try to remove snap await driver.clickElement({ diff --git a/test/e2e/snaps/test-snap-managestate.spec.js b/test/e2e/snaps/test-snap-managestate.spec.js index 7e209d01a..5bcc9bcbb 100644 --- a/test/e2e/snaps/test-snap-managestate.spec.js +++ b/test/e2e/snaps/test-snap-managestate.spec.js @@ -55,17 +55,17 @@ describe('Test Snap manageState', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-networkaccess.spec.js b/test/e2e/snaps/test-snap-networkaccess.spec.js index 8ea925712..e80012a55 100644 --- a/test/e2e/snaps/test-snap-networkaccess.spec.js +++ b/test/e2e/snaps/test-snap-networkaccess.spec.js @@ -54,17 +54,17 @@ describe('Test Snap networkAccess', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -97,7 +97,7 @@ describe('Test Snap networkAccess', function () { // click ok button await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); }, diff --git a/test/e2e/snaps/test-snap-notification.spec.js b/test/e2e/snaps/test-snap-notification.spec.js index 5113a327a..d53fe06b9 100644 --- a/test/e2e/snaps/test-snap-notification.spec.js +++ b/test/e2e/snaps/test-snap-notification.spec.js @@ -55,17 +55,17 @@ describe('Test Snap Notification', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -85,19 +85,25 @@ describe('Test Snap Notification', function () { await driver.delay(1000); // check to see that there is one notification + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); const notificationResult = await driver.findElement( - '.account-menu__icon__notification-count', + '[data-testid="global-menu-notification-count"]', ); assert.equal(await notificationResult.getText(), '1'); + await driver.clickElement('.menu__background'); // try to click on the account menu icon (via xpath) - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.delay(500); // try to click on the notification item (via xpath) await driver.clickElement({ text: 'Notifications', - tag: 'div', + tag: 'span', }); await driver.delay(500); diff --git a/test/e2e/snaps/test-snap-rpc.spec.js b/test/e2e/snaps/test-snap-rpc.spec.js index 3505a6c85..5fe574490 100644 --- a/test/e2e/snaps/test-snap-rpc.spec.js +++ b/test/e2e/snaps/test-snap-rpc.spec.js @@ -53,10 +53,12 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement('[data-testid="snap-install-scroll"]'); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); @@ -72,10 +74,10 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -98,17 +100,17 @@ describe('Test Snap RPC', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-txinsights.spec.js b/test/e2e/snaps/test-snap-txinsights.spec.js index 138e42897..cb8bfc3b1 100644 --- a/test/e2e/snaps/test-snap-txinsights.spec.js +++ b/test/e2e/snaps/test-snap-txinsights.spec.js @@ -53,17 +53,17 @@ describe('Test Snap TxInsights', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-update.spec.js b/test/e2e/snaps/test-snap-update.spec.js index ac80c18ff..485c87b66 100644 --- a/test/e2e/snaps/test-snap-update.spec.js +++ b/test/e2e/snaps/test-snap-update.spec.js @@ -53,10 +53,12 @@ describe('Test Snap update', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); + + await driver.clickElement('[data-testid="snap-install-scroll"]'); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); @@ -72,10 +74,10 @@ describe('Test Snap update', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); @@ -103,17 +105,19 @@ describe('Test Snap update', function () { windowHandles, ); - await driver.waitForSelector({ text: 'Approve & update' }); + await driver.waitForSelector({ text: 'Update' }); + + await driver.clickElement('[data-testid="snap-update-scroll"]'); await driver.clickElement({ - text: 'Approve & update', + text: 'Update', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/snaps/test-snap-wasm.spec.js b/test/e2e/snaps/test-snap-wasm.spec.js index 55326ac32..1af49492f 100644 --- a/test/e2e/snaps/test-snap-wasm.spec.js +++ b/test/e2e/snaps/test-snap-wasm.spec.js @@ -52,17 +52,17 @@ describe('Test Snap WASM', function () { tag: 'button', }); - await driver.waitForSelector({ text: 'Approve & install' }); + await driver.waitForSelector({ text: 'Install' }); await driver.clickElement({ - text: 'Approve & install', + text: 'Install', tag: 'button', }); - await driver.waitForSelector({ text: 'Ok' }); + await driver.waitForSelector({ text: 'OK' }); await driver.clickElement({ - text: 'Ok', + text: 'OK', tag: 'button', }); diff --git a/test/e2e/swaps/shared.js b/test/e2e/swaps/shared.js index ef7eb8278..12fbe4ccb 100644 --- a/test/e2e/swaps/shared.js +++ b/test/e2e/swaps/shared.js @@ -24,24 +24,25 @@ const loadExtension = async (driver) => { }; const buildQuote = async (driver, options) => { - await driver.clickElement( - '.wallet-overview__buttons .icon-button:nth-child(3)', - ); - await driver.fill('input[placeholder*="0"]', options.amount); - await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount. - await driver.waitForSelector( - '[class="dropdown-input-pair dropdown-input-pair__to"]', - ); - await driver.clickElement('.dropdown-input-pair__to'); + await driver.clickElement('[data-testid="token-overview-button-swap"]'); await driver.fill( - 'input[data-testid="search-list-items"]', + 'input[data-testid="prepare-swap-page-from-token-amount"]', + options.amount, + ); + await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount. + await driver.clickElement('[data-testid="prepare-swap-page-swap-to"]'); + await driver.waitForSelector('[id="list-with-search__text-search"]'); + + await driver.fill( + 'input[id="list-with-search__text-search"]', options.swapTo || options.swapToContractAddress, ); + await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount. if (options.swapTo) { await driver.wait(async () => { const tokenNames = await driver.findElements( - '.searchable-item-list__primary-label', + '[data-testid="searchable-item-list-primary-label"]', ); if (tokenNames.length === 0) { return false; @@ -51,118 +52,140 @@ const buildQuote = async (driver, options) => { }); } if (options.swapToContractAddress) { - await driver.waitForSelector({ - css: '.searchable-item-list__item button.btn-primary', - text: 'Import', - }); + await driver.waitForSelector( + '[data-testid="searchable-item-list-import-button"]', + ); } - await driver.clickElement('.searchable-item-list__primary-label'); + await driver.clickElement( + '[data-testid="searchable-item-list-primary-label"]', + ); }; const reviewQuote = async (driver, options) => { - await driver.clickElement({ text: 'Review swap', tag: 'button' }); - await driver.waitForSelector({ text: 'Swap', tag: 'button' }); - await driver.waitForSelector({ - css: '[class*="box--align-items-center"]', - text: 'Estimated gas fee', - }); - const sourceValue = await driver.waitForSelector( - '.main-quote-summary__source-row-value', + const summary = await driver.waitForSelector( + '[data-testid="exchange-rate-display-quote-rate"]', ); + const summaryText = await summary.getText(); + assert.equal(summaryText.includes(options.swapFrom), true); + assert.equal(summaryText.includes(options.swapTo), true); + const quote = summaryText.split(`\n`); + + const elementSwapToAmount = await driver.findElement( + '[data-testid="prepare-swap-page-receive-amount"]', + ); + const swapToAmount = await elementSwapToAmount.getText(); + const expectedAmount = parseFloat(quote[3]) * options.amount; + const dotIndex = swapToAmount.indexOf('.'); + const decimals = dotIndex === -1 ? 0 : swapToAmount.length - dotIndex - 1; assert.equal( - await sourceValue.getText(), - options.amount, - 'Error: Quote has wrong amount', + swapToAmount, + expectedAmount.toFixed(decimals), + `Expecting ${expectedAmount.toFixed( + decimals, + )} but got ${swapToAmount} instead`, ); - const sourceSymbol = await driver.waitForSelector( - '.main-quote-summary__source-row-symbol', - ); - assert.equal( - await sourceSymbol.getText(), - options.swapFrom, - 'Error: SwapFrom has wrong symbol', - ); - const swapToSymbol = await driver.waitForSelector( - '.main-quote-summary__destination-row > span', - ); - assert.equal( - await swapToSymbol.getText(), - options.swapTo, - 'Error: SwapTo has wrong symbol', - ); - await driver.waitForSelector( - '[class="exchange-rate-display main-quote-summary__exchange-rate-display"]', - ); - await driver.waitForSelector('[class="fee-card__info-tooltip-container"]'); - await driver.waitForSelector({ - css: '[class="countdown-timer__time"]', - text: '0:23', - }); + + await driver.findElement('[data-testid="review-quote-gas-fee-in-fiat"]'); + + await driver.findElement('[data-testid="info-tooltip"]'); + + if (!options.skipCounter) { + await driver.waitForSelector({ + css: '[data-testid="countdown-timer__timer-container"]', + text: '0:25', + }); + } }; -const waitForTransactionToComplete = async (driver, tokenName) => { - const sucessfulTransactionMessage = await driver.waitForSelector( +const waitForTransactionToComplete = async (driver, options) => { + await driver.waitForSelector({ + css: '[data-testid="awaiting-swap-header"]', + text: 'Processing', + }); + + await driver.waitForSelector( { - css: '[class="awaiting-swap__header"]', + css: '[data-testid="awaiting-swap-header"]', text: 'Transaction complete', }, { timeout: 30000 }, ); - assert.equal( - await sucessfulTransactionMessage.getText(), - 'Transaction complete', - 'Incorrect transaction message', - ); - const sucessfulTransactionToken = await driver.waitForSelector({ - css: '[class="awaiting-swap__amount-and-symbol"]', - text: tokenName, + + await driver.findElement({ + css: '[data-testid="awaiting-swap-main-description"]', + text: `${options.tokenName}`, }); - assert.equal( - await sucessfulTransactionToken.getText(), - tokenName, - 'Incorrect token name', - ); + await driver.clickElement({ text: 'Close', tag: 'button' }); await driver.waitForSelector('[data-testid="home__asset-tab"]'); }; const checkActivityTransaction = async (driver, options) => { await driver.clickElement('[data-testid="home__activity-tab"]'); - const itemsText = await driver.findElements('.list-item__title'); + await driver.waitForSelector('[data-testid="list-item-title"]'); + + const transactionList = await driver.findElements( + '[data-testid="list-item-title"]', + ); + const transactionText = await transactionList[options.index].getText(); assert.equal( - await itemsText[options.index].getText(), + transactionText, `Swap ${options.swapFrom} to ${options.swapTo}`, - 'Title is incorrect', + 'Transaction not found', ); - const amountValues = await driver.findElements( - '.transaction-list-item__primary-currency', - ); - assert.equal( - await amountValues[options.index].getText(), - `-${options.amount} ${options.swapFrom}`, - 'Transaction amount is incorrect', - ); - await itemsText[options.index].click(); + + await driver.findElement({ + css: '[data-testid="list-item-right-content"]', + text: `-${options.amount} ${options.swapFrom}`, + }); + + await transactionList[options.index].click(); await driver.delay(regularDelayMs); - const txStatus = await driver.findElement( - '.transaction-list-item-details__tx-status >div > div:last-child', - ); - assert.equal( - await txStatus.getText(), - `Confirmed`, - `Transaction status is not 'Confirmed'`, - ); - const txAmount = await driver.findElement( - '.transaction-breakdown__value--amount', - ); - assert.equal( - await txAmount.getText(), - `-${options.amount} ${options.swapFrom}`, - 'Transaction breakdown is incorrect', - ); + + await driver.findElement({ + css: '[data-testid="transaction-list-item-details-tx-status"]', + text: `Confirmed`, + }); + + await driver.findElement({ + css: '[data-testid="transaction-breakdown-value-amount"]', + text: `-${options.amount} ${options.swapFrom}`, + }); + await driver.clickElement('[data-testid="popover-close"]'); }; +const checkNotification = async (driver, options) => { + const boxTitle = await driver.findElement( + '[data-testid="mm-banner-base-title"]', + ); + assert.equal(await boxTitle.getText(), options.title, 'Invalid box title'); + const boxContent = await driver.findElement( + '[data-testid="mm-banner-alert-notification-text"]', + ); + const bodyText = await boxContent.getText(); + console.log(`test: ${bodyText}`); + assert.equal( + bodyText.includes(options.text), + true, + 'Invalid box text content', + ); +}; + +const changeExchangeRate = async (driver) => { + await driver.clickElement( + '[data-testid="exchange-rate-display-base-symbol"]', + ); + await driver.waitForSelector({ text: 'Quote details', tag: 'h2' }); + + const networkFees = await driver.findElements( + '[data-testid*="select-quote-popover-row"]', + ); + const random = Math.floor(Math.random() * networkFees.length); + await networkFees[random].click(); + await driver.clickElement({ text: 'Select', tag: 'button' }); +}; + module.exports = { withFixturesOptions, loadExtension, @@ -170,4 +193,6 @@ module.exports = { reviewQuote, waitForTransactionToComplete, checkActivityTransaction, + checkNotification, + changeExchangeRate, }; diff --git a/test/e2e/swaps/swap-eth.spec.js b/test/e2e/swaps/swap-eth.spec.js index 6506fa262..380ce2965 100644 --- a/test/e2e/swaps/swap-eth.spec.js +++ b/test/e2e/swaps/swap-eth.spec.js @@ -6,6 +6,7 @@ const { reviewQuote, waitForTransactionToComplete, checkActivityTransaction, + changeExchangeRate, } = require('./shared'); describe('Swap Eth for another Token', function () { @@ -25,7 +26,7 @@ describe('Swap Eth for another Token', function () { swapTo: 'USDC', }); await reviewQuote(driver, { - amount: '0.001', + amount: 0.001, swapFrom: 'TESTETH', swapTo: 'USDC', }); @@ -36,12 +37,12 @@ describe('Swap Eth for another Token', function () { swapTo: 'DAI', }); await reviewQuote(driver, { - amount: '0.003', + amount: 0.003, swapFrom: 'TESTETH', swapTo: 'DAI', }); await driver.clickElement({ text: 'Swap', tag: 'button' }); - await waitForTransactionToComplete(driver, 'DAI'); + await waitForTransactionToComplete(driver, { tokenName: 'DAI' }); await checkActivityTransaction(driver, { index: 0, amount: '0.003', @@ -57,7 +58,7 @@ describe('Swap Eth for another Token', function () { }, ); }); - it('Completes a Swap between Eth and Dai', async function () { + it('Completes a Swap between ETH and DAI after changing initial rate', async function () { await withFixtures( { ...withFixturesOptions, @@ -70,12 +71,19 @@ describe('Swap Eth for another Token', function () { swapTo: 'DAI', }); await reviewQuote(driver, { - amount: '2', + amount: 2, swapFrom: 'TESTETH', swapTo: 'DAI', }); + await changeExchangeRate(driver); + await reviewQuote(driver, { + amount: 2, + swapFrom: 'TESTETH', + swapTo: 'DAI', + skipCounter: true, + }); await driver.clickElement({ text: 'Swap', tag: 'button' }); - await waitForTransactionToComplete(driver, 'DAI'); + await waitForTransactionToComplete(driver, { tokenName: 'DAI' }); await checkActivityTransaction(driver, { index: 0, amount: '2', diff --git a/test/e2e/swaps/swaps-notifications.spec.js b/test/e2e/swaps/swaps-notifications.spec.js index 02bfe43dd..4d24bf5fe 100644 --- a/test/e2e/swaps/swaps-notifications.spec.js +++ b/test/e2e/swaps/swaps-notifications.spec.js @@ -1,8 +1,12 @@ const { strict: assert } = require('assert'); - const { withFixtures } = require('../helpers'); - -const { withFixturesOptions, loadExtension, buildQuote } = require('./shared'); +const { + withFixturesOptions, + loadExtension, + buildQuote, + reviewQuote, + checkNotification, +} = require('./shared'); describe('Swaps - notifications', function () { async function mockTradesApiPriceSlippageError(mockServer) { @@ -65,33 +69,34 @@ describe('Swaps - notifications', function () { amount: 2, swapTo: 'INUINU', }); - const reviewSwapButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await reviewSwapButton.getText(), 'Review swap'); - assert.equal(await reviewSwapButton.isEnabled(), false); - const continueButton = await driver.findClickableElement( - '.actionable-message__action-warning', - ); - assert.equal(await continueButton.getText(), 'Continue'); - await continueButton.click(); - assert.equal(await reviewSwapButton.isEnabled(), true); - await reviewSwapButton.click(); - await driver.waitForSelector({ - css: '[class*="box--align-items-center"]', - text: 'Estimated gas fee', + await checkNotification(driver, { + title: 'Potentially inauthentic token', + text: 'INUINU is only verified on 1 source. Consider verifying it on Etherscan before proceeding.', + }); + await driver.clickElement({ text: 'Continue swapping', tag: 'button' }); + await driver.waitForSelector({ + text: 'Swap', + tag: 'button', + }); + await checkNotification(driver, { + title: 'Check your rate before proceeding', + text: 'Price impact could not be determined due to lack of market price data.', + }); + await driver.clickElement({ text: 'Swap anyway', tag: 'button' }); + await reviewQuote(driver, { + amount: 2, + swapFrom: 'TESTETH', + swapTo: 'INUINU', + skipCounter: true, + }); + const swapButton = await driver.findElement({ + text: 'Swap', + tag: 'button', }); - const swapButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await swapButton.isEnabled(), false); - await driver.clickElement({ text: 'I understand', tag: 'button' }); - assert.equal(await swapButton.getText(), 'Swap'); assert.equal(await swapButton.isEnabled(), true); }, ); }); - it('tests a notification for not enough balance', async function () { await withFixtures( { @@ -104,30 +109,26 @@ describe('Swaps - notifications', function () { amount: 50, swapTo: 'USDC', }); - const reviewSwapButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await reviewSwapButton.getText(), 'Review swap'); - assert.equal(await reviewSwapButton.isEnabled(), true); - await reviewSwapButton.click(); - await driver.waitForSelector({ - css: '[class*="box--align-items-center"]', - text: 'Estimated gas fee', + await checkNotification(driver, { + title: 'Insufficient balance', + text: 'You need 50 more TESTETH to complete this swap', }); - await driver.waitForSelector({ - css: '[class*="actionable-message__message"]', - text: 'You need 43.4467 more TESTETH to complete this swap', + await reviewQuote(driver, { + swapFrom: 'TESTETH', + swapTo: 'USDC', + amount: 50, + skipCounter: true, + }); + const swapButton = await driver.waitForSelector({ + text: 'Swap', + tag: 'button', }); - const swapButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); assert.equal(await swapButton.getText(), 'Swap'); assert.equal(await swapButton.isEnabled(), false); }, ); }); - - it('tests notifications for verified token on 0 sources and high slippage', async function () { + it('tests notifications for token import', async function () { await withFixtures( { ...withFixturesOptions, @@ -139,39 +140,51 @@ describe('Swaps - notifications', function () { amount: 2, swapToContractAddress: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf', }); - await driver.waitForSelector({ - css: '.popover-header', - text: 'Import token?', - }); await driver.clickElement( '[data-testid="page-container__import-button"]', ); - const reviewSwapButton = await driver.findElement( - '[data-testid="page-container-footer-next"]', - ); - assert.equal(await reviewSwapButton.isEnabled(), false); - const continueButton = await driver.findClickableElement( - '.actionable-message__action-danger', - ); - assert.equal(await continueButton.getText(), 'Continue'); - await continueButton.click(); - assert.equal(await reviewSwapButton.isEnabled(), true); - await driver.clickElement('[class="slippage-buttons__header-text"]'); - await driver.clickElement({ text: 'custom', tag: 'button' }); - await driver.fill( - 'input[data-testid="slippage-buttons__custom-slippage"]', - '20', - ); - await driver.waitForSelector({ - css: '[class*="slippage-buttons__error-text"]', - text: 'Slippage amount is too high and will result in a bad rate. Please reduce your slippage tolerance to a value below 15%.', + await checkNotification(driver, { + title: 'Token added manually', + text: 'Verify this token on Etherscan and make sure it is the token you want to trade.', }); - assert.equal(await reviewSwapButton.isEnabled(), false); - await driver.fill( - 'input[data-testid="slippage-buttons__custom-slippage"]', - '4', - ); - assert.equal(await reviewSwapButton.isEnabled(), true); + }, + ); + }); + it('tests notifications for slippage', async function () { + await withFixtures( + { + ...withFixturesOptions, + title: this.test.title, + }, + async ({ driver }) => { + await loadExtension(driver); + await buildQuote(driver, { + amount: '.0001', + swapTo: 'DAI', + }); + await driver.clickElement('[title="Transaction settings"]'); + await driver.clickElement({ text: 'custom', tag: 'button' }); + await driver.fill('input[data-testid*="slippage"]', '0'); + await checkNotification(driver, { + title: 'Sourcing zero-slippage providers', + text: 'There are fewer zero-slippage quote providers which will result in a less competitive quote.', + }); + await driver.fill('input[data-testid*="slippage"]', '1'); + await checkNotification(driver, { + title: 'Increase slippage to avoid transaction failure', + text: 'Max slippage is too low which may cause your transaction to fail.', + }); + await driver.fill('input[data-testid*="slippage"]', '15'); + await checkNotification(driver, { + title: 'Very high slippage', + text: 'The slippage entered is considered very high and may result in a bad rate', + }); + await driver.fill('input[data-testid*="slippage"]', '20'); + await checkNotification(driver, { + title: 'Reduce slippage to continue', + text: 'Slippage tolerance must be 15% or less. Anything higher will result in a bad rate.', + }); + await driver.fill('input[data-testid*="slippage"]', '4'); }, ); }); diff --git a/test/e2e/tests/account-details.spec.js b/test/e2e/tests/account-details.spec.js index 8b8c990f0..9e70e6ee5 100644 --- a/test/e2e/tests/account-details.spec.js +++ b/test/e2e/tests/account-details.spec.js @@ -24,12 +24,11 @@ describe('Show account details', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); + await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement( - '[data-testid="account-options-menu__account-details"]', + '[data-testid="account-list-item-menu-button"]', ); + await driver.clickElement('[data-testid="account-list-menu-details"'); const qrCode = await driver.findElement('.qr-code__wrapper'); assert.equal(await qrCode.isDisplayed(), true); diff --git a/test/e2e/tests/add-account.spec.js b/test/e2e/tests/add-account.spec.js index 4b74e61b4..61a903653 100644 --- a/test/e2e/tests/add-account.spec.js +++ b/test/e2e/tests/add-account.spec.js @@ -1,18 +1,23 @@ const { strict: assert } = require('assert'); const { TEST_SEED_PHRASE, - convertToHexValue, withFixtures, - regularDelayMs, completeImportSRPOnboardingFlow, sendTransaction, findAnotherAccountFromAccountList, + waitForAccountRendered, + convertToHexValue, + regularDelayMs, } = require('../helpers'); -const enLocaleMessages = require('../../../app/_locales/en/messages.json'); + const FixtureBuilder = require('../fixture-builder'); +const { shortenAddress } = require('../../../ui/helpers/utils/util'); describe('Add account', function () { const testPassword = 'correct horse battery staple'; + const firstAccount = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; + const secondAccount = '0x3ED0eE22E0685Ebbf07b2360A8331693c413CC59'; + const ganacheOptions = { accounts: [ { @@ -22,8 +27,6 @@ describe('Add account', function () { }, ], }; - const firstAccount = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; - const secondAccount = '0x3ED0eE22E0685Ebbf07b2360A8331693c413CC59'; it('should display correct new account name after create', async function () { await withFixtures( @@ -37,13 +40,15 @@ describe('Add account', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - await driver.fill('.new-account-create-form input', '2nd account'); - await driver.clickElement({ text: 'Create', tag: 'button' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="multichain-account-menu-add-account"]', + ); + await driver.fill('[placeholder="Account 2"]', '2nd account'); + await driver.clickElement({ text: 'Create', tag: 'button' }); const accountName = await driver.waitForSelector({ - css: '.selected-account__name', + css: '[data-testid="account-menu-icon"]', text: '2nd', }); assert.equal(await accountName.getText(), '2nd account'); @@ -70,70 +75,81 @@ describe('Add account', function () { ); // Check address of 1st account - const firstAccountPublicAddress = await checkAccountDetails(driver); - assert.equal(firstAccountPublicAddress, firstAccount); - await driver.delay(regularDelayMs); + await waitForAccountRendered(driver); + const firstAccountPublicAddress = await retrieveShortenAccountAddress( + driver, + ); + assert.equal(firstAccountPublicAddress, shortenAddress(firstAccount)); - // Create a new account - await driver.findClickableElement('.account-menu__icon'); + // Create 2nd account await driver.clickElement('[data-testid="account-menu-icon"]'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.clickElement( + '[data-testid="multichain-account-menu-add-account"]', + ); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); + await waitForAccountRendered(driver); // Check address of 2nd account - const secondAccountPublicAddress = await checkAccountDetails(driver); - assert.strictEqual(secondAccountPublicAddress, secondAccount); - await driver.delay(regularDelayMs); + const secondAccountPublicAddress = await retrieveShortenAccountAddress( + driver, + ); + assert.strictEqual( + secondAccountPublicAddress, + shortenAddress(secondAccount), + ); - // Give 2nd locally account some balance so it will not be removed after recovering SRP + // Log into the account with balance(account 1) + // and transfer some balance to 2nd account + // so they will not be removed after recovering SRP const accountOneSelector = await findAnotherAccountFromAccountList( driver, 1, 'Account 1', ); + await waitForAccountRendered(driver); await driver.clickElement(accountOneSelector); await sendTransaction(driver, secondAccount, '2.8'); // Lock the account - await driver.clickElement('.account-menu__icon'); - await driver.delay(regularDelayMs); - - const lockButton = await driver.findClickableElement( - '.account-menu__lock-button', + await driver.clickElement( + '[data-testid="account-options-menu-button"]', ); - await lockButton.click(); + await driver.delay(regularDelayMs); + await driver.waitForSelector('[data-testid="global-menu-lock"]'); + await driver.clickElement('[data-testid="global-menu-lock"]'); + await driver.waitForSelector('[data-testid="unlock-page"]'); // Recover via SRP in "forget password" option const restoreSeedLink = await driver.findClickableElement( '.unlock-page__link', ); - await restoreSeedLink.click(); - await driver.delay(regularDelayMs); - await driver.pasteIntoField( '[data-testid="import-srp__srp-word-0"]', TEST_SEED_PHRASE, ); - await driver.fill('#password', 'correct horse battery staple'); await driver.fill('#confirm-password', 'correct horse battery staple'); - await driver.clickElement({ - text: enLocaleMessages.restore.message, - tag: 'button', - }); + + await driver.delay(regularDelayMs); + await driver.clickElement( + '[data-testid="create-new-vault-submit-button"]', + ); // Land in 1st account home page await driver.findElement('.home__main-view'); + await waitForAccountRendered(driver); // Check address of 1st account - const restoredFirstAccountPublicAddress = await checkAccountDetails( - driver, + const restoredFirstAccountPublicAddress = + await retrieveShortenAccountAddress(driver); + assert.equal( + restoredFirstAccountPublicAddress, + shortenAddress(firstAccount), ); - assert.equal(restoredFirstAccountPublicAddress, firstAccount); - await driver.delay(regularDelayMs); + // Check address of 2nd account const accountTwoSelector = await findAnotherAccountFromAccountList( driver, @@ -141,10 +157,13 @@ describe('Add account', function () { 'Account 2', ); await driver.clickElement(accountTwoSelector); - const restoredSecondAccountPublicAddress = await checkAccountDetails( - driver, + + const restoredSecondAccountPublicAddress = + await retrieveShortenAccountAddress(driver); + assert.equal( + restoredSecondAccountPublicAddress, + shortenAddress(secondAccount), ); - assert.equal(restoredSecondAccountPublicAddress, secondAccount); }, ); }); @@ -164,64 +183,73 @@ describe('Add account', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.delay(regularDelayMs); + await waitForAccountRendered(driver); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.clickElement('[data-testid="account-menu-icon"]'); + + await driver.clickElement( + '[data-testid="multichain-account-menu-add-account"]', + ); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); + // Wait for 2nd account to be created + await waitForAccountRendered(driver); + const secondAccountCreated = await driver.findElement( + '[data-testid="account-menu-icon"]', + ); + assert.equal(await secondAccountCreated.getText(), '2nd account'); + + await driver.clickElement('[data-testid="account-menu-icon"]'); + + const menuItems = await driver.findElements( + '.multichain-account-list-item', + ); + assert.equal(menuItems.length, 2); + + // User cannot delete 2nd account generated from the SRP imported in onboarding await driver.clickElement( - '[data-testid="account-options-menu-button"]', + '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', + ); + await driver.waitForElementNotPresent( + '[data-testid="account-list-menu-remove"]', ); - const menuItems = await driver.findElements('.menu-item'); - assert.equal(menuItems.length, 3); - - // click out of menu + // Create 3rd account with private key await driver.clickElement('.menu__background'); - - // import with private key - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Import account', tag: 'div' }); - - // enter private key', + await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.fill('#private-key-box', testPrivateKey); - await driver.clickElement({ text: 'Import', tag: 'button' }); - - // should show the correct account name - const importedAccountName = await driver.findElement( - '.selected-account__name', - ); - assert.equal(await importedAccountName.getText(), 'Account 3'); await driver.clickElement( - '[data-testid="account-options-menu-button"]', + '[data-testid="import-account-confirm-button"]', ); - const menuItems2 = await driver.findElements('.menu-item'); - assert.equal(menuItems2.length, 4); - - await driver.findElement( - '[data-testid="account-options-menu__remove-account"]', + // Wait for 3rd account to be created + await waitForAccountRendered(driver); + const thirdAccountCreated = await driver.findElement( + '[data-testid="account-menu-icon"]', ); + assert.equal(await thirdAccountCreated.getText(), 'Account 3'); + + // User can delete 3rd account imported with a private key + await driver.clickElement('[data-testid="account-menu-icon"]'); + const importedMenuItems = await driver.findElements( + '.multichain-account-list-item', + ); + assert.equal(importedMenuItems.length, 3); + await driver.clickElement( + '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', + ); + await driver.findElement('[data-testid="account-list-menu-remove"]'); }, ); }); }); -async function checkAccountDetails(driver) { - await driver.clickElement('[data-testid="account-options-menu-button"]'); - await driver.clickElement( - '[data-testid="account-options-menu__account-details"]', +async function retrieveShortenAccountAddress(driver) { + // get the shorten public address for account + const accountDOM = await driver.waitForSelector( + '.multichain-address-copy-button', ); - - await driver.findVisibleElement('.account-details-modal'); - // get the public address for the "second account" - const accountDOM = await driver.findElement('.qr-code__address'); - const accountAddress = await accountDOM.getText(); - await driver.clickElement('.account-modal__close'); - await driver.waitForElementNotPresent('.account-details-modal '); - - return accountAddress; + return await accountDOM.getText(); } diff --git a/test/e2e/tests/add-custom-network.spec.js b/test/e2e/tests/add-custom-network.spec.js index d87953bda..efea4d1d2 100644 --- a/test/e2e/tests/add-custom-network.spec.js +++ b/test/e2e/tests/add-custom-network.spec.js @@ -1,6 +1,11 @@ const { strict: assert } = require('assert'); const FixtureBuilder = require('../fixture-builder'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openDapp, + regularDelayMs, +} = require('../helpers'); describe('Custom network', function () { const chainID = '42161'; @@ -96,14 +101,14 @@ describe('Custom network', function () { }, ); }); - it("don't add bad rpc custom network", async function () { + + it('don’t add bad rpc custom network', async function () { await withFixtures( { dapp: true, fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() .build(), - ganacheOptions, title: this.test.title, }, async ({ driver }) => { @@ -135,16 +140,23 @@ describe('Custom network', function () { 'MetaMask Notification', windowHandles, ); + + const errMsg1 = 'verify the network details'; + await driver.findElement({ + tag: 'a', + text: errMsg1, + }); + await driver.clickElement({ tag: 'button', text: 'Approve', }); - const errMsg = + const errMsg2 = 'Chain ID returned by the custom network does not match the submitted chain ID.'; await driver.findElement({ tag: 'span', - text: errMsg, + text: errMsg2, }); const approveBtn = await driver.findElement({ @@ -161,7 +173,7 @@ describe('Custom network', function () { ); }); - it("don't add unreachable custom network", async function () { + it('don’t add unreachable custom network', async function () { await withFixtures( { dapp: true, @@ -236,21 +248,23 @@ describe('Custom network', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ tag: 'div', text: 'Settings' }); + // Avoid a stale element error + await driver.delay(regularDelayMs); + + await driver.clickElement('[data-testid="network-display"]'); - await driver.clickElement('.network-display'); await driver.clickElement({ tag: 'button', text: 'Add network' }); - await driver.clickElement({ tag: 'button', text: 'Add', }); + // verify network details const title = await driver.findElement({ tag: 'h6', text: 'Arbitrum One', }); + assert.equal( await title.getText(), 'Arbitrum One', @@ -298,7 +312,7 @@ describe('Custom network', function () { }); // verify network switched const networkDisplayed = await driver.findElement({ - tag: 'span', + tag: 'p', text: 'Arbitrum One', }); assert.equal( @@ -322,10 +336,10 @@ describe('Custom network', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ tag: 'div', text: 'Settings' }); + // Avoid a stale element error + await driver.delay(regularDelayMs); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ tag: 'button', text: 'Add network' }); // had to put all Add elements in list since list is changing and networks are not always in same order @@ -342,11 +356,13 @@ describe('Custom network', function () { }); // verify if added network is in list of networks - const networkDisplay = await driver.findElement('.network-display'); + const networkDisplay = await driver.findElement( + '[data-testid="network-display"]', + ); await networkDisplay.click(); const arbitrumNetwork = await driver.findElements({ - text: `Arbitrum One`, + text: 'Arbitrum One', tag: 'span', }); assert.ok(arbitrumNetwork.length, 1); @@ -354,7 +370,7 @@ describe('Custom network', function () { ); }); - it('Add a custom network and then delete that same network', async function () { + it('Delete the Arbitrum network', async function () { await withFixtures( { fixtures: new FixtureBuilder() @@ -378,30 +394,24 @@ describe('Custom network', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Networks', tag: 'div' }); const arbitrumNetwork = await driver.clickElement({ - text: `Arbitrum One`, + text: 'Arbitrum One', tag: 'div', }); - await driver.clickElement({ - tag: 'button', - text: 'Delete', - }); + // Click first Delete button + await driver.clickElement('button.btn-danger'); - await driver.waitForSelector('.modal-container__footer', { - timeout: 15000, - }); - // should be deleted from the modal shown again to complete deletion custom network - await driver.clickElement({ - tag: 'button', - text: 'Delete', - }); + // Click modal Delete button + await driver.clickElement('button.btn-danger-primary'); - // it checks if custom network is delete + // Checks if Arbitrum is deleted const existNetwork = await driver.isElementPresent(arbitrumNetwork); assert.equal(existNetwork, false, 'Network is not deleted'); }, diff --git a/test/e2e/tests/add-hide-token.spec.js b/test/e2e/tests/add-hide-token.spec.js index 74b66f130..b1ead17a8 100644 --- a/test/e2e/tests/add-hide-token.spec.js +++ b/test/e2e/tests/add-hide-token.spec.js @@ -1,4 +1,5 @@ const { strict: assert } = require('assert'); +const { toHex } = require('@metamask/controller-utils'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -18,7 +19,7 @@ describe('Hide token', function () { fixtures: new FixtureBuilder() .withTokensController({ allTokens: { - '0x539': { + [toHex(1337)]: { '0x5cfe73b6021e818b776b421b1c4db2474086a7e1': [ { address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', @@ -50,16 +51,16 @@ describe('Hide token', function () { await driver.press('#password', driver.Key.ENTER); await driver.waitForSelector({ - css: '.asset-list-item__token-button', + css: '[data-testid="multichain-token-list-item-value"]', text: '0 TST', }); - let assets = await driver.findElements('.asset-list-item'); + let assets = await driver.findElements('.multichain-token-list-item'); assert.equal(assets.length, 2); await driver.clickElement({ text: 'Tokens', tag: 'button' }); - await driver.clickElement({ text: 'TST', tag: 'span' }); + await driver.clickElement({ text: 'TST', tag: 'p' }); await driver.clickElement('[data-testid="asset-options__button"]'); @@ -74,7 +75,7 @@ describe('Hide token', function () { // wait for confirm hide modal to be removed from DOM. await confirmHideModal.waitForElementState('hidden'); - assets = await driver.findElements('.asset-list-item'); + assets = await driver.findElements('.multichain-token-list-item'); assert.equal(assets.length, 1); }, ); @@ -106,7 +107,7 @@ describe('Add existing token using search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement({ text: 'import tokens', tag: 'a' }); + await driver.clickElement({ text: 'Import tokens', tag: 'button' }); await driver.fill('#search-tokens', 'BAT'); await driver.clickElement({ text: 'BAT', @@ -123,3 +124,122 @@ describe('Add existing token using search', function () { ); }); }); + +describe('Add token using wallet_watchAsset', function () { + const ganacheOptions = { + accounts: [ + { + secretKey: + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', + balance: convertToHexValue(25000000000000000000), + }, + ], + }; + + it('opens a notification that adds a token when wallet_watchAsset is executed, then approves', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.openNewPage('http://127.0.0.1:8080/'); + + await driver.executeScript(` + window.ethereum.request({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', + options: { + address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', + symbol: 'TST', + decimals: 4 + }, + } + }) + `); + + const windowHandles = await driver.waitUntilXWindowHandles(3); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Add token', + }); + + await driver.switchToWindowWithTitle('MetaMask', windowHandles); + + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '0 TST', + }); + }, + ); + }); + + it('opens a notification that adds a token when wallet_watchAsset is executed, then rejects', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .build(), + ganacheOptions, + title: this.test.title, + }, + async ({ driver }) => { + await driver.navigate(); + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + await driver.openNewPage('http://127.0.0.1:8080/'); + + await driver.executeScript(` + window.ethereum.request({ + method: 'wallet_watchAsset', + params: { + type: 'ERC20', + options: { + address: '0x86002be4cdd922de1ccb831582bf99284b99ac12', + symbol: 'TST', + decimals: 4 + }, + } + }) + `); + + const windowHandles = await driver.waitUntilXWindowHandles(3); + + await driver.switchToWindowWithTitle( + 'MetaMask Notification', + windowHandles, + ); + + await driver.clickElement({ + tag: 'button', + text: 'Cancel', + }); + + await driver.switchToWindowWithTitle('MetaMask', windowHandles); + + const assetListItems = await driver.findElements( + '.multichain-token-list-item', + ); + + assert.strictEqual(assetListItems.length, 1); + }, + ); + }); +}); diff --git a/test/e2e/tests/address-book.spec.js b/test/e2e/tests/address-book.spec.js index af1214521..d730a4fc9 100644 --- a/test/e2e/tests/address-book.spec.js +++ b/test/e2e/tests/address-book.spec.js @@ -1,5 +1,9 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + logInWithBalanceValidation, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Address Book', function () { @@ -12,6 +16,7 @@ describe('Address Book', function () { }, ], }; + it('Sends to an address book entry', async function () { await withFixtures( { @@ -33,10 +38,9 @@ describe('Address Book', function () { ganacheOptions, title: this.test.title, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="eth-overview-send"]'); const recipientRowTitle = await driver.findElement( @@ -96,23 +100,25 @@ describe('Address Book', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.identicon__address-wrapper'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Contacts', tag: 'div' }); - await driver.clickElement('[data-testid="recipient"]'); + await driver.clickElement({ text: 'Test Name 1', tag: 'p' }); await driver.clickElement({ text: 'Edit', tag: 'button' }); const inputUsername = await driver.findElement('#nickname'); await inputUsername.fill('Test Name Edit'); - const inputAddress = await driver.findElement('#address'); + await inputAddress.fill('0x74cE91B75935D6Bedc27eE002DeFa566c5946f74'); await driver.clickElement('[data-testid="page-container-footer-next"]'); const recipientUsername = await driver.findElement({ text: 'Test Name Edit', - tag: 'div', + tag: 'p', }); assert.equal( await recipientUsername.getText(), @@ -157,11 +163,13 @@ describe('Address Book', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.identicon__address-wrapper'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Contacts', tag: 'div' }); - await driver.clickElement({ text: 'Test Name 1', tag: 'div' }); + await driver.clickElement({ text: 'Test Name 1', tag: 'p' }); await driver.clickElement({ text: 'Edit', tag: 'button' }); await driver.clickElement({ text: 'Delete account', tag: 'a' }); // it checks if account is deleted diff --git a/test/e2e/tests/advanced-settings.spec.js b/test/e2e/tests/advanced-settings.spec.js index 88a4a7f27..58f5d61da 100644 --- a/test/e2e/tests/advanced-settings.spec.js +++ b/test/e2e/tests/advanced-settings.spec.js @@ -26,7 +26,9 @@ describe('Advanced Settings', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Advanced', tag: 'div' }); diff --git a/test/e2e/tests/auto-lock.spec.js b/test/e2e/tests/auto-lock.spec.js index c49680f0d..71a25966b 100644 --- a/test/e2e/tests/auto-lock.spec.js +++ b/test/e2e/tests/auto-lock.spec.js @@ -25,7 +25,9 @@ describe('Auto-Lock Timer', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); // Set Auto Lock Timer - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Advanced', tag: 'div' }); const sixSecsInMins = '0.1'; diff --git a/test/e2e/tests/backup-restore.spec.js b/test/e2e/tests/backup-restore.spec.js index cf90718a5..d52cf1fa6 100644 --- a/test/e2e/tests/backup-restore.spec.js +++ b/test/e2e/tests/backup-restore.spec.js @@ -70,7 +70,9 @@ describe('Backup and Restore', function () { await driver.press('#password', driver.Key.ENTER); // Download user settings - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Advanced', tag: 'div' }); await driver.clickElement({ @@ -107,7 +109,9 @@ describe('Backup and Restore', function () { await driver.press('#password', driver.Key.ENTER); // Restore - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Advanced', tag: 'div' }); const restore = await driver.findElement('#restore-file'); diff --git a/test/e2e/tests/chain-interactions.spec.js b/test/e2e/tests/chain-interactions.spec.js index 6597f67c1..3135ab168 100644 --- a/test/e2e/tests/chain-interactions.spec.js +++ b/test/e2e/tests/chain-interactions.spec.js @@ -55,9 +55,12 @@ describe('Chain Interactions', function () { await driver.switchToWindow(extension); // verify networks - const networkDisplay = await driver.findElement('.network-display'); - await networkDisplay.click(); - assert.equal(await networkDisplay.getText(), 'Localhost 8545'); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: 'Localhost 8545', + }); + + await driver.clickElement('[data-testid="network-display"]'); const ganacheChain = await driver.findElements({ text: `Localhost ${port}`, tag: 'span', @@ -100,10 +103,10 @@ describe('Chain Interactions', function () { await driver.switchToWindow(extension); // verify current network - const networkDisplay = await driver.findElement( - '[data-testid="network-display"]', - ); - assert.equal(await networkDisplay.getText(), `Localhost ${port}`); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: `Localhost ${port}`, + }); }, ); }); diff --git a/test/e2e/tests/clear-activity.spec.js b/test/e2e/tests/clear-activity.spec.js index a16254c9c..c6767271c 100644 --- a/test/e2e/tests/clear-activity.spec.js +++ b/test/e2e/tests/clear-activity.spec.js @@ -44,12 +44,11 @@ describe('Clear account activity', function () { }); // Clear activity and nonce data - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); - await driver.clickElement({ - css: '.tab-bar__tab__content__title', - text: 'Advanced', - }); + await driver.clickElement({ text: 'Advanced', tag: 'div' }); await driver.clickElement({ text: 'Clear activity tab data', tag: 'button', diff --git a/test/e2e/tests/contract-interactions.spec.js b/test/e2e/tests/contract-interactions.spec.js index 780b21c5f..02e95d7bd 100644 --- a/test/e2e/tests/contract-interactions.spec.js +++ b/test/e2e/tests/contract-interactions.spec.js @@ -1,5 +1,9 @@ -const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openDapp, + locateAccountBalanceDOM, +} = require('../helpers'); const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); const FixtureBuilder = require('../fixture-builder'); @@ -89,12 +93,7 @@ describe('Deploy contract and call contract methods', function () { // renders the correct ETH balance await driver.switchToWindow(extension); - const balance = await ganacheServer.getBalance(); - const balanceElement = await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: balance, - }); - assert.equal(`${balance}\nETH`, await balanceElement.getText()); + await locateAccountBalanceDOM(driver, ganacheServer); }, ); }); diff --git a/test/e2e/tests/custom-rpc-history.spec.js b/test/e2e/tests/custom-rpc-history.spec.js index 8696d2f7f..8b43ab712 100644 --- a/test/e2e/tests/custom-rpc-history.spec.js +++ b/test/e2e/tests/custom-rpc-history.spec.js @@ -1,5 +1,9 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + regularDelayMs, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Stores custom RPC history', function () { @@ -31,7 +35,7 @@ describe('Stores custom RPC history', function () { const networkName = 'Secondary Ganache Testnet'; await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Add network', tag: 'button' }); @@ -66,7 +70,7 @@ describe('Stores custom RPC history', function () { '.networks-tab__add-network-form-footer .btn-primary', ); - await driver.findElement({ text: networkName, tag: 'span' }); + await driver.findElement({ text: networkName, tag: 'p' }); }, ); }); @@ -87,7 +91,7 @@ describe('Stores custom RPC history', function () { const duplicateRpcUrl = 'https://mainnet.infura.io/v3/'; await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Add network', tag: 'button' }); @@ -131,7 +135,7 @@ describe('Stores custom RPC history', function () { const duplicateChainId = '1'; await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Add network', tag: 'button' }); @@ -179,7 +183,7 @@ describe('Stores custom RPC history', function () { await driver.press('#password', driver.Key.ENTER); await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'span' }); }, @@ -218,12 +222,16 @@ describe('Stores custom RPC history', function () { await driver.press('#password', driver.Key.ENTER); await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); + + await driver.clickElement('.toggle-button'); + + await driver.delay(regularDelayMs); // only recent 3 are found and in correct order (most recent at the top) const customRpcs = await driver.findElements({ text: 'http://127.0.0.1:8545/', - tag: 'span', + tag: 'div', }); // click Mainnet to dismiss network dropdown @@ -267,7 +275,7 @@ describe('Stores custom RPC history', function () { await driver.press('#password', driver.Key.ENTER); await driver.waitForElementNotPresent('.loading-overlay'); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Add network', tag: 'button' }); diff --git a/test/e2e/tests/custom-token-add-approve.spec.js b/test/e2e/tests/custom-token-add-approve.spec.js index 086651f87..fe78862d4 100644 --- a/test/e2e/tests/custom-token-add-approve.spec.js +++ b/test/e2e/tests/custom-token-add-approve.spec.js @@ -46,7 +46,7 @@ describe('Create token, approve token and approve token without gas', function ( await driver.clickElement(`[data-testid="home__asset-tab"]`); await driver.clickElement({ tag: 'button', text: 'Tokens' }); - await driver.clickElement({ text: 'import tokens', tag: 'a' }); + await driver.clickElement({ text: 'Import tokens', tag: 'button' }); await driver.clickElement({ text: 'Custom token', tag: 'button', @@ -69,11 +69,10 @@ describe('Create token, approve token and approve token without gas', function ( // renders balance for newly created token await driver.clickElement('.app-header__logo-container'); await driver.clickElement({ tag: 'button', text: 'Tokens' }); - const asset = await driver.waitForSelector({ - css: '.asset-list-item__token-value', - text: '10', + await driver.waitForSelector({ + css: '[data-testid="multichain-token-list-item-value"]', + text: '10 TST', }); - assert.equal(await asset.getText(), '10'); }, ); }); diff --git a/test/e2e/tests/dapp-interactions.spec.js b/test/e2e/tests/dapp-interactions.spec.js index bfd0a8e22..dae045ef0 100644 --- a/test/e2e/tests/dapp-interactions.spec.js +++ b/test/e2e/tests/dapp-interactions.spec.js @@ -43,8 +43,10 @@ describe('Dapp interactions', function () { // Lock Account await driver.switchToWindow(extension); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Lock', tag: 'button' }); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement({ text: 'Lock', tag: 'div' }); // Trigger Notification await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); @@ -88,8 +90,10 @@ describe('Dapp interactions', function () { // Lock Account await driver.switchToWindow(extension); - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Lock', tag: 'button' }); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); + await driver.clickElement({ text: 'Lock', tag: 'div' }); // Connect to Dapp1 await openDapp(driver, null, DAPP_ONE_URL); diff --git a/test/e2e/tests/encrypt-decrypt.spec.js b/test/e2e/tests/encrypt-decrypt.spec.js index a311b7545..47e453de9 100644 --- a/test/e2e/tests/encrypt-decrypt.spec.js +++ b/test/e2e/tests/encrypt-decrypt.spec.js @@ -152,7 +152,6 @@ describe('Encrypt Decrypt', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); await openDapp(driver); // ------ Get Encryption key and display ETH ------ diff --git a/test/e2e/tests/ens.spec.js b/test/e2e/tests/ens.spec.js index be48816c9..2f6f67cb4 100644 --- a/test/e2e/tests/ens.spec.js +++ b/test/e2e/tests/ens.spec.js @@ -102,7 +102,7 @@ describe('ENS', function () { await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.pasteIntoField( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', sampleEnsDomain, ); diff --git a/test/e2e/tests/eth-sign.spec.js b/test/e2e/tests/eth-sign.spec.js index b6868a2aa..4cde44f32 100644 --- a/test/e2e/tests/eth-sign.spec.js +++ b/test/e2e/tests/eth-sign.spec.js @@ -1,22 +1,13 @@ const { strict: assert } = require('assert'); const { - convertToHexValue, withFixtures, openDapp, DAPP_URL, + login, + defaultGanacheOptions, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const ganacheOptions = { - accounts: [ - { - secretKey: - '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC', - balance: convertToHexValue(25000000000000000000), - }, - ], -}; - describe('Eth sign', function () { it('will detect if eth_sign is disabled', async function () { await withFixtures( @@ -25,13 +16,12 @@ describe('Eth sign', function () { fixtures: new FixtureBuilder() .withPermissionControllerConnectedToTestDapp() .build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await login(driver); await openDapp(driver); await driver.clickElement('#ethSign'); @@ -61,13 +51,12 @@ describe('Eth sign', function () { }) .withPermissionControllerConnectedToTestDapp() .build(), - ganacheOptions, + ganacheOptions: defaultGanacheOptions, title: this.test.title, }, async ({ driver }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await login(driver); await openDapp(driver); await driver.clickElement('#ethSign'); @@ -80,18 +69,20 @@ describe('Eth sign', function () { windowHandles, ); - const title = await driver.findElement( - '.request-signature__content__title', - ); - const origin = await driver.findElement('.request-signature__origin'); - assert.equal(await title.getText(), 'Signature request'); - assert.equal(await origin.getText(), DAPP_URL); + await driver.findElement({ + css: '.request-signature__content__title', + text: 'Signature request', + }); - const personalMessageRow = await driver.findElement( - '.request-signature__row-value', - ); - const personalMessage = await personalMessageRow.getText(); - assert.equal(personalMessage, expectedPersonalMessage); + await driver.findElement({ + css: '.request-signature__origin', + text: DAPP_URL, + }); + + await driver.findElement({ + css: '.request-signature__row-value', + text: expectedPersonalMessage, + }); await driver.clickElement('[data-testid="page-container-footer-next"]'); await driver.clickElement( @@ -103,8 +94,10 @@ describe('Eth sign', function () { await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); // Verify - const result = await driver.findElement('#ethSignResult'); - assert.equal(await result.getText(), expectedEthSignResult); + await driver.findElement({ + css: '#ethSignResult', + text: expectedEthSignResult, + }); }, ); }); diff --git a/test/e2e/tests/from-import-ui.spec.js b/test/e2e/tests/import-flow.spec.js similarity index 66% rename from test/e2e/tests/from-import-ui.spec.js rename to test/e2e/tests/import-flow.spec.js index e0966999d..19f02219a 100644 --- a/test/e2e/tests/from-import-ui.spec.js +++ b/test/e2e/tests/import-flow.spec.js @@ -22,10 +22,9 @@ const ganacheOptions = { ], }; -describe('MetaMask Import UI', function () { - it('Importing wallet using Secret Recovery Phrase', async function () { +describe('Import flow', function () { + it('Import wallet using Secret Recovery Phrase', async function () { const testPassword = 'correct horse battery staple'; - const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; await withFixtures( { @@ -44,29 +43,31 @@ describe('MetaMask Import UI', function () { ); // Show account information + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '[data-testid="account-list-item-menu-button"]', + ); + await driver.clickElement('[data-testid="account-list-menu-details"'); + await driver.findVisibleElement('.qr-code__wrapper'); + + // shows a QR code for the account + await driver.findVisibleElement('.popover-container'); + // shows the correct account address + const address = await driver.findElement( + '.multichain-address-copy-button', + ); + assert.equal(await address.getText(), '0x0Cc...afD3'); + + await driver.clickElement('[data-testid="popover-close"]'); + + // logs out of the account await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - await driver.clickElement( - '[data-testid="account-options-menu__account-details"]', - ); - await driver.findVisibleElement('.qr-code__wrapper'); - // shows a QR code for the account - const detailsModal = await driver.findVisibleElement('span .modal'); - // shows the correct account address - const address = await driver.findElement('.qr-code__address'); - - assert.equal(await address.getText(), testAddress); - - await driver.clickElement('.account-modal__close'); - await detailsModal.waitForElementState('hidden'); - - // logs out of the account - await driver.clickElement('.account-menu__icon .identicon'); const lockButton = await driver.findClickableElement( - '.account-menu__lock-button', + '[data-testid="global-menu-lock"]', ); - assert.equal(await lockButton.getText(), 'Lock'); + assert.equal(await lockButton.getText(), 'Lock MetaMask'); await lockButton.click(); // accepts the account password after lock @@ -76,32 +77,39 @@ describe('MetaMask Import UI', function () { // Create a new account // switches to localhost await driver.delay(largeDelayMs); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); + await driver.clickElement('.toggle-button'); await driver.clickElement({ text: 'Localhost', tag: 'span' }); // choose Create account from the account menu - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Create account', tag: 'div' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement({ text: 'Add account', tag: 'button' }); // set account name - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.delay(regularDelayMs); await driver.clickElement({ text: 'Create', tag: 'button' }); // should show the correct account name - const accountName = await driver.findElement('.selected-account__name'); - assert.equal(await accountName.getText(), '2nd account'); + const accountName = await driver.isElementPresent({ + tag: 'span', + text: '2nd account', + }); + + assert.equal(accountName, true, 'Account name is not correct'); // Switch back to original account // chooses the original account from the account menu - await driver.clickElement('.account-menu__icon'); - await driver.clickElement('.account-menu__name'); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement( + '.multichain-account-list-item__account-name__button', + ); // Send ETH from inside MetaMask // starts a send transaction await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); await driver.fill('.unit-input__input', '1'); @@ -130,7 +138,7 @@ describe('MetaMask Import UI', function () { ); }); - it('Importing wallet using Secret Recovery Phrase with pasting word by word', async function () { + it('Import wallet using Secret Recovery Phrase with pasting word by word', async function () { const testPassword = 'correct horse battery staple'; const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'; @@ -151,22 +159,23 @@ describe('MetaMask Import UI', function () { ); // Show account information + await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement( - '[data-testid="account-options-menu__account-details"]', + '[data-testid="account-list-item-menu-button"]', ); + await driver.clickElement('[data-testid="account-list-menu-details"'); await driver.findVisibleElement('.qr-code__wrapper'); // shows the correct account address - const address = await driver.findElement('.qr-code__address'); + const address = await driver.findElement( + '.qr-code [data-testid="address-copy-button-text"]', + ); assert.equal(await address.getText(), testAddress); }, ); }); - it('Import Account using private key', async function () { + it('Import Account using private key and remove imported account', async function () { const testPrivateKey1 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6'; const testPrivateKey2 = @@ -186,77 +195,65 @@ describe('MetaMask Import UI', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - // Imports an account with private key - // choose Create account from the account menu - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Import account', tag: 'div' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement({ text: 'Import account', tag: 'button' }); - // enter private key', + // Imports Account 4 with private key + await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey1); - await driver.clickElement({ text: 'Import', tag: 'button' }); - - // should show the correct account name - const importedAccountName = await driver.findElement( - '.selected-account__name', + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', ); - assert.equal(await importedAccountName.getText(), 'Account 4'); - // should show the imported label - // confirm 4th account is account 4, as expected + // New imported account has correct name and label + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', + text: 'Account 4', + }); + const accountMenuItemSelector = await findAnotherAccountFromAccountList( driver, 4, 'Account 4', ); - // confirm label is present on the same menu item - const importedLabel = await driver.findElement( - `${accountMenuItemSelector} .keyring-label`, - ); - assert.equal(await importedLabel.getText(), 'IMPORTED'); + await driver.findElement({ + css: `${accountMenuItemSelector} .mm-tag`, + text: 'Imported', + }); - // Imports and removes an account - // choose Create account from the account menu - await driver.clickElement({ text: 'Import account', tag: 'div' }); - // enter private key + // Imports Account 5 with private key + await driver.clickElement({ text: 'Import account', tag: 'button' }); + await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey2); - await driver.clickElement({ text: 'Import', tag: 'button' }); - - // should see new account in account menu - const importedAccount2Name = await driver.findElement( - '.selected-account__name', + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', ); - assert.equal(await importedAccount2Name.getText(), 'Account 5'); - await driver.clickElement('.account-menu__icon'); + + // New imported account has correct name and label + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', + text: 'Account 5', + }); + await driver.clickElement('[data-testid="account-menu-icon"]'); const accountListItems = await driver.findElements( - '.account-menu__account', + '.multichain-account-list-item', ); assert.equal(accountListItems.length, 5); - await driver.clickPoint('.account-menu__icon', 0, 0); - - // should open the remove account modal await driver.clickElement( - '[data-testid="account-options-menu-button"]', + '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', ); - await driver.clickElement( - '[data-testid="account-options-menu__remove-account"]', - ); - await driver.findElement('.confirm-remove-account__account'); - // should remove the account + // Account 5 can be removed + await driver.clickElement('[data-testid="account-list-menu-remove"]'); await driver.clickElement({ text: 'Remove', tag: 'button' }); - - // Wait until selected account switches away from removed account to first account - await driver.waitForSelector({ - css: '.selected-account__name', + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', text: 'Account 1', }); - - await driver.delay(regularDelayMs); - await driver.clickElement('.account-menu__icon'); - + await driver.clickElement('[data-testid="account-menu-icon"]'); const accountListItemsAfterRemoval = await driver.findElements( - '.account-menu__account', + '.multichain-account-list-item', ); assert.equal(accountListItemsAfterRemoval.length, 4); }, @@ -279,8 +276,8 @@ describe('MetaMask Import UI', function () { await driver.press('#password', driver.Key.ENTER); // Imports an account with JSON file - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Import account', tag: 'div' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.clickElement('.dropdown__select'); await driver.clickElement({ text: 'JSON File', tag: 'option' }); @@ -296,31 +293,28 @@ describe('MetaMask Import UI', function () { fileInput.sendKeys(importJsonFile); await driver.fill('#json-password-box', 'foobarbazqux'); - - await driver.clickElement({ text: 'Import', tag: 'button' }); - - // should show the correct account name - const importedAccountName = await driver.findElement( - '.selected-account__name', + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', ); - assert.equal(await importedAccountName.getText(), 'Account 4'); - // should show the imported label - // confirm 4th account is account 4, as expected + // New imported account has correct name and label + await driver.findElement({ + css: '[data-testid="account-menu-icon"]', + text: 'Account 4', + }); + const accountMenuItemSelector = await findAnotherAccountFromAccountList( driver, 4, 'Account 4', ); - - // confirm label is present on the same menu item - const importedLabel = await driver.findElement( - `${accountMenuItemSelector} .keyring-label`, - ); - assert.equal(await importedLabel.getText(), 'IMPORTED'); + await driver.findElement({ + css: `${accountMenuItemSelector} .mm-tag`, + text: 'Imported', + }); const accountListItems = await driver.findElements( - '.account-menu__account', + '.multichain-account-list-item', ); assert.equal(accountListItems.length, 4); }, @@ -345,12 +339,15 @@ describe('MetaMask Import UI', function () { await driver.press('#password', driver.Key.ENTER); // choose Import Account from the account menu - await driver.clickElement('.account-menu__icon'); - await driver.clickElement({ text: 'Import account', tag: 'div' }); + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement({ text: 'Import account', tag: 'button' }); - // enter private key', + // enter private key + await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey); - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // error should occur await driver.waitForSelector({ @@ -375,10 +372,10 @@ describe('MetaMask Import UI', function () { await driver.press('#password', driver.Key.ENTER); // choose Connect hardware wallet from the account menu - await driver.clickElement('.account-menu__icon'); + await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement({ - text: 'Connect hardware wallet', - tag: 'div', + text: 'Hardware wallet', + tag: 'button', }); await driver.delay(regularDelayMs); diff --git a/test/e2e/tests/incremental-security.spec.js b/test/e2e/tests/incremental-security.spec.js index 6c243bd79..29535738c 100644 --- a/test/e2e/tests/incremental-security.spec.js +++ b/test/e2e/tests/incremental-security.spec.js @@ -65,21 +65,21 @@ describe('Incremental Security', function () { await driver.clickElement('[data-testid="pin-extension-done"]'); // open account menu + await driver.clickElement('[data-testid="account-menu-icon"]'); await driver.clickElement( - '[data-testid="account-options-menu-button"]', - ); - await driver.clickElement( - '[data-testid="account-options-menu__account-details"]', + '.multichain-account-list-item--selected [data-testid="account-list-item-menu-button"]', ); + await driver.clickElement('[data-testid="account-list-menu-details"'); // gets the current accounts address - const address = await driver.findElement('.qr-code__address'); + const address = await driver.findElement( + '.qr-code .multichain-address-copy-button', + ); const publicAddress = await address.getText(); // wait for account modal to be visible - const accountModal = await driver.findVisibleElement('span .modal'); - - await driver.clickElement('.account-modal__close'); + const accountModal = await driver.findVisibleElement('.popover-bg'); + await driver.clickElement('[data-testid="popover-close"]'); // wait for account modal to be removed from DOM await accountModal.waitForElementState('hidden'); diff --git a/test/e2e/tests/lock-account.spec.js b/test/e2e/tests/lock-account.spec.js index 1fd2910a6..5b03fd724 100644 --- a/test/e2e/tests/lock-account.spec.js +++ b/test/e2e/tests/lock-account.spec.js @@ -24,17 +24,19 @@ describe('Lock and unlock', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); - const lockButton = await driver.findClickableElement( - '.account-menu__lock-button', + await driver.clickElement( + '[data-testid="account-options-menu-button"]', ); - assert.equal(await lockButton.getText(), 'Lock'); + const lockButton = await driver.findClickableElement( + '[data-testid="global-menu-lock"]', + ); + assert.equal(await lockButton.getText(), 'Lock MetaMask'); await lockButton.click(); await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); const walletBalance = await driver.findElement( - '[data-testid="wallet-balance"] .list-item__heading', + '.eth-overview__primary-balance', ); assert.equal(/^25\s*ETH$/u.test(await walletBalance.getText()), true); }, diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index fe2ae4c1a..a930a3235 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -3,6 +3,7 @@ const { TEST_SEED_PHRASE_TWO, convertToHexValue, withFixtures, + assertAccountBalanceForDOM, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -71,7 +72,7 @@ describe('MetaMask Responsive UI', function () { // assert balance const balance = await driver.findElement( - '[data-testid="wallet-balance"]', + '[data-testid="eth-overview__primary-currency"]', ); assert.ok(/^0\sETH$/u.test(await balance.getText())); }, @@ -108,11 +109,7 @@ describe('MetaMask Responsive UI', function () { await driver.press('#confirm-password', driver.Key.ENTER); // balance renders - const balance = await ganacheServer.getBalance(); - await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balance} ETH`, - }); + await assertAccountBalanceForDOM(driver, ganacheServer); }, ); }); @@ -145,7 +142,7 @@ describe('MetaMask Responsive UI', function () { await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); diff --git a/test/e2e/tests/navigate-transactions.spec.js b/test/e2e/tests/navigate-transactions.spec.js index 438ef5028..507071f4a 100644 --- a/test/e2e/tests/navigate-transactions.spec.js +++ b/test/e2e/tests/navigate-transactions.spec.js @@ -1,5 +1,10 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openDapp, + locateAccountBalanceDOM, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Navigate transactions', function () { @@ -227,11 +232,7 @@ describe('Navigate transactions', function () { // reject transactions await driver.clickElement({ text: 'Reject 4', tag: 'a' }); await driver.clickElement({ text: 'Reject all', tag: 'button' }); - const balance = await ganacheServer.getBalance(); - const balanceElement = await driver.findElement( - '[data-testid="eth-overview__primary-currency"]', - ); - assert.equal(`${balance}\nETH`, await balanceElement.getText()); + await locateAccountBalanceDOM(driver, ganacheServer); }, ); }); diff --git a/test/e2e/tests/network-error.spec.js b/test/e2e/tests/network-error.spec.js index 778d32b35..959f1ea29 100644 --- a/test/e2e/tests/network-error.spec.js +++ b/test/e2e/tests/network-error.spec.js @@ -1,12 +1,16 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + logInWithBalanceValidation, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Gas API fallback', function () { async function mockGasApiDown(mockServer) { await mockServer .forGet( - 'https://gas-api.metaswap.codefi.network/networks/1/suggestedGasFees', + 'https://gas-api.metaswap.codefi.network/networks/1337/suggestedGasFees', ) .always() .thenCallback(() => { @@ -62,15 +66,13 @@ describe('Gas API fallback', function () { ganacheOptions, title: this.test.title, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); @@ -79,6 +81,8 @@ describe('Gas API fallback', function () { await driver.clickElement({ text: 'Next', tag: 'button' }); + await driver.findElement('.transaction-alerts'); + const error = await driver.isElementPresent({ text: 'Network is busy. Gas prices are high and estimates are less accurate.', }); diff --git a/test/e2e/tests/onboarding.spec.js b/test/e2e/tests/onboarding.spec.js index a585d4896..d0f494fd7 100644 --- a/test/e2e/tests/onboarding.spec.js +++ b/test/e2e/tests/onboarding.spec.js @@ -9,6 +9,7 @@ const { importSRPOnboardingFlow, importWrongSRPOnboardingFlow, testSRPDropdownIterations, + assertAccountBalanceForDOM, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -270,8 +271,9 @@ describe('MetaMask onboarding', function () { // Add custome network localhost 8546 during onboarding await driver.clickElement({ text: 'Advanced configuration', tag: 'a' }); + await driver.clickElement('.mm-picker-network'); await driver.clickElement({ - text: 'Add custom network', + text: 'Add network', tag: 'button', }); @@ -299,16 +301,12 @@ describe('MetaMask onboarding', function () { assert.equal(networkNotification, true); // Check localhost 8546 is selected and its balance value is correct - const networkDisplay = await driver.findElement( - '[data-testid="network-display"]', - ); - assert.equal(await networkDisplay.getText(), networkName); + await driver.findElement({ + css: '[data-testid="network-display"]', + text: networkName, + }); - const balance = await secondaryGanacheServer.getBalance(); - const balanceElement = await driver.findElement( - '[data-testid="eth-overview__primary-currency"]', - ); - assert.equal(`${balance}\nETH`, await balanceElement.getText()); + await assertAccountBalanceForDOM(driver, secondaryGanacheServer); }, ); }); diff --git a/test/e2e/tests/permissions.spec.js b/test/e2e/tests/permissions.spec.js index 5ea819b64..648e0552e 100644 --- a/test/e2e/tests/permissions.spec.js +++ b/test/e2e/tests/permissions.spec.js @@ -55,9 +55,7 @@ describe('Permissions', function () { await driver.clickElement( '[data-testid="account-options-menu-button"]', ); - await driver.clickElement( - '[data-testid="account-options-menu__connected-sites"]', - ); + await driver.clickElement('.menu-item'); await driver.findElement({ text: 'Connected sites', diff --git a/test/e2e/tests/provider-api.spec.js b/test/e2e/tests/provider-api.spec.js index 2321e0157..b8c0e0edc 100644 --- a/test/e2e/tests/provider-api.spec.js +++ b/test/e2e/tests/provider-api.spec.js @@ -46,7 +46,7 @@ describe('MetaMask', function () { const windowHandles = await driver.getAllWindowHandles(); await driver.switchToWindow(windowHandles[0]); - await driver.clickElement('.network-display'); + await driver.clickElement('[data-testid="network-display"]'); await driver.clickElement({ text: 'Ethereum Mainnet', tag: 'span' }); await driver.switchToWindowWithTitle('E2E Test Dapp', windowHandles); diff --git a/test/e2e/tests/send-eth.spec.js b/test/e2e/tests/send-eth.spec.js index dd2ae8650..81a330085 100644 --- a/test/e2e/tests/send-eth.spec.js +++ b/test/e2e/tests/send-eth.spec.js @@ -1,6 +1,12 @@ const { strict: assert } = require('assert'); const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); -const { convertToHexValue, withFixtures, openDapp } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + openDapp, + assertAccountBalanceForDOM, + logInWithBalanceValidation, +} = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); describe('Send ETH from inside MetaMask using default gas', function () { @@ -20,15 +26,14 @@ describe('Send ETH from inside MetaMask using default gas', function () { ganacheOptions, title: this.test.title, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); @@ -111,15 +116,14 @@ describe('Send ETH non-contract address with data that matches ERC20 transfer da ganacheOptions, title: this.test.title, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0xc427D562164062a23a5cFf596A4a3208e72Acd28', ); @@ -128,9 +132,13 @@ describe('Send ETH non-contract address with data that matches ERC20 transfer da '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', ); + await driver.findClickableElement({ text: 'Next', tag: 'button' }); await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.clickElement({ text: '0xc42...cd28' }); + await driver.findClickableElement( + '[data-testid="sender-to-recipient__name"]', + ); + await driver.clickElement('[data-testid="sender-to-recipient__name"]'); const recipientAddress = await driver.findElements({ text: '0xc427D562164062a23a5cFf596A4a3208e72Acd28', @@ -168,7 +176,7 @@ describe('Send ETH from inside MetaMask using advanced gas modal', function () { await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); @@ -343,11 +351,16 @@ describe('Send ETH from dapp using advanced gas controls', function () { text: '0.04503836 ETH', }); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.waitUntilXWindowHandles(2); await driver.switchToWindow(extension); - // finds the transaction in the transactions list + // Identify the transaction in the transactions list + await driver.waitForSelector( + '[data-testid="eth-overview__primary-currency"]', + ); + await driver.clickElement('[data-testid="home__activity-tab"]'); await driver.waitForSelector( '.transaction-list__completed-transactions .transaction-list-item:nth-of-type(1)', @@ -385,6 +398,7 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { }, ], }; + it('finds the transaction in the transactions list', async function () { await withFixtures( { @@ -393,7 +407,7 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { smartContract, title: this.test.title, }, - async ({ driver, contractRegistry }) => { + async ({ driver, contractRegistry, ganacheServer }) => { const contractAddress = await contractRegistry.getContractAddress( smartContract, ); @@ -404,7 +418,7 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', contractAddress, ); @@ -412,22 +426,24 @@ describe('Send ETH from inside MetaMask to a Multisig Address', function () { await inputAmount.fill('1'); // Continue to next screen + await driver.findClickableElement({ text: 'Next', tag: 'button' }); await driver.clickElement({ text: 'Next', tag: 'button' }); + await driver.findClickableElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); + // Go back to home screen to check txn + await assertAccountBalanceForDOM(driver, ganacheServer); await driver.clickElement('[data-testid="home__activity-tab"]'); - await driver.wait(async () => { - const confirmedTxes = await driver.findElements( - '.transaction-list__completed-transactions .transaction-list-item', - ); - return confirmedTxes.length === 1; - }, 10000); + const txn = await driver.isElementPresent( + '.transaction-list__completed-transactions .transaction-list-item', + ); - const failedTx = await driver.isElementPresent( + assert.equal(txn, true); + + await driver.assertElementNotPresent( '.transaction-status-label--failed', ); - assert.equal(failedTx, false, 'Transaction failed'); }, ); }); diff --git a/test/e2e/tests/send-hex-address.spec.js b/test/e2e/tests/send-hex-address.spec.js index c971f4f01..f4f3c22b5 100644 --- a/test/e2e/tests/send-hex-address.spec.js +++ b/test/e2e/tests/send-hex-address.spec.js @@ -1,5 +1,9 @@ const { strict: assert } = require('assert'); -const { convertToHexValue, withFixtures } = require('../helpers'); +const { + convertToHexValue, + withFixtures, + logInWithBalanceValidation, +} = require('../helpers'); const { SMART_CONTRACTS } = require('../seeder/smart-contracts'); const FixtureBuilder = require('../fixture-builder'); @@ -24,17 +28,16 @@ describe('Send ETH to a 40 character hexadecimal address', function () { title: this.test.title, failOnConsoleError: false, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); // Send ETH await driver.clickElement('[data-testid="eth-overview-send"]'); // Paste address without hex prefix await driver.pasteIntoField( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); await driver.waitForSelector({ @@ -79,7 +82,7 @@ describe('Send ETH to a 40 character hexadecimal address', function () { // Type address without hex prefix await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); await driver.waitForSelector({ @@ -131,22 +134,18 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }, async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - const balanceAfterDeployment = await ganacheServer.getBalance(); - await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balanceAfterDeployment} ETH`, - }); + await logInWithBalanceValidation(driver, ganacheServer); // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); - await driver.clickElement('.token-cell'); + await driver.clickElement( + '[data-testid="multichain-token-list-button"]', + ); await driver.clickElement('[data-testid="eth-overview-send"]'); // Paste address without hex prefix await driver.pasteIntoField( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); await driver.waitForSelector({ @@ -155,14 +154,14 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }); await driver.waitForSelector({ css: '.transaction-detail-item', - text: '0.00008455 ETH', + text: '0.000042 ETH', }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction await driver.waitForSelector({ css: '.confirm-page-container-summary__title', - text: '0 TST', + text: '0', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); @@ -196,22 +195,18 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }, async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); - const balanceAfterDeployment = await ganacheServer.getBalance(); - await driver.waitForSelector({ - css: '[data-testid="eth-overview__primary-currency"]', - text: `${balanceAfterDeployment} ETH`, - }); + await logInWithBalanceValidation(driver, ganacheServer); // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); - await driver.clickElement('.token-cell'); + await driver.clickElement( + '[data-testid="multichain-token-list-button"]', + ); await driver.clickElement('[data-testid="eth-overview-send"]'); // Type address without hex prefix await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', nonHexPrefixedAddress, ); await driver.waitForSelector({ @@ -220,14 +215,14 @@ describe('Send ERC20 to a 40 character hexadecimal address', function () { }); await driver.waitForSelector({ css: '.transaction-detail-item', - text: '0.00008455 ETH', + text: '0.000042 ETH', }); await driver.clickElement({ text: 'Next', tag: 'button' }); // Confirm transaction await driver.waitForSelector({ css: '.confirm-page-container-summary__title', - text: '0 TST', + text: '0', }); await driver.clickElement({ text: 'Confirm', tag: 'button' }); await driver.clickElement('[data-testid="home__activity-tab"]'); diff --git a/test/e2e/tests/send-to-contract.spec.js b/test/e2e/tests/send-to-contract.spec.js index 62a6a58e4..9b9ae3dd7 100644 --- a/test/e2e/tests/send-to-contract.spec.js +++ b/test/e2e/tests/send-to-contract.spec.js @@ -34,12 +34,14 @@ describe('Send ERC20 token to contract address', function () { // Send TST await driver.clickElement('[data-testid="home__asset-tab"]'); - await driver.clickElement('.token-cell'); + await driver.clickElement( + '[data-testid="multichain-token-list-button"]', + ); await driver.clickElement('[data-testid="eth-overview-send"]'); // Type contract address await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', contractAddress, ); diff --git a/test/e2e/tests/settings-general.spec.js b/test/e2e/tests/settings-general.spec.js index 4279f6908..ad937acd0 100644 --- a/test/e2e/tests/settings-general.spec.js +++ b/test/e2e/tests/settings-general.spec.js @@ -26,7 +26,9 @@ describe('Settings', function () { await driver.press('#password', driver.Key.ENTER); // goes to the settings screen - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); // finds the jazzicon toggle turned on diff --git a/test/e2e/tests/settings-search.spec.js b/test/e2e/tests/settings-search.spec.js index c4af0ccc2..343b16ccf 100644 --- a/test/e2e/tests/settings-search.spec.js +++ b/test/e2e/tests/settings-search.spec.js @@ -35,7 +35,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.general); @@ -61,7 +63,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.advanced); @@ -88,7 +92,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.contacts); @@ -115,7 +121,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.security); @@ -142,7 +150,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.alerts); @@ -169,7 +179,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.networks); @@ -196,7 +208,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.experimental); @@ -223,7 +237,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', settingsSearch.about); @@ -250,7 +266,9 @@ describe('Settings Search', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.fill('#search-settings', 'Lorem ipsum'); diff --git a/test/e2e/tests/simple-send.spec.js b/test/e2e/tests/simple-send.spec.js index b5b083ed8..e8c582a2c 100644 --- a/test/e2e/tests/simple-send.spec.js +++ b/test/e2e/tests/simple-send.spec.js @@ -2,6 +2,7 @@ const { convertToHexValue, withFixtures, sendTransaction, + logInWithBalanceValidation, } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); @@ -22,10 +23,10 @@ describe('Simple send', function () { ganacheOptions, title: this.test.title, }, - async ({ driver }) => { + async ({ driver, ganacheServer }) => { await driver.navigate(); - await driver.fill('#password', 'correct horse battery staple'); - await driver.press('#password', driver.Key.ENTER); + await logInWithBalanceValidation(driver, ganacheServer); + await sendTransaction( driver, '0x985c30949c92df7a0bd42e0f3e3d539ece98db24', diff --git a/test/e2e/tests/state-logs.spec.js b/test/e2e/tests/state-logs.spec.js index 2f33af584..f08a38fd1 100644 --- a/test/e2e/tests/state-logs.spec.js +++ b/test/e2e/tests/state-logs.spec.js @@ -45,7 +45,9 @@ describe('State logs', function () { await driver.press('#password', driver.Key.ENTER); // Download state logs - await driver.clickElement('.account-menu__icon'); + await driver.clickElement( + '[data-testid="account-options-menu-button"]', + ); await driver.clickElement({ text: 'Settings', tag: 'div' }); await driver.clickElement({ text: 'Advanced', tag: 'div' }); await driver.clickElement({ diff --git a/test/e2e/tests/switch-custom-network.spec.js b/test/e2e/tests/switch-custom-network.spec.js index a670f913f..9e0cf6857 100644 --- a/test/e2e/tests/switch-custom-network.spec.js +++ b/test/e2e/tests/switch-custom-network.spec.js @@ -88,7 +88,7 @@ describe('Swtich ethereum chain', function () { await driver.switchToWindow(extension); const currentNetworkName = await driver.findElement({ - tag: 'span', + tag: 'p', text: 'Localhost 8546', }); diff --git a/test/e2e/tests/token-details.spec.js b/test/e2e/tests/token-details.spec.js index 6f5ed33c0..07c4a8570 100644 --- a/test/e2e/tests/token-details.spec.js +++ b/test/e2e/tests/token-details.spec.js @@ -24,7 +24,7 @@ describe('Token Details', function () { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement({ text: 'import tokens', tag: 'a' }); + await driver.clickElement({ text: 'Import tokens', tag: 'button' }); await driver.clickElement({ text: 'Custom token', tag: 'button' }); const tokenAddress = '0x2EFA2Cb29C2341d8E5Ba7D3262C9e9d6f1Bf3711'; diff --git a/test/e2e/user-actions-benchmark.js b/test/e2e/user-actions-benchmark.js index 7b72c73f5..00a53c8d0 100644 --- a/test/e2e/user-actions-benchmark.js +++ b/test/e2e/user-actions-benchmark.js @@ -33,10 +33,12 @@ async function loadNewAccount() { await driver.fill('#password', 'correct horse battery staple'); await driver.press('#password', driver.Key.ENTER); - await driver.clickElement('.account-menu__icon'); + await driver.clickElement('[data-testid="account-menu-icon"]'); const timestampBeforeAction = new Date(); - await driver.clickElement({ text: 'Create account', tag: 'div' }); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.clickElement( + '[data-testid="multichain-account-menu-add-account"]', + ); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); await driver.waitForSelector({ css: '.currency-display-component__text', @@ -64,7 +66,7 @@ async function confirmTx() { await driver.clickElement('[data-testid="eth-overview-send"]'); await driver.fill( - 'input[placeholder="Search, public address (0x), or ENS"]', + 'input[placeholder="Enter public address (0x) or ENS name"]', '0x2f318C334780961FB129D2a6c30D0763d9a5C970', ); diff --git a/test/helpers/setup-helper.js b/test/helpers/setup-helper.js index 0767a0056..a17a41aac 100644 --- a/test/helpers/setup-helper.js +++ b/test/helpers/setup-helper.js @@ -73,14 +73,15 @@ const popoverContent = window.document.createElement('div'); popoverContent.setAttribute('id', 'popover-content'); window.document.body.appendChild(popoverContent); -// fetch +// Fetch // fetch is part of node js in future versions, thus triggering no-shadow // eslint-disable-next-line no-shadow -const fetch = require('node-fetch'); +const { default: fetch, Headers, Request, Response } = require('node-fetch'); -/* eslint-disable-next-line no-shadow */ -const { Headers, Request, Response } = fetch; Object.assign(window, { fetch, Headers, Request, Response }); +// some of our libraries currently assume that `fetch` is globally available, +// so we need to assign this for tests to run +global.fetch = fetch; // localStorage window.localStorage = { diff --git a/test/jest/mock-store.js b/test/jest/mock-store.js index 80934403b..ef05b7ba3 100644 --- a/test/jest/mock-store.js +++ b/test/jest/mock-store.js @@ -252,6 +252,7 @@ export const createSwapsMockStore = () => { }, }, selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + currentLocale: 'en', keyringTypes: [KeyringType.imported, KeyringType.hdKeyTree], keyrings: [ { @@ -288,6 +289,10 @@ export const createSwapsMockStore = () => { mobileActive: true, extensionActive: true, }, + swapRedesign: { + mobileActive: true, + extensionActive: true, + }, }, quotes: { TEST_AGG_1: { diff --git a/test/jest/setup.js b/test/jest/setup.js index d1ba51e61..a3c5d2568 100644 --- a/test/jest/setup.js +++ b/test/jest/setup.js @@ -40,6 +40,45 @@ beforeEach(() => { }); expect.extend({ + /** + * Tests that the given promise is fulfilled within a certain amount of time + * (which is the default time that Jest tests wait before timing out as + * configured in the Jest configuration file). + * + * Inspired by . + * + * @param {Promise} promise - The promise to test. + * @returns The result of the matcher. + */ + async toBeFulfilled(promise) { + if (this.isNot) { + throw new Error( + "Using `.not.toBeFulfilled(...)` is not supported. Use `.rejects` to test the promise's rejection value instead.", + ); + } + + let rejectionValue = UNRESOLVED; + try { + await promise; + } catch (e) { + rejectionValue = e; + } + + if (rejectionValue !== UNRESOLVED) { + return { + message: () => + `Expected promise to be fulfilled, but it was rejected with ${rejectionValue}.`, + pass: false, + }; + } + + return { + message: () => + 'This message should not be displayed as it is for the negative case, which will never happen.', + pass: true, + }; + }, + /** * Tests that the given promise is never fulfilled or rejected past a certain * amount of time (which is the default time that Jest tests wait before diff --git a/test/merge-coverage.js b/test/merge-coverage.js index 9cf8cad20..61394f1c1 100644 --- a/test/merge-coverage.js +++ b/test/merge-coverage.js @@ -6,10 +6,29 @@ const reports = require('istanbul-reports'); const glob = require('fast-glob'); const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); -const yaml = require('js-yaml'); +// Temporarily commented out as we can't rely on the commented yaml file +// Can be restored when the codecov checks are restored +// const yaml = require('js-yaml'); const codecovTargets = require('../coverage-targets'); -const codecovConfig = yaml.load(fs.readFileSync('codecov.yml', 'utf8')); +// Temporarily commented out as we can't rely on the commented yaml file +// Can be restored when the codecov checks are restored. In the meantime +// the important parts of the yaml file are copied below in normal js object +// format. +// const codecovConfig = yaml.load(fs.readFileSync('codecov.yml', 'utf8')); + +const codecovConfig = { + coverage: { + status: { + global: {}, + project: { + transforms: { + paths: ['development/build/transforms/**/*.js'], + }, + }, + }, + }, +}; const COVERAGE_DIR = './coverage/'; diff --git a/tsconfig.json b/tsconfig.json index 560d2fcb3..0418262ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,13 @@ "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", + "esModuleInterop": true, "inlineSources": true, "isolatedModules": true, "jsx": "react", "lib": ["dom", "es2020"], "moduleResolution": "node", + "noEmit": true, "noEmitOnError": true, "outDir": "tsout", "rootDir": ".", @@ -15,22 +17,22 @@ "strict": true }, "exclude": [ - "**/*.test.js", - "**/*.test.ts", - "**/*.test.tsx", "**/jest-coverage/**/*", "**/__mocks__/**/*", "**/storybook-build/**/*", "**/*.stories.*", ".storybook/**/*", + "app/scripts/controllers/*.test.ts", + "app/scripts/lib/**/*.test.ts", "builds/**/*", "dist/**/*", "node_modules/**", "development/ts-migration-dashboard/build/**/*", - "development/ts-migration-dashboard/intermediate/**/*" + "development/ts-migration-dashboard/intermediate/**/*", + "ui/**/*.test.js", + "ui/**/*.test.ts", + "ui/**/*.test.tsx" ], "extends": "@tsconfig/node16/tsconfig.json", - "paths": { - "*": ["./types/*"] - } + "include": ["app", "development", "shared", "types", "ui"] } diff --git a/types/global.d.ts b/types/global.d.ts index 042d05558..f1e8c1b18 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -7,6 +7,14 @@ declare class Platform { closeCurrentWindow: () => void; } + export declare global { var platform: Platform; + + namespace jest { + interface Matchers { + toBeFulfilled(): Promise; + toNeverResolve(): Promise; + } + } } diff --git a/ui/components/app/account-menu/__snapshots__/account-menu.test.js.snap b/ui/components/app/account-menu/__snapshots__/account-menu.test.js.snap deleted file mode 100644 index f7d981ce4..000000000 --- a/ui/components/app/account-menu/__snapshots__/account-menu.test.js.snap +++ /dev/null @@ -1,400 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Account Menu Render Content should not render keyring label if keyring tyoe is Custody - JSONRPC 1`] = ` -
-