mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal
This commit is contained in:
commit
4e7ee233e6
23
app/_locales/en/messages.json
generated
23
app/_locales/en/messages.json
generated
@ -951,6 +951,9 @@
|
||||
"custodyRefreshTokenModalTitle": {
|
||||
"message": "Your custodian session has expired"
|
||||
},
|
||||
"custodySessionExpired": {
|
||||
"message": "Custodian session expired."
|
||||
},
|
||||
"custom": {
|
||||
"message": "Advanced"
|
||||
},
|
||||
@ -1987,16 +1990,16 @@
|
||||
"message": "Prior to clicking confirm:"
|
||||
},
|
||||
"ledgerConnectionInstructionStepFour": {
|
||||
"message": "Enable \"smart contract data\" or \"blind signing\" on your Ledger device"
|
||||
"message": "Enable \"smart contract data\" or \"blind signing\" on your Ledger device."
|
||||
},
|
||||
"ledgerConnectionInstructionStepOne": {
|
||||
"message": "Enable Use Ledger Live under Settings > Advanced"
|
||||
"message": "Enable Use Ledger Live under Settings > Advanced."
|
||||
},
|
||||
"ledgerConnectionInstructionStepThree": {
|
||||
"message": "Plug in your Ledger device and select the Ethereum app"
|
||||
"message": "Be sure your Ledger is plugged in and to select the Ethereum app."
|
||||
},
|
||||
"ledgerConnectionInstructionStepTwo": {
|
||||
"message": "Open and unlock Ledger Live App"
|
||||
"message": "Open and unlock Ledger Live App."
|
||||
},
|
||||
"ledgerConnectionPreferenceDescription": {
|
||||
"message": "Customize how you connect your Ledger to MetaMask. $1 is recommended, but other options are available. Read more here: $2",
|
||||
@ -2795,7 +2798,7 @@
|
||||
"message": "Open Codefi Compliance"
|
||||
},
|
||||
"openFullScreenForLedgerWebHid": {
|
||||
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
|
||||
"message": "Go to full screen to connect your Ledger.",
|
||||
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
|
||||
},
|
||||
"openInBlockExplorer": {
|
||||
@ -2807,6 +2810,9 @@
|
||||
"openSeaNew": {
|
||||
"message": "OpenSea"
|
||||
},
|
||||
"operationFailed": {
|
||||
"message": "Operation Failed"
|
||||
},
|
||||
"optional": {
|
||||
"message": "Optional"
|
||||
},
|
||||
@ -3790,6 +3796,9 @@
|
||||
"stableLowercase": {
|
||||
"message": "stable"
|
||||
},
|
||||
"stake": {
|
||||
"message": "Stake"
|
||||
},
|
||||
"stateLogError": {
|
||||
"message": "Error in retrieving state logs."
|
||||
},
|
||||
@ -4472,6 +4481,10 @@
|
||||
"transactionErrored": {
|
||||
"message": "Transaction encountered an error."
|
||||
},
|
||||
"transactionFailed": {
|
||||
"message": "Transaction Failed"
|
||||
},
|
||||
|
||||
"transactionFee": {
|
||||
"message": "Transaction fee"
|
||||
},
|
||||
|
14
app/build-types/mmi/images/icons/stake.svg
Normal file
14
app/build-types/mmi/images/icons/stake.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_735_24127)">
|
||||
<path
|
||||
d="M3.99902 19C7.24796 17.5 8.87242 16.75 10.4969 16.75M16.9948 19C13.7458 17.5 12.1214 16.75 10.4969 16.75M10.4969 16.75V11.5M10.4969 11.5L10 10.4091M10.4969 11.5V9.5L10.9967 8.5M10 10.4091C10 10.4091 5.00889 11.0985 2.99935 9.5C1.29118 8.14126 1 4.5 1 4.5C1 4.5 5.55008 3.95155 7.54545 5.90909C8.91802 7.25563 10 10.4091 10 10.4091ZM10.9967 8.5C10.9967 8.5 11.5374 4.11404 13.4959 2.5C15.2137 1.08439 18.9941 1 18.9941 1C18.9941 1 19.1777 5.2683 17.4946 7C15.6792 8.86783 10.9967 8.5 10.9967 8.5Z"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_735_24127">
|
||||
<rect width="20" height="20" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 873 B |
@ -22,7 +22,6 @@ import {
|
||||
MESSAGE_TYPE,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
} from '../../shared/constants/app';
|
||||
import { SECOND } from '../../shared/constants/time';
|
||||
import {
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
REJECT_NOTIFICATION_CLOSE_SIG,
|
||||
@ -442,7 +441,6 @@ export function setupController(
|
||||
infuraProjectId: process.env.INFURA_PROJECT_ID,
|
||||
// User confirmation callbacks:
|
||||
showUserConfirmation: triggerUi,
|
||||
openPopup,
|
||||
// initial state
|
||||
initState,
|
||||
// initial locale code
|
||||
@ -839,22 +837,6 @@ async function triggerUi() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the browser popup for user confirmation of watchAsset
|
||||
* then it waits until user interact with the UI
|
||||
*/
|
||||
async function openPopup() {
|
||||
await triggerUi();
|
||||
await new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
if (!notificationIsOpen) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, SECOND);
|
||||
});
|
||||
}
|
||||
|
||||
// It adds the "App Installed" event into a queue of events, which will be tracked only after a user opts into metrics.
|
||||
const addAppInstalledEvent = () => {
|
||||
if (controller) {
|
||||
|
@ -802,8 +802,8 @@ export default class TransactionController extends EventEmitter {
|
||||
this.txStateManager.getTransactionWithActionId(actionId);
|
||||
if (existingTxMeta) {
|
||||
this.emit('newUnapprovedTx', existingTxMeta);
|
||||
this._requestApproval(existingTxMeta);
|
||||
existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta);
|
||||
this._requestApproval(existingTxMeta);
|
||||
return existingTxMeta;
|
||||
}
|
||||
}
|
||||
@ -875,9 +875,9 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
this.addTransaction(txMeta);
|
||||
this.emit('newUnapprovedTx', txMeta);
|
||||
this._requestApproval(txMeta);
|
||||
|
||||
txMeta = await this.addTransactionGasDefaults(txMeta);
|
||||
this._requestApproval(txMeta);
|
||||
|
||||
return txMeta;
|
||||
}
|
||||
|
@ -339,6 +339,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
}),
|
||||
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`,
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
this.assetsContractController = new AssetsContractController(
|
||||
@ -699,10 +707,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
initState: initState.CachedBalancesController,
|
||||
});
|
||||
|
||||
this.tokensController.hub.on('pendingSuggestedAsset', async () => {
|
||||
await opts.openPopup();
|
||||
});
|
||||
|
||||
let additionalKeyrings = [keyringBuilderFactory(QRHardwareKeyring)];
|
||||
|
||||
if (this.canUseHardwareWallets()) {
|
||||
|
@ -889,17 +889,6 @@ function setupBundlerDefaults(
|
||||
// Run TypeScript files through Babel
|
||||
{ extensions },
|
||||
],
|
||||
// Transpile libraries that use ES2020 unsupported by Chrome v78
|
||||
[
|
||||
babelify,
|
||||
{
|
||||
only: [
|
||||
'./**/node_modules/@ethereumjs/util',
|
||||
'./**/node_modules/superstruct',
|
||||
],
|
||||
global: true,
|
||||
},
|
||||
],
|
||||
// Inline `fs.readFileSync` files
|
||||
brfs,
|
||||
],
|
||||
|
@ -80,7 +80,6 @@
|
||||
"app/scripts/lib/createStreamSink.js",
|
||||
"app/scripts/lib/createTabIdMiddleware.js",
|
||||
"app/scripts/lib/decrypt-message-manager.js",
|
||||
"app/scripts/lib/encryption-public-key-manager.js",
|
||||
"app/scripts/lib/ens-ipfs/contracts/registry.js",
|
||||
"app/scripts/lib/ens-ipfs/contracts/resolver.js",
|
||||
"app/scripts/lib/ens-ipfs/resolver.js",
|
||||
|
@ -687,6 +687,7 @@
|
||||
"URL": true,
|
||||
"clearInterval": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.info": true,
|
||||
"console.log": true,
|
||||
"setInterval": true,
|
||||
@ -696,13 +697,13 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": 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,
|
||||
@ -730,18 +731,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>abort-controller": {
|
||||
"globals": {
|
||||
"AbortController": true
|
||||
@ -766,8 +755,8 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/controller-utils>@metamask/utils": true,
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": true,
|
||||
"@metamask/utils": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -775,18 +764,6 @@
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -687,6 +687,7 @@
|
||||
"URL": true,
|
||||
"clearInterval": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.info": true,
|
||||
"console.log": true,
|
||||
"setInterval": true,
|
||||
@ -696,13 +697,13 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": 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,
|
||||
@ -730,18 +731,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>abort-controller": {
|
||||
"globals": {
|
||||
"AbortController": true
|
||||
@ -766,8 +755,8 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/controller-utils>@metamask/utils": true,
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": true,
|
||||
"@metamask/utils": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -775,18 +764,6 @@
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -687,6 +687,7 @@
|
||||
"URL": true,
|
||||
"clearInterval": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.info": true,
|
||||
"console.log": true,
|
||||
"setInterval": true,
|
||||
@ -696,13 +697,13 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": 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,
|
||||
@ -730,18 +731,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>abort-controller": {
|
||||
"globals": {
|
||||
"AbortController": true
|
||||
@ -766,8 +755,8 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/controller-utils>@metamask/utils": true,
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": true,
|
||||
"@metamask/utils": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -775,18 +764,6 @@
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -687,6 +687,7 @@
|
||||
"URL": true,
|
||||
"clearInterval": true,
|
||||
"clearTimeout": true,
|
||||
"console.error": true,
|
||||
"console.info": true,
|
||||
"console.log": true,
|
||||
"setInterval": true,
|
||||
@ -696,13 +697,13 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": 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,
|
||||
@ -730,18 +731,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>abort-controller": {
|
||||
"globals": {
|
||||
"AbortController": true
|
||||
@ -766,8 +755,8 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/controller-utils>@metamask/utils": true,
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": true,
|
||||
"@metamask/utils": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -775,18 +764,6 @@
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
"TextEncoder": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/utils>superstruct": true,
|
||||
"browserify>buffer": true,
|
||||
"nock>debug": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/controller-utils>@spruceid/siwe-parser": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -226,10 +226,10 @@
|
||||
"@metamask/address-book-controller": "^2.0.0",
|
||||
"@metamask/announcement-controller": "^3.0.0",
|
||||
"@metamask/approval-controller": "^2.1.0",
|
||||
"@metamask/assets-controllers": "^5.0.0",
|
||||
"@metamask/assets-controllers": "^6.0.0",
|
||||
"@metamask/base-controller": "^2.0.0",
|
||||
"@metamask/contract-metadata": "^2.3.1",
|
||||
"@metamask/controller-utils": "^3.1.0",
|
||||
"@metamask/controller-utils": "^3.2.0",
|
||||
"@metamask/design-tokens": "^1.9.0",
|
||||
"@metamask/desktop": "^0.3.0",
|
||||
"@metamask/eth-json-rpc-infura": "^8.0.0",
|
||||
|
@ -8,3 +8,46 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [
|
||||
CHAIN_IDS.OPTIMISM,
|
||||
CHAIN_IDS.ARBITRUM,
|
||||
];
|
||||
|
||||
export const ALLOWED_BRIDGE_TOKEN_ADDRESSES = {
|
||||
[CHAIN_IDS.MAINNET]: [
|
||||
'0xdac17f958d2ee523a2206206994597c13d831ec7',
|
||||
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||||
'0x6b175474e89094c44da98b954eedeac495271d0f',
|
||||
'0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0',
|
||||
'0x8965349fb649a33a30cbfda057d8ec2c48abe2a2',
|
||||
],
|
||||
[CHAIN_IDS.BSC]: [
|
||||
'0x55d398326f99059ff775485246999027b3197955',
|
||||
'0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d',
|
||||
'0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3',
|
||||
'0x2170ed0880ac9a755fd29b2688956bd959f933f8',
|
||||
'0xcc42724c6683b7e57334c4e856f4c9965ed682bd',
|
||||
'0x1ce0c2827e2ef14d5c4f29a091d735a204794041',
|
||||
],
|
||||
[CHAIN_IDS.POLYGON]: [
|
||||
'0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
|
||||
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
|
||||
'0x8f3cf7ad23cd3cadbd9735aff958023239c6a063',
|
||||
'0x7ceb23fd6bc0add59e62ac25578270cff1b9f619',
|
||||
'0x2c89bbc92bd86f8075d1decc58c7f4e0107f286b',
|
||||
],
|
||||
[CHAIN_IDS.AVALANCHE]: [
|
||||
'0xc7198437980c041c805a1edcba50c1ce5db95118',
|
||||
'0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
|
||||
'0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664',
|
||||
'0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e',
|
||||
'0xd586e7f844cea2f87f50152665bcbc2c279d8d70',
|
||||
'0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab',
|
||||
],
|
||||
[CHAIN_IDS.OPTIMISM]: [
|
||||
'0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
|
||||
'0x7f5c764cbc14f9669b88837ca1490cca17c31607',
|
||||
'0xda10009cbd5d07dd0cecc66161fc93d7c9000da1',
|
||||
],
|
||||
[CHAIN_IDS.ARBITRUM]: [
|
||||
'0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9',
|
||||
'0xff970a61a04b1ca14834a43f5de4533ebddb5cc8',
|
||||
'0xda10009cbd5d07dd0cecc66161fc93d7c9000da1',
|
||||
],
|
||||
};
|
||||
|
11
shared/modules/hash.utils.ts
Normal file
11
shared/modules/hash.utils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/* eslint-disable no-undef */
|
||||
export async function sha256(str: string): Promise<string> {
|
||||
const buf = await crypto.subtle.digest(
|
||||
'SHA-256',
|
||||
new TextEncoder().encode(str),
|
||||
);
|
||||
|
||||
return Array.prototype.map
|
||||
.call(new Uint8Array(buf), (x: number) => `00${x.toString(16)}`.slice(-2))
|
||||
.join('');
|
||||
}
|
@ -176,6 +176,8 @@ export async function determineTransactionType(txParams, query) {
|
||||
contractCode = resultCode;
|
||||
|
||||
if (isContractAddress) {
|
||||
const hasValue = txParams.value && txParams.value !== '0x0';
|
||||
|
||||
const tokenMethodName = [
|
||||
TransactionType.tokenMethodApprove,
|
||||
TransactionType.tokenMethodSetApprovalForAll,
|
||||
@ -184,13 +186,8 @@ export async function determineTransactionType(txParams, query) {
|
||||
TransactionType.tokenMethodSafeTransferFrom,
|
||||
].find((methodName) => isEqualCaseInsensitive(methodName, name));
|
||||
|
||||
const isSendWithApprove =
|
||||
txParams.value &&
|
||||
txParams.value !== '0x0' &&
|
||||
tokenMethodName === TransactionType.tokenMethodApprove;
|
||||
|
||||
result =
|
||||
data && tokenMethodName && !isSendWithApprove
|
||||
data && tokenMethodName && !hasValue
|
||||
? tokenMethodName
|
||||
: TransactionType.contractInteraction;
|
||||
} else {
|
||||
|
@ -135,7 +135,7 @@ describe('Transaction.utils', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a token transfer type when the recipient is a contract and data is for the respective method call', async function () {
|
||||
it('should return a token transfer type when the recipient is a contract, there is no value passed, and data is for the respective method call', async function () {
|
||||
const _providerResultStub = {
|
||||
// 1 gwei
|
||||
eth_gasPrice: '0x0de0b6b3a7640000',
|
||||
@ -159,6 +159,48 @@ describe('Transaction.utils', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'should NOT return a token transfer type and instead return contract interaction' +
|
||||
' when the recipient is a contract, the data matches the respective method call, but there is a value passed',
|
||||
async function () {
|
||||
const _providerResultStub = {
|
||||
// 1 gwei
|
||||
eth_gasPrice: '0x0de0b6b3a7640000',
|
||||
// by default, all accounts are external accounts (not contracts)
|
||||
eth_getCode: '0xab',
|
||||
};
|
||||
const _provider = createTestProviderTools({
|
||||
scaffold: _providerResultStub,
|
||||
}).provider;
|
||||
|
||||
const resultWithEmptyValue = await determineTransactionType(
|
||||
{
|
||||
value: '0x0',
|
||||
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
|
||||
data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
|
||||
},
|
||||
new EthQuery(_provider),
|
||||
);
|
||||
expect(resultWithEmptyValue).toMatchObject({
|
||||
type: TransactionType.tokenMethodTransfer,
|
||||
getCodeResponse: '0xab',
|
||||
});
|
||||
|
||||
const resultWithValue = await determineTransactionType(
|
||||
{
|
||||
value: '0x12345',
|
||||
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
|
||||
data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
|
||||
},
|
||||
new EthQuery(_provider),
|
||||
);
|
||||
expect(resultWithValue).toMatchObject({
|
||||
type: TransactionType.contractInteraction,
|
||||
getCodeResponse: '0xab',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('should NOT return a token transfer type when the recipient is not a contract but the data matches the respective method call', async function () {
|
||||
const _providerResultStub = {
|
||||
// 1 gwei
|
||||
|
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/5.1.2/',
|
||||
TEST_SNAPS_WEBSITE_URL: 'https://metamask.github.io/test-snaps/5.2.0/',
|
||||
};
|
||||
|
92
test/e2e/snaps/test-snap-wasm.spec.js
Normal file
92
test/e2e/snaps/test-snap-wasm.spec.js
Normal file
@ -0,0 +1,92 @@
|
||||
const { withFixtures } = require('../helpers');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
const { TEST_SNAPS_WEBSITE_URL } = require('./enums');
|
||||
|
||||
describe('Test Snap WASM', function () {
|
||||
it('can use webassembly inside a snap', async function () {
|
||||
const ganacheOptions = {
|
||||
accounts: [
|
||||
{
|
||||
secretKey:
|
||||
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC',
|
||||
balance: 25000000000000000000,
|
||||
},
|
||||
],
|
||||
};
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: new FixtureBuilder().build(),
|
||||
ganacheOptions,
|
||||
failOnConsoleError: false,
|
||||
title: this.test.title,
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
// enter pw into extension
|
||||
await driver.fill('#password', 'correct horse battery staple');
|
||||
await driver.press('#password', driver.Key.ENTER);
|
||||
|
||||
// navigate to test snaps page and connect
|
||||
await driver.openNewPage(TEST_SNAPS_WEBSITE_URL);
|
||||
await driver.delay(1000);
|
||||
const snapButton = await driver.findElement('#connectWasmSnap');
|
||||
await driver.scrollToElement(snapButton);
|
||||
await driver.delay(1000);
|
||||
await driver.clickElement('#connectWasmSnap');
|
||||
await driver.delay(1000);
|
||||
|
||||
// switch to metamask extension and click connect
|
||||
const windowHandles = await driver.waitUntilXWindowHandles(
|
||||
3,
|
||||
1000,
|
||||
10000,
|
||||
);
|
||||
// const extensionPage = windowHandles[0];
|
||||
await driver.switchToWindowWithTitle(
|
||||
'MetaMask Notification',
|
||||
windowHandles,
|
||||
);
|
||||
await driver.clickElement({
|
||||
text: 'Connect',
|
||||
tag: 'button',
|
||||
});
|
||||
|
||||
await driver.waitForSelector({ text: 'Approve & install' });
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Approve & install',
|
||||
tag: 'button',
|
||||
});
|
||||
|
||||
await driver.waitForSelector({ text: 'Ok' });
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Ok',
|
||||
tag: 'button',
|
||||
});
|
||||
|
||||
// click send inputs on test snap page
|
||||
await driver.switchToWindowWithTitle('Test Snaps', windowHandles);
|
||||
|
||||
// wait for npm installation success
|
||||
await driver.waitForSelector({
|
||||
css: '#connectWasmSnap',
|
||||
text: 'Reconnect to WebAssembly Snap',
|
||||
});
|
||||
|
||||
// enter number for test to input field
|
||||
await driver.pasteIntoField('#wasmInput', '23');
|
||||
|
||||
// find and click on send error
|
||||
await driver.clickElement('#sendWasmMessage');
|
||||
|
||||
// wait for the correct output
|
||||
await driver.waitForSelector({
|
||||
css: '#wasmResult',
|
||||
text: '28657',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
@ -103,3 +103,6 @@
|
||||
@import 'network-account-balance-header/index';
|
||||
@import 'approve-content-card/index';
|
||||
@import 'transaction-alerts/transaction-alerts';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
@import '../institutional/transaction-failed-modal/index';
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
@ -0,0 +1,52 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LedgerInstructionField Component rendering should render properly with data instruction 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="confirm-detail-row"
|
||||
>
|
||||
<div
|
||||
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-info box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-primary-muted box--rounded-sm"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/info.svg');"
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
class="ledger-live-dialog"
|
||||
>
|
||||
<h6
|
||||
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
Prior to clicking confirm:
|
||||
</h6>
|
||||
<h6
|
||||
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
• Be sure your Ledger is plugged in and to select the Ethereum app.
|
||||
</h6>
|
||||
<h6
|
||||
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
• Enable "smart contract data" or "blind signing" on your Ledger device.
|
||||
</h6>
|
||||
<h6
|
||||
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
<span>
|
||||
<button
|
||||
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md mm-text--text-align-left box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
|
||||
>
|
||||
Go to full screen to connect your Ledger.
|
||||
</button>
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -19,15 +19,13 @@ import {
|
||||
getLedgerTransportStatus,
|
||||
} from '../../../ducks/app/app';
|
||||
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import Button from '../../ui/button';
|
||||
import { BannerAlert, ButtonLink, Text } from '../../component-library';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import {
|
||||
FONT_WEIGHT,
|
||||
SEVERITIES,
|
||||
TEXT_ALIGN,
|
||||
TextColor,
|
||||
TypographyVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import Dialog from '../../ui/dialog';
|
||||
import {
|
||||
getPlatform,
|
||||
getEnvironmentType,
|
||||
@ -42,14 +40,9 @@ const renderInstructionStep = (
|
||||
) => {
|
||||
return (
|
||||
show && (
|
||||
<Typography
|
||||
boxProps={{ margin: 0 }}
|
||||
color={color}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
variant={TypographyVariant.H7}
|
||||
>
|
||||
<Text color={color} as="h6">
|
||||
{text}
|
||||
</Typography>
|
||||
</Text>
|
||||
)
|
||||
);
|
||||
};
|
||||
@ -71,8 +64,8 @@ export default function LedgerInstructionField({ showDataInstruction }) {
|
||||
ledgerTransportType === LedgerTransportTypes.webhid &&
|
||||
webHidConnectedStatus !== WebHIDConnectedStatuses.connected
|
||||
) {
|
||||
const devices = await window.navigator.hid.getDevices();
|
||||
const webHidIsConnected = devices.some(
|
||||
const devices = await window.navigator?.hid?.getDevices();
|
||||
const webHidIsConnected = devices?.some(
|
||||
(device) => device.vendorId === Number(LEDGER_USB_VENDOR_ID),
|
||||
);
|
||||
dispatch(
|
||||
@ -136,28 +129,28 @@ export default function LedgerInstructionField({ showDataInstruction }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="confirm-detail-row">
|
||||
<Dialog type="message">
|
||||
<BannerAlert severity={SEVERITIES.INFO}>
|
||||
<div className="ledger-live-dialog">
|
||||
{renderInstructionStep(t('ledgerConnectionInstructionHeader'))}
|
||||
{renderInstructionStep(
|
||||
`- ${t('ledgerConnectionInstructionStepOne')}`,
|
||||
`• ${t('ledgerConnectionInstructionStepOne')}`,
|
||||
!isFirefox && usingLedgerLive,
|
||||
)}
|
||||
{renderInstructionStep(
|
||||
`- ${t('ledgerConnectionInstructionStepTwo')}`,
|
||||
`• ${t('ledgerConnectionInstructionStepTwo')}`,
|
||||
!isFirefox && usingLedgerLive,
|
||||
)}
|
||||
{renderInstructionStep(
|
||||
`- ${t('ledgerConnectionInstructionStepThree')}`,
|
||||
`• ${t('ledgerConnectionInstructionStepThree')}`,
|
||||
)}
|
||||
{renderInstructionStep(
|
||||
`- ${t('ledgerConnectionInstructionStepFour')}`,
|
||||
`• ${t('ledgerConnectionInstructionStepFour')}`,
|
||||
showDataInstruction,
|
||||
)}
|
||||
{renderInstructionStep(
|
||||
<span>
|
||||
<Button
|
||||
type="link"
|
||||
<ButtonLink
|
||||
textAlign={TEXT_ALIGN.LEFT}
|
||||
onClick={async () => {
|
||||
if (environmentTypeIsFullScreen) {
|
||||
window.location.reload();
|
||||
@ -167,14 +160,14 @@ export default function LedgerInstructionField({ showDataInstruction }) {
|
||||
}}
|
||||
>
|
||||
{t('ledgerConnectionInstructionCloseOtherApps')}
|
||||
</Button>
|
||||
</ButtonLink>
|
||||
</span>,
|
||||
transportStatus === HardwareTransportStates.deviceOpenFailure,
|
||||
)}
|
||||
{renderInstructionStep(
|
||||
<span>
|
||||
<Button
|
||||
type="link"
|
||||
<ButtonLink
|
||||
textAlign={TEXT_ALIGN.LEFT}
|
||||
onClick={async () => {
|
||||
if (environmentTypeIsFullScreen) {
|
||||
const connectedDevices =
|
||||
@ -200,19 +193,20 @@ export default function LedgerInstructionField({ showDataInstruction }) {
|
||||
{environmentTypeIsFullScreen
|
||||
? t('clickToConnectLedgerViaWebHID')
|
||||
: t('openFullScreenForLedgerWebHid')}
|
||||
</Button>
|
||||
</ButtonLink>
|
||||
</span>,
|
||||
usingWebHID &&
|
||||
webHidConnectedStatus === WebHIDConnectedStatuses.notConnected,
|
||||
TextColor.WARNING_DEFAULT,
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
</BannerAlert>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LedgerInstructionField.propTypes = {
|
||||
// whether or not to show the data instruction
|
||||
showDataInstruction: PropTypes.bool,
|
||||
};
|
||||
|
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import LedgerInstructionField from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/LedgerInstructionField',
|
||||
argTypes: {
|
||||
showDataInstruction: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <LedgerInstructionField {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import LedgerInstructionField from '.';
|
||||
|
||||
describe('LedgerInstructionField Component', () => {
|
||||
const mockStore = {
|
||||
appState: {
|
||||
ledgerWebHidConnectedStatus: 'notConnected',
|
||||
},
|
||||
metamask: {
|
||||
ledgerTransportType: 'webhid',
|
||||
},
|
||||
};
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should render properly with data instruction', () => {
|
||||
const store = configureMockStore()(mockStore);
|
||||
|
||||
const { container } = renderWithProvider(
|
||||
<LedgerInstructionField showDataInstruction />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -181,7 +181,9 @@ describe('EthOverview', () => {
|
||||
|
||||
await waitFor(() =>
|
||||
expect(openTabSpy).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining(`/bridge?metamaskEntry=ext`),
|
||||
url: expect.stringContaining(
|
||||
'/bridge?metamaskEntry=ext_bridge_button',
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -112,7 +112,14 @@
|
||||
align-items: center;
|
||||
margin: 16px 0 4px 0;
|
||||
padding: 0 16px;
|
||||
max-width: 100%;
|
||||
max-width: 326px;
|
||||
}
|
||||
|
||||
&__primary-container {
|
||||
display: flex;
|
||||
max-width: inherit;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__primary-balance {
|
||||
@ -129,6 +136,11 @@
|
||||
color: var(--color-text-alternative);
|
||||
}
|
||||
|
||||
&__portfolio-button {
|
||||
height: inherit;
|
||||
padding-inline-start: 16px;
|
||||
}
|
||||
|
||||
&__button:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
@ -44,11 +44,14 @@ const TokenOverview = ({ className, token }) => {
|
||||
<WalletOverview
|
||||
balance={
|
||||
<div className="token-overview__balance">
|
||||
<CurrencyDisplay
|
||||
className="token-overview__primary-balance"
|
||||
displayValue={balanceToRender}
|
||||
suffix={token.symbol}
|
||||
/>
|
||||
<div className="token-overview__primary-container">
|
||||
<CurrencyDisplay
|
||||
style={{ display: 'contents' }}
|
||||
className="token-overview__primary-balance"
|
||||
displayValue={balanceToRender}
|
||||
suffix={token.symbol}
|
||||
/>
|
||||
</div>
|
||||
{formattedFiatBalance ? (
|
||||
<CurrencyDisplay
|
||||
className="token-overview__secondary-balance"
|
||||
|
@ -23,6 +23,7 @@ jest.mock('../../../../shared/constants/network', () => ({
|
||||
},
|
||||
},
|
||||
}));
|
||||
let openTabSpy;
|
||||
|
||||
describe('TokenOverview', () => {
|
||||
const mockStore = {
|
||||
@ -68,6 +69,11 @@ describe('TokenOverview', () => {
|
||||
openTab: jest.fn(),
|
||||
},
|
||||
});
|
||||
openTabSpy = jest.spyOn(global.platform, 'openTab');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
openTabSpy.mockClear();
|
||||
});
|
||||
|
||||
const token = {
|
||||
@ -209,8 +215,6 @@ describe('TokenOverview', () => {
|
||||
mockedStoreWithBuyableChainId,
|
||||
);
|
||||
|
||||
const openTabSpy = jest.spyOn(global.platform, 'openTab');
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={token} />,
|
||||
mockedStore,
|
||||
@ -228,5 +232,137 @@ describe('TokenOverview', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should always show the Portfolio button', () => {
|
||||
const mockToken = {
|
||||
name: 'test',
|
||||
isERC721: false,
|
||||
address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
|
||||
symbol: 'test',
|
||||
};
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={mockToken} />,
|
||||
store,
|
||||
);
|
||||
const portfolioButton = queryByTestId('home__portfolio-site');
|
||||
expect(portfolioButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open the Portfolio URI when clicking on Portfolio button', async () => {
|
||||
const mockToken = {
|
||||
name: 'test',
|
||||
isERC721: false,
|
||||
address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
|
||||
symbol: 'test',
|
||||
};
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={mockToken} />,
|
||||
store,
|
||||
);
|
||||
|
||||
const portfolioButton = queryByTestId('home__portfolio-site');
|
||||
|
||||
expect(portfolioButton).toBeInTheDocument();
|
||||
expect(portfolioButton).not.toBeDisabled();
|
||||
|
||||
fireEvent.click(portfolioButton);
|
||||
expect(openTabSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(openTabSpy).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining(`?metamaskEntry=ext`),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should show the Bridge button if chain id and token are supported', async () => {
|
||||
const mockToken = {
|
||||
name: 'test',
|
||||
isERC721: false,
|
||||
address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
|
||||
symbol: 'test',
|
||||
};
|
||||
|
||||
const mockedStoreWithBridgeableChainId = {
|
||||
metamask: {
|
||||
...mockStore.metamask,
|
||||
provider: { type: 'test', chainId: CHAIN_IDS.POLYGON },
|
||||
},
|
||||
};
|
||||
const mockedStore = configureMockStore([thunk])(
|
||||
mockedStoreWithBridgeableChainId,
|
||||
);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={mockToken} />,
|
||||
mockedStore,
|
||||
);
|
||||
const bridgeButton = queryByTestId('token-overview-bridge');
|
||||
expect(bridgeButton).toBeInTheDocument();
|
||||
expect(bridgeButton).not.toBeDisabled();
|
||||
|
||||
fireEvent.click(bridgeButton);
|
||||
expect(openTabSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(openTabSpy).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining(
|
||||
'/bridge?metamaskEntry=ext_bridge_button&token=0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not show the Bridge button if chain id is not supported', async () => {
|
||||
const mockToken = {
|
||||
name: 'test',
|
||||
isERC721: false,
|
||||
address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f619',
|
||||
symbol: 'test',
|
||||
};
|
||||
|
||||
const mockedStoreWithBridgeableChainId = {
|
||||
metamask: {
|
||||
...mockStore.metamask,
|
||||
provider: { type: 'test', chainId: CHAIN_IDS.FANTOM },
|
||||
},
|
||||
};
|
||||
const mockedStore = configureMockStore([thunk])(
|
||||
mockedStoreWithBridgeableChainId,
|
||||
);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={mockToken} />,
|
||||
mockedStore,
|
||||
);
|
||||
const bridgeButton = queryByTestId('token-overview-bridge');
|
||||
expect(bridgeButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show the Bridge button if token is not supported', async () => {
|
||||
const mockToken = {
|
||||
name: 'test',
|
||||
isERC721: false,
|
||||
address: '0x7ceb23fd6bc0add59e62ac25578270cff1B9f620',
|
||||
symbol: 'test',
|
||||
};
|
||||
|
||||
const mockedStoreWithBridgeableChainId = {
|
||||
metamask: {
|
||||
...mockStore.metamask,
|
||||
provider: { type: 'test', chainId: CHAIN_IDS.POLYGON },
|
||||
},
|
||||
};
|
||||
const mockedStore = configureMockStore([thunk])(
|
||||
mockedStoreWithBridgeableChainId,
|
||||
);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TokenOverview token={mockToken} />,
|
||||
mockedStore,
|
||||
);
|
||||
const bridgeButton = queryByTestId('token-overview-bridge');
|
||||
expect(bridgeButton).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -132,6 +132,7 @@ export enum IconName {
|
||||
Snaps = 'snaps',
|
||||
Speedometer = 'speedometer',
|
||||
Star = 'star',
|
||||
Stake = 'stake',
|
||||
Student = 'student',
|
||||
SwapHorizontal = 'swap-horizontal',
|
||||
SwapVertical = 'swap-vertical',
|
||||
|
@ -0,0 +1 @@
|
||||
export { default } from './interactive-replacement-token-notification';
|
@ -0,0 +1,143 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getCurrentKeyring, getSelectedAddress } from '../../../selectors';
|
||||
import { getInteractiveReplacementToken } from '../../../selectors/institutional/selectors';
|
||||
import { getIsUnlocked } from '../../../ducks/metamask/metamask';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
|
||||
import { sha256 } from '../../../../shared/modules/hash.utils';
|
||||
import {
|
||||
Size,
|
||||
IconColor,
|
||||
AlignItems,
|
||||
DISPLAY,
|
||||
BLOCK_SIZES,
|
||||
JustifyContent,
|
||||
TextColor,
|
||||
TextVariant,
|
||||
BackgroundColor,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import {
|
||||
Icon,
|
||||
IconName,
|
||||
IconSize,
|
||||
ButtonLink,
|
||||
Text,
|
||||
} from '../../component-library';
|
||||
|
||||
import Box from '../../ui/box';
|
||||
|
||||
const InteractiveReplacementTokenNotification = ({ isVisible }) => {
|
||||
const t = useI18nContext();
|
||||
const dispatch = useDispatch();
|
||||
const mmiActions = mmiActionsFactory();
|
||||
|
||||
const keyring = useSelector(getCurrentKeyring);
|
||||
const address = useSelector(getSelectedAddress);
|
||||
const isUnlocked = useSelector(getIsUnlocked);
|
||||
const interactiveReplacementToken = useSelector(
|
||||
getInteractiveReplacementToken,
|
||||
);
|
||||
|
||||
const [showNotification, setShowNotification] = useState(isVisible);
|
||||
|
||||
useEffect(() => {
|
||||
const handleShowNotification = async () => {
|
||||
const hasInteractiveReplacementToken =
|
||||
interactiveReplacementToken &&
|
||||
Boolean(Object.keys(interactiveReplacementToken).length);
|
||||
|
||||
if (!/^Custody/u.test(keyring.type)) {
|
||||
setShowNotification(false);
|
||||
return;
|
||||
} else if (!hasInteractiveReplacementToken) {
|
||||
setShowNotification(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await dispatch(mmiActions.getCustodianToken());
|
||||
const custodyAccountDetails = await dispatch(
|
||||
mmiActions.getAllCustodianAccountsWithToken(
|
||||
keyring.type.split(' - ')[1],
|
||||
token,
|
||||
),
|
||||
);
|
||||
|
||||
const showNotificationValue =
|
||||
isUnlocked &&
|
||||
interactiveReplacementToken.oldRefreshToken &&
|
||||
custodyAccountDetails &&
|
||||
Boolean(Object.keys(custodyAccountDetails).length);
|
||||
|
||||
let tokenAccount;
|
||||
|
||||
if (Array.isArray(custodyAccountDetails)) {
|
||||
tokenAccount = custodyAccountDetails
|
||||
.filter(
|
||||
(item) => item.address.toLowerCase() === address.toLowerCase(),
|
||||
)
|
||||
.map((item) => ({
|
||||
token: item.authDetails?.refreshToken,
|
||||
}))[0];
|
||||
}
|
||||
|
||||
const refreshTokenAccount = await sha256(
|
||||
tokenAccount?.token + interactiveReplacementToken.url,
|
||||
);
|
||||
|
||||
setShowNotification(
|
||||
showNotificationValue &&
|
||||
refreshTokenAccount === interactiveReplacementToken.oldRefreshToken,
|
||||
);
|
||||
};
|
||||
|
||||
handleShowNotification();
|
||||
}, [
|
||||
dispatch,
|
||||
address,
|
||||
interactiveReplacementToken,
|
||||
isUnlocked,
|
||||
keyring,
|
||||
mmiActions,
|
||||
]);
|
||||
|
||||
return showNotification ? (
|
||||
<Box
|
||||
width={BLOCK_SIZES.FULL}
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
alignItems={AlignItems.center}
|
||||
padding={[1, 2]}
|
||||
backgroundColor={BackgroundColor.backgroundAlternative}
|
||||
marginBottom={1}
|
||||
className="interactive-replacement-token-notification"
|
||||
data-testid="interactive-replacement-token-notification"
|
||||
>
|
||||
<Icon
|
||||
name={IconName.Danger}
|
||||
color={IconColor.errorDefault}
|
||||
size={IconSize.Xl}
|
||||
/>
|
||||
<Text variant={TextVariant.bodyXs} gap={2} color={TextColor.errorDefault}>
|
||||
{t('custodySessionExpired')}
|
||||
</Text>
|
||||
<ButtonLink
|
||||
data-testid="show-modal"
|
||||
size={Size.auto}
|
||||
marginLeft={1}
|
||||
onClick={() => {
|
||||
dispatch(mmiActions.showInteractiveReplacementTokenModal());
|
||||
}}
|
||||
>
|
||||
{t('learnMore')}
|
||||
</ButtonLink>
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default InteractiveReplacementTokenNotification;
|
||||
|
||||
InteractiveReplacementTokenNotification.propTypes = {
|
||||
isVisible: PropTypes.bool,
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
.interactive-replacement-token-notification {
|
||||
height: 24px;
|
||||
|
||||
@media screen and (min-width: $break-large) {
|
||||
width: 85vw;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1280px) {
|
||||
width: 62vw;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../../../store/store';
|
||||
import testData from '../../../../.storybook/test-data';
|
||||
import InteractiveReplacementTokenNotification from '.';
|
||||
|
||||
const customData = {
|
||||
...testData,
|
||||
metamask: {
|
||||
...testData.metamask,
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
selectedAddress: '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281',
|
||||
identities: {
|
||||
'0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281': {
|
||||
address: '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281',
|
||||
name: 'Custodian A',
|
||||
},
|
||||
},
|
||||
isUnlocked: true,
|
||||
interactiveReplacementToken: {
|
||||
oldRefreshToken:
|
||||
'81f96a88b6cbc5f50d3864122349fa9a9755833ee82a7e3cf6f268c78aab51ab',
|
||||
url: 'url',
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
keyrings: [
|
||||
{
|
||||
type: 'Custody - Saturn',
|
||||
accounts: ['0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureStore(customData);
|
||||
|
||||
export default {
|
||||
title: 'Components/Institutional/InteractiveReplacementToken-Notification',
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
component: InteractiveReplacementTokenNotification,
|
||||
args: {
|
||||
isVisible: true,
|
||||
},
|
||||
argTypes: {
|
||||
onClick: {
|
||||
action: 'onClick',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => (
|
||||
<InteractiveReplacementTokenNotification {...args} />
|
||||
);
|
||||
|
||||
DefaultStory.storyName = 'InteractiveReplacementTokenNotification';
|
@ -0,0 +1,156 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { sha256 } from '../../../../shared/modules/hash.utils';
|
||||
import { KeyringType } from '../../../../shared/constants/keyring';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import InteractiveReplacementTokenNotification from './interactive-replacement-token-notification';
|
||||
|
||||
jest.mock('../../../../shared/modules/hash.utils');
|
||||
|
||||
const mockedShowInteractiveReplacementTokenModal = jest
|
||||
.fn()
|
||||
.mockReturnValue({ type: 'TYPE' });
|
||||
|
||||
const mockedGetCustodianToken = jest
|
||||
.fn()
|
||||
.mockReturnValue({ type: 'Custody', payload: 'token' });
|
||||
|
||||
const mockedGetAllCustodianAccountsWithToken = jest.fn().mockReturnValue({
|
||||
type: 'TYPE',
|
||||
payload: [
|
||||
{
|
||||
address: '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281',
|
||||
authDetails: { refreshToken: 'def' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
jest.mock('../../../store/institutional/institution-background', () => ({
|
||||
mmiActionsFactory: () => ({
|
||||
getCustodianToken: mockedGetCustodianToken,
|
||||
getAllCustodianAccountsWithToken: mockedGetAllCustodianAccountsWithToken,
|
||||
showInteractiveReplacementTokenModal:
|
||||
mockedShowInteractiveReplacementTokenModal,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Interactive Replacement Token Notification', () => {
|
||||
const selectedAddress = '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281';
|
||||
|
||||
const identities = {
|
||||
'0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281': {
|
||||
address: '0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281',
|
||||
name: 'Custodian A',
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
selectedAddress,
|
||||
identities,
|
||||
isUnlocked: false,
|
||||
interactiveReplacementToken: { oldRefreshToken: 'abc' },
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
keyrings: [
|
||||
{
|
||||
type: KeyringType.imported,
|
||||
accounts: ['0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281', '0x2'],
|
||||
},
|
||||
{
|
||||
type: KeyringType.ledger,
|
||||
accounts: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it('should not render if show notification is false', () => {
|
||||
const store = configureMockStore([thunk])(mockStore);
|
||||
|
||||
renderWithProvider(<InteractiveReplacementTokenNotification />, store);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('interactive-replacement-token-notification'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render if show notification is true and click on learn more', async () => {
|
||||
const customMockStore = {
|
||||
...mockStore,
|
||||
metamask: {
|
||||
...mockStore.metamask,
|
||||
isUnlocked: true,
|
||||
interactiveReplacementToken: { oldRefreshToken: 'def', url: 'url' },
|
||||
keyrings: [
|
||||
{
|
||||
type: 'Custody - Saturn',
|
||||
accounts: ['0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const store = configureMockStore([thunk])(customMockStore);
|
||||
|
||||
sha256.mockReturnValue('def');
|
||||
await act(async () => {
|
||||
renderWithProvider(<InteractiveReplacementTokenNotification />, store);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByTestId('interactive-replacement-token-notification'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('show-modal')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('show-modal'));
|
||||
});
|
||||
|
||||
expect(mockedShowInteractiveReplacementTokenModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render and call showNotification when component starts', async () => {
|
||||
const customMockStore = {
|
||||
...mockStore,
|
||||
metamask: {
|
||||
...mockStore.metamask,
|
||||
isUnlocked: true,
|
||||
interactiveReplacementToken: { oldRefreshToken: 'def', url: 'url' },
|
||||
keyrings: [
|
||||
{
|
||||
type: 'Custody - Saturn',
|
||||
accounts: ['0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281', '0x2'],
|
||||
},
|
||||
{
|
||||
type: KeyringType.ledger,
|
||||
accounts: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureMockStore([thunk])(customMockStore);
|
||||
|
||||
sha256.mockReturnValue('def');
|
||||
await act(async () => {
|
||||
renderWithProvider(<InteractiveReplacementTokenNotification />, store);
|
||||
});
|
||||
|
||||
expect(mockedGetCustodianToken).toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByTestId('interactive-replacement-token-notification'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './transaction-failed';
|
@ -0,0 +1,7 @@
|
||||
.transaction-failed {
|
||||
&__description {
|
||||
border: 1px solid var(--color-border-muted);
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import withModalProps from '../../../helpers/higher-order-components/with-modal-props';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import Modal from '../../app/modal';
|
||||
import Box from '../../ui/box/box';
|
||||
import {
|
||||
AlignItems,
|
||||
BorderRadius,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
FONT_WEIGHT,
|
||||
TEXT_ALIGN,
|
||||
TextVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { Text, Icon, IconName, IconSize } from '../../component-library';
|
||||
|
||||
const TransactionFailedModal = ({
|
||||
hideModal,
|
||||
closeNotification,
|
||||
operationFailed,
|
||||
errorMessage,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const handleSubmit = () => {
|
||||
if (closeNotification) {
|
||||
global.platform.closeCurrentWindow();
|
||||
}
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal onSubmit={handleSubmit} submitText={t('ok')}>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
alignItems={AlignItems.center}
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
style={{ flex: 1, overflowY: 'auto' }}
|
||||
>
|
||||
<Icon name={IconName.Warning} size={IconSize.Xl} />
|
||||
<Text
|
||||
as="h1"
|
||||
variant={TextVariant.displayMd}
|
||||
textAlign={TEXT_ALIGN.CENTER}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
>
|
||||
{operationFailed
|
||||
? `${t('operationFailed')}!`
|
||||
: `${t('transactionFailed')}!`}
|
||||
</Text>
|
||||
<Text
|
||||
textAlign={TEXT_ALIGN.CENTER}
|
||||
variant={TextVariant.bodySm}
|
||||
paddingTop={4}
|
||||
paddingBottom={4}
|
||||
paddingLeft={4}
|
||||
paddingRight={4}
|
||||
borderRadius={BorderRadius.MD}
|
||||
className="transaction-failed__description"
|
||||
>
|
||||
{errorMessage}
|
||||
</Text>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
TransactionFailedModal.propTypes = {
|
||||
hideModal: PropTypes.func,
|
||||
errorMessage: PropTypes.string,
|
||||
closeNotification: PropTypes.bool,
|
||||
operationFailed: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withModalProps(TransactionFailedModal);
|
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import TransactionFailedModal from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/Institutional/TransactionFailedModal',
|
||||
argTypes: {},
|
||||
args: {
|
||||
errorMessage: 'test',
|
||||
operationFailed: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => {
|
||||
return <TransactionFailedModal {...args} />;
|
||||
};
|
||||
|
||||
DefaultStory.storyName = 'TransactionFailedModal';
|
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import testData from '../../../../.storybook/test-data';
|
||||
import TransactionFailed from '.';
|
||||
|
||||
const mockErrorMessage = 'Something went wrong';
|
||||
|
||||
describe('Transaction Failed', () => {
|
||||
const mockStore = {
|
||||
...testData,
|
||||
};
|
||||
|
||||
const store = configureMockStore()(mockStore);
|
||||
|
||||
it('renders the error message', () => {
|
||||
renderWithProvider(
|
||||
<TransactionFailed errorMessage={mockErrorMessage} />,
|
||||
store,
|
||||
);
|
||||
const errorMessageElement = screen.getByText(mockErrorMessage);
|
||||
expect(errorMessageElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the correct title when operation fails', () => {
|
||||
const operationFailed = true;
|
||||
const title = 'Operation Failed!';
|
||||
renderWithProvider(
|
||||
<TransactionFailed
|
||||
operationFailed={operationFailed}
|
||||
errorMessage={mockErrorMessage}
|
||||
/>,
|
||||
store,
|
||||
);
|
||||
const titleElement = screen.getByText(title);
|
||||
expect(titleElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the correct title when transaction fails', () => {
|
||||
const operationFailed = false;
|
||||
const title = 'Transaction Failed!';
|
||||
renderWithProvider(
|
||||
<TransactionFailed
|
||||
operationFailed={operationFailed}
|
||||
errorMessage={mockErrorMessage}
|
||||
/>,
|
||||
store,
|
||||
);
|
||||
const titleElement = screen.getByText(title);
|
||||
expect(titleElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('closes window when closeNotification is true', () => {
|
||||
global.platform = {
|
||||
closeCurrentWindow: jest.fn(),
|
||||
};
|
||||
renderWithProvider(<TransactionFailed closeNotification />, store);
|
||||
const okButton = screen.getByText('Ok');
|
||||
fireEvent.click(okButton);
|
||||
expect(global.platform.closeCurrentWindow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import Box from '../../ui/box/box';
|
||||
@ -31,6 +31,14 @@ export const NetworkListItem = ({
|
||||
onDeleteClick,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const networkRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
if (networkRef.current && selected) {
|
||||
networkRef.current.querySelector('.mm-button-link').focus();
|
||||
}
|
||||
}, [networkRef, selected]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
onClick={onClick}
|
||||
@ -43,6 +51,7 @@ export const NetworkListItem = ({
|
||||
alignItems={AlignItems.center}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
ref={networkRef}
|
||||
>
|
||||
{selected && (
|
||||
<Box
|
||||
@ -53,7 +62,14 @@ export const NetworkListItem = ({
|
||||
)}
|
||||
<AvatarNetwork name={name} src={iconSrc} />
|
||||
<Box className="multichain-network-list-item__network-name">
|
||||
<ButtonLink onClick={onClick} color={TextColor.textDefault} ellipsis>
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
}}
|
||||
color={TextColor.textDefault}
|
||||
ellipsis
|
||||
>
|
||||
{name.length > MAXIMUM_CHARACTERS_WITHOUT_TOOLTIP ? (
|
||||
<Tooltip
|
||||
title={name}
|
||||
|
1
ui/ducks/institutional/index.js
Normal file
1
ui/ducks/institutional/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as institutionalFeature } from './institutional';
|
106
ui/ducks/institutional/institutional.js
Normal file
106
ui/ducks/institutional/institutional.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { captureException } from '@sentry/browser';
|
||||
import { mmiActionsFactory } from '../../store/institutional/institution-background';
|
||||
|
||||
const name = 'institutionalFeatures';
|
||||
|
||||
const initialState = {
|
||||
historicalReports: {},
|
||||
complianceProjectId: '',
|
||||
complianceClientId: '',
|
||||
reportsInProgress: {},
|
||||
};
|
||||
|
||||
const slice = createSlice({
|
||||
name,
|
||||
initialState,
|
||||
reducers: {
|
||||
setHistoricalReports(state, action) {
|
||||
state.historicalReports[action.payload.address] = [
|
||||
...action.payload.reports,
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { actions, reducer } = slice;
|
||||
|
||||
export default reducer;
|
||||
|
||||
export const getComplianceProjectId = (state) =>
|
||||
state.metamask[name].complianceProjectId;
|
||||
export const getComplianceClientId = (state) =>
|
||||
state.metamask[name].complianceClientId;
|
||||
export const getComplianceTenantSubdomain = (state) =>
|
||||
state.metamask[name].complianceTenantSubdomain;
|
||||
export const getComplianceHistoricalReports = (state) =>
|
||||
state.metamask[name].historicalReports;
|
||||
export const getComplianceReportsInProgress = (state) =>
|
||||
state.metamask[name].reportsInProgress;
|
||||
export const getInstitutionalConnectRequests = (state) =>
|
||||
state.metamask[name].connectRequests;
|
||||
export const complianceActivated = (state) =>
|
||||
Boolean(state.metamask[name].complianceProjectId);
|
||||
|
||||
export const getComplianceHistoricalReportsByAddress = (address) =>
|
||||
createSelector(getComplianceHistoricalReports, (reports) =>
|
||||
reports ? reports[address] : [],
|
||||
);
|
||||
|
||||
export const getComplianceReportsInProgressByAddress = (address) =>
|
||||
createSelector(getComplianceReportsInProgress, (reports) =>
|
||||
reports ? reports[address.toLowerCase()] : undefined,
|
||||
);
|
||||
|
||||
export const fetchHistoricalReports = (address, testProjectId = undefined) => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const mmiActions = mmiActionsFactory();
|
||||
|
||||
let projectId;
|
||||
|
||||
// testProjectId is provided to make a test request, which checks if projectId is correct
|
||||
if (!testProjectId) {
|
||||
projectId = getComplianceProjectId(state);
|
||||
if (!projectId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await dispatch(
|
||||
mmiActions.getComplianceHistoricalReportsByAddress(address, projectId),
|
||||
);
|
||||
|
||||
dispatch(
|
||||
mmiActions.syncReportsInProgress({
|
||||
address,
|
||||
historicalReports: result.items ? result.items : [],
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
actions.setHistoricalReports({
|
||||
address,
|
||||
reports: result.items
|
||||
? result.items.filter((report) => report.status !== 'inProgress')
|
||||
: [],
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
captureException(error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function generateComplianceReport(address) {
|
||||
return (dispatch, _getState) => {
|
||||
const mmiActions = mmiActionsFactory();
|
||||
dispatch(mmiActions.generateComplianceReport(address));
|
||||
};
|
||||
}
|
||||
|
||||
const { setHistoricalReports } = actions;
|
||||
|
||||
export { setHistoricalReports };
|
70
ui/ducks/institutional/institutional.test.js
Normal file
70
ui/ducks/institutional/institutional.test.js
Normal file
@ -0,0 +1,70 @@
|
||||
import InstitutionalReducer, {
|
||||
fetchHistoricalReports,
|
||||
getComplianceClientId,
|
||||
getComplianceProjectId,
|
||||
getComplianceTenantSubdomain,
|
||||
getComplianceHistoricalReports,
|
||||
getComplianceReportsInProgress,
|
||||
getInstitutionalConnectRequests,
|
||||
complianceActivated,
|
||||
getComplianceReportsInProgressByAddress,
|
||||
generateComplianceReport,
|
||||
} from './institutional';
|
||||
|
||||
const mockSyncReportsInProgress = jest.fn();
|
||||
const mockGenerateComplianceReport = jest.fn();
|
||||
|
||||
jest.mock('../../store/institutional/institution-background', () => ({
|
||||
mmiActionsFactory: () => ({
|
||||
generateComplianceReport: mockGenerateComplianceReport,
|
||||
getComplianceHistoricalReportsByAddress: jest.fn(),
|
||||
syncReportsInProgress: mockSyncReportsInProgress,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Institutional Duck', () => {
|
||||
const initState = {
|
||||
historicalReports: {},
|
||||
complianceProjectId: '',
|
||||
complianceClientId: '',
|
||||
reportsInProgress: {},
|
||||
};
|
||||
|
||||
describe('InstitutionalReducer', () => {
|
||||
it('should initialize state', () => {
|
||||
expect(InstitutionalReducer(undefined, {})).toStrictEqual(initState);
|
||||
});
|
||||
|
||||
it('should correctly return all getters values', async () => {
|
||||
const state = {
|
||||
metamask: {
|
||||
institutionalFeatures: {
|
||||
complianceProjectId: 'complianceProjectId',
|
||||
complianceClientId: 'complianceClientId',
|
||||
complianceTenantSubdomain: 'subdomain',
|
||||
reportsInProgress: { id: [{ reportId: 'id' }] },
|
||||
connectRequests: [{ id: 'id' }],
|
||||
historicalReports: { id: [{ reportId: 'id' }] },
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(getComplianceProjectId(state)).toBe('complianceProjectId');
|
||||
expect(getComplianceClientId(state)).toBe('complianceClientId');
|
||||
expect(getComplianceTenantSubdomain(state)).toBe('subdomain');
|
||||
expect(getComplianceHistoricalReports(state).id[0].reportId).toBe('id');
|
||||
expect(getComplianceReportsInProgress(state).id).toHaveLength(1);
|
||||
expect(getInstitutionalConnectRequests(state)).toHaveLength(1);
|
||||
expect(complianceActivated(state)).toBe(true);
|
||||
expect(getComplianceReportsInProgressByAddress('id')(state)).toHaveLength(
|
||||
1,
|
||||
);
|
||||
await fetchHistoricalReports('0xAddress', 'projectId')(
|
||||
jest.fn().mockReturnValue({ items: [{ status: 'test' }] }),
|
||||
() => state,
|
||||
);
|
||||
expect(mockSyncReportsInProgress).toHaveBeenCalled();
|
||||
await generateComplianceReport('0xAddress')(jest.fn(), () => state);
|
||||
expect(mockGenerateComplianceReport).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -62,3 +62,7 @@ export function getIsCustodianSupportedChain(state) {
|
||||
)
|
||||
: true;
|
||||
}
|
||||
|
||||
export function getInteractiveReplacementToken(state) {
|
||||
return state.metamask.interactiveReplacementToken || {};
|
||||
}
|
||||
|
@ -46,7 +46,10 @@ import {
|
||||
ALLOWED_DEV_SWAPS_CHAIN_IDS,
|
||||
} from '../../shared/constants/swaps';
|
||||
|
||||
import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../shared/constants/bridge';
|
||||
import {
|
||||
ALLOWED_BRIDGE_CHAIN_IDS,
|
||||
ALLOWED_BRIDGE_TOKEN_ADDRESSES,
|
||||
} from '../../shared/constants/bridge';
|
||||
|
||||
import {
|
||||
shortenAddress,
|
||||
@ -747,6 +750,15 @@ export function getIsBridgeChain(state) {
|
||||
return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId);
|
||||
}
|
||||
|
||||
export const getIsBridgeToken = (tokenAddress) => (state) => {
|
||||
const chainId = getCurrentChainId(state);
|
||||
const isBridgeChain = getIsBridgeChain(state);
|
||||
return (
|
||||
isBridgeChain &&
|
||||
ALLOWED_BRIDGE_TOKEN_ADDRESSES[chainId].includes(tokenAddress.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
export function getIsBuyableChain(state) {
|
||||
const chainId = getCurrentChainId(state);
|
||||
return Object.keys(BUYABLE_CHAINS_MAP).includes(chainId);
|
||||
|
@ -450,4 +450,23 @@ describe('Selectors', () => {
|
||||
const isFantomSupported = selectors.getIsBridgeChain(mockState);
|
||||
expect(isFantomSupported).toBeFalsy();
|
||||
});
|
||||
|
||||
it('#getIsBridgeToken', () => {
|
||||
mockState.metamask.provider.chainId = '0xa';
|
||||
const isOptimismTokenSupported = selectors.getIsBridgeToken(
|
||||
'0x94B008aa00579c1307b0ef2c499ad98a8ce58e58',
|
||||
)(mockState);
|
||||
expect(isOptimismTokenSupported).toBeTruthy();
|
||||
|
||||
const isOptimismUnknownTokenSupported = selectors.getIsBridgeToken(
|
||||
'0x94B008aa00579c1307b0ef2c499ad98a8ce58e60',
|
||||
)(mockState);
|
||||
expect(isOptimismUnknownTokenSupported).toBeFalsy();
|
||||
|
||||
mockState.metamask.provider.chainId = '0xfa';
|
||||
const isFantomTokenSupported = selectors.getIsBridgeToken(
|
||||
'0x94B008aa00579c1307b0ef2c499ad98a8ce58e58',
|
||||
)(mockState);
|
||||
expect(isFantomTokenSupported).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
69
yarn.lock
69
yarn.lock
@ -3615,35 +3615,37 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/assets-controllers@npm:^5.0.0":
|
||||
version: 5.0.1
|
||||
resolution: "@metamask/assets-controllers@npm:5.0.1"
|
||||
"@metamask/assets-controllers@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@metamask/assets-controllers@npm:6.0.0"
|
||||
dependencies:
|
||||
"@ethersproject/bignumber": ^5.7.0
|
||||
"@ethersproject/contracts": ^5.7.0
|
||||
"@ethersproject/providers": ^5.7.0
|
||||
"@metamask/abi-utils": ^1.1.0
|
||||
"@metamask/approval-controller": ^2.1.0
|
||||
"@metamask/base-controller": ^2.0.0
|
||||
"@metamask/contract-metadata": ^2.3.1
|
||||
"@metamask/controller-utils": ^3.1.0
|
||||
"@metamask/controller-utils": ^3.2.0
|
||||
"@metamask/metamask-eth-abis": 3.0.0
|
||||
"@metamask/network-controller": ^6.0.0
|
||||
"@metamask/network-controller": ^7.0.0
|
||||
"@metamask/preferences-controller": ^3.0.0
|
||||
"@metamask/utils": ^3.3.1
|
||||
"@metamask/utils": ^5.0.1
|
||||
"@types/uuid": ^8.3.0
|
||||
abort-controller: ^3.0.0
|
||||
async-mutex: ^0.2.6
|
||||
babel-runtime: ^6.26.0
|
||||
eth-query: ^2.1.2
|
||||
eth-rpc-errors: ^4.0.0
|
||||
eth-rpc-errors: ^4.0.2
|
||||
ethereumjs-util: ^7.0.10
|
||||
immer: ^9.0.6
|
||||
multiformats: ^9.5.2
|
||||
single-call-balance-checker-abi: ^1.0.0
|
||||
uuid: ^8.3.2
|
||||
peerDependencies:
|
||||
"@metamask/network-controller": ^6.0.0
|
||||
checksum: aa2ab83752c121fe410f191660c4b57be9cc74cbe462e8c35b86077160c8d6640ccf19d0fa423c8123803e00ba6d7c9112d68b9e058c0fbac1df6210ad3be2b7
|
||||
"@metamask/approval-controller": ^2.1.0
|
||||
"@metamask/network-controller": ^7.0.0
|
||||
checksum: 79c56421567b48b24deab2410645ae60022954ad590e6d91c3770fbeaf24ea3e92608935ae15696349a51bfc5d4337b22cc21f48b0d09bbf4e2f1a0c4aa96904
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -3723,18 +3725,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/controller-utils@npm:^3.0.0, @metamask/controller-utils@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "@metamask/controller-utils@npm:3.1.0"
|
||||
"@metamask/controller-utils@npm:^3.0.0, @metamask/controller-utils@npm:^3.1.0, @metamask/controller-utils@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "@metamask/controller-utils@npm:3.2.0"
|
||||
dependencies:
|
||||
"@metamask/utils": ^3.3.1
|
||||
"@metamask/utils": ^5.0.1
|
||||
"@spruceid/siwe-parser": 1.1.3
|
||||
eth-ens-namehash: ^2.0.8
|
||||
eth-rpc-errors: ^4.0.0
|
||||
eth-rpc-errors: ^4.0.2
|
||||
ethereumjs-util: ^7.0.10
|
||||
ethjs-unit: ^0.1.6
|
||||
fast-deep-equal: ^3.1.3
|
||||
checksum: 811fc4b9da98ca406a0e002c87933687e745d20f802305bb2af0affcdad454189c705caae9389444da1f5f88f2d10269b3ae8354aa1f600a11ddb9315cfa5718
|
||||
checksum: 06b27f9273719ca6eb556c032b77e9066c8d38ad4ff081896a68046e1e4764482f244bf849d51fc622f425e54c9063cc697abdb0cb2f2aaab9a0d8807f2310f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -4079,6 +4081,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/network-controller@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "@metamask/network-controller@npm:7.0.0"
|
||||
dependencies:
|
||||
"@metamask/base-controller": ^2.0.0
|
||||
"@metamask/controller-utils": ^3.1.0
|
||||
"@metamask/swappable-obj-proxy": ^2.1.0
|
||||
"@metamask/utils": ^3.3.1
|
||||
async-mutex: ^0.2.6
|
||||
babel-runtime: ^6.26.0
|
||||
eth-json-rpc-infura: ^5.1.0
|
||||
eth-query: ^2.1.2
|
||||
immer: ^9.0.6
|
||||
uuid: ^8.3.2
|
||||
web3-provider-engine: ^16.0.3
|
||||
checksum: 62eeb223f164db1cb5914070b9ec799ee980c0723e9e154afaab3a3032d5ac133c7d1e1cbc506fd9083254f1888a7f7e99137278a107c9b9d854cb9c629b1713
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/notification-controller@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@metamask/notification-controller@npm:2.0.0"
|
||||
@ -4452,16 +4473,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/utils@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@metamask/utils@npm:5.0.0"
|
||||
"@metamask/utils@npm:^5.0.0, @metamask/utils@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "@metamask/utils@npm:5.0.1"
|
||||
dependencies:
|
||||
"@ethereumjs/tx": ^4.1.1
|
||||
"@types/debug": ^4.1.7
|
||||
debug: ^4.3.4
|
||||
semver: ^7.3.8
|
||||
superstruct: ^1.0.3
|
||||
checksum: 34e39fc0bf28db5fe92676753de3291b05a517f8c81dbe332a4b6002739a58450a89fb2bddd85922a4f420affb1674604e6ad4627cdf052459e5371361ef7dd2
|
||||
checksum: 29745bd3d2db06bf66263bdec04e93a8f417c46c69d8054149c0046ed54b5f13d26d94a998fff1a31b5a8e7a2200935bfc8392a5fa3c4261e3cecd3ccdd9ddc0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24148,11 +24169,11 @@ __metadata:
|
||||
"@metamask/address-book-controller": ^2.0.0
|
||||
"@metamask/announcement-controller": ^3.0.0
|
||||
"@metamask/approval-controller": ^2.1.0
|
||||
"@metamask/assets-controllers": ^5.0.0
|
||||
"@metamask/assets-controllers": ^6.0.0
|
||||
"@metamask/auto-changelog": ^2.1.0
|
||||
"@metamask/base-controller": ^2.0.0
|
||||
"@metamask/contract-metadata": ^2.3.1
|
||||
"@metamask/controller-utils": ^3.1.0
|
||||
"@metamask/controller-utils": ^3.2.0
|
||||
"@metamask/design-tokens": ^1.9.0
|
||||
"@metamask/desktop": ^0.3.0
|
||||
"@metamask/eslint-config": ^9.0.0
|
||||
@ -34395,14 +34416,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"vm2@npm:^3.9.3":
|
||||
version: 3.9.16
|
||||
resolution: "vm2@npm:3.9.16"
|
||||
version: 3.9.17
|
||||
resolution: "vm2@npm:3.9.17"
|
||||
dependencies:
|
||||
acorn: ^8.7.0
|
||||
acorn-walk: ^8.2.0
|
||||
bin:
|
||||
vm2: bin/vm2
|
||||
checksum: 646b45dca721acb3c8e4ae0742129f13612972387911c2475f3c06ac2b4232000cab0bdaaa65d97d6ea8dc70880e039542618b1b3d04adea79cd94803cbc4ab3
|
||||
checksum: 9a03740a40ab2be5e3348a95fb31512da1a3c85318febb07e5299fa103ff05bcd7b6f458211fa38a1281dc27beccd04ff90355fc1d34fe2ee6ca10d0bb8c6f35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user