mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge branch 'develop' into master-sync
This commit is contained in:
commit
bbf4d23fcc
@ -12,7 +12,7 @@ executors:
|
||||
NODE_OPTIONS: --max_old_space_size=2048
|
||||
shellcheck:
|
||||
docker:
|
||||
- image: koalaman/shellcheck-alpine@sha256:35882cba254810c7de458528011e935ba2c4f3ebcb224275dfa7ebfa930ef294
|
||||
- image: koalaman/shellcheck-alpine@sha256:dfaf08fab58c158549d3be64fb101c626abc5f16f341b569092577ae207db199
|
||||
|
||||
workflows:
|
||||
test_and_release:
|
||||
@ -25,10 +25,15 @@ workflows:
|
||||
only:
|
||||
- /^Version-v(\d+)[.](\d+)[.](\d+)/
|
||||
- prep-deps
|
||||
- test-deps-audit
|
||||
- test-deps-audit:
|
||||
requires:
|
||||
- prep-deps
|
||||
- test-deps-depcheck:
|
||||
requires:
|
||||
- prep-deps
|
||||
- test-yarn-dedupe:
|
||||
requires:
|
||||
- prep-deps
|
||||
- validate-lavamoat-config:
|
||||
filters:
|
||||
branches:
|
||||
@ -51,9 +56,12 @@ workflows:
|
||||
- prep-build-test-metrics:
|
||||
requires:
|
||||
- prep-deps
|
||||
- prep-build-storybook:
|
||||
- test-storybook:
|
||||
requires:
|
||||
- prep-deps
|
||||
- prep-build-storybook:
|
||||
requires:
|
||||
- test-storybook
|
||||
- test-lint:
|
||||
requires:
|
||||
- prep-deps
|
||||
@ -330,6 +338,26 @@ jobs:
|
||||
root: .
|
||||
paths:
|
||||
- storybook-build
|
||||
|
||||
test-storybook:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Test Storybook
|
||||
command: yarn storybook:test
|
||||
|
||||
test-yarn-dedupe:
|
||||
executor: node-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Detect yarn lock deduplications
|
||||
command: yarn yarn-deduplicate && git diff --exit-code yarn.lock
|
||||
|
||||
test-lint:
|
||||
executor: node-browsers
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
set -x
|
||||
set -o pipefail
|
||||
|
||||
# use `improved-yarn-audit` since that allows for exclude
|
||||
|
@ -1,8 +1,5 @@
|
||||
# MetaMask Browser Extension
|
||||
|
||||
Hey! We are hiring JavaScript Engineers! [Apply here](https://boards.greenhouse.io/consensys/jobs/2572388)!
|
||||
---
|
||||
|
||||
You can find the latest version of MetaMask on [our official website](https://metamask.io/). For help using MetaMask, visit our [User Support Site](https://metamask.zendesk.com/hc/en-us).
|
||||
|
||||
For [general questions](https://community.metamask.io/c/learn/26), [feature requests](https://community.metamask.io/c/feature-requests-ideas/13), or [developer questions](https://community.metamask.io/c/developer-questions/11), visit our [Community Forum](https://community.metamask.io/).
|
||||
|
@ -1,4 +1,49 @@
|
||||
{
|
||||
"QRHardwareInvalidTransactionTitle": {
|
||||
"message": "Error"
|
||||
},
|
||||
"QRHardwareMismatchedSignId": {
|
||||
"message": "Incongruent transaction data. Please check the transaction details."
|
||||
},
|
||||
"QRHardwarePubkeyAccountOutOfRange": {
|
||||
"message": "No more accounts. If you would like to access another account unlisted below, please reconnect your hardware wallet and select it."
|
||||
},
|
||||
"QRHardwareScanInstructions": {
|
||||
"message": "Place the QR code in front of your camera. The screen is blurred, but it will not affect the reading."
|
||||
},
|
||||
"QRHardwareSignRequestCancel": {
|
||||
"message": "Reject"
|
||||
},
|
||||
"QRHardwareSignRequestDescription": {
|
||||
"message": "After you’ve signed with your wallet, click on 'Get Signature' to receive the signature"
|
||||
},
|
||||
"QRHardwareSignRequestGetSignature": {
|
||||
"message": "Get Signature"
|
||||
},
|
||||
"QRHardwareSignRequestSubtitle": {
|
||||
"message": "Scan the QR code with your wallet"
|
||||
},
|
||||
"QRHardwareSignRequestTitle": {
|
||||
"message": "Request Signature"
|
||||
},
|
||||
"QRHardwareUnknownQRCodeTitle": {
|
||||
"message": "Error"
|
||||
},
|
||||
"QRHardwareUnknownWalletQRCode": {
|
||||
"message": "Invalid QR code. Please scan the sync QR code of the hardware wallet."
|
||||
},
|
||||
"QRHardwareWalletImporterTitle": {
|
||||
"message": "Scan QR Code"
|
||||
},
|
||||
"QRHardwareWalletSteps1Description": {
|
||||
"message": "Connect an airgapped hardware wallet that communicates through QR-codes. Officially supported airgapped hardware wallets include:"
|
||||
},
|
||||
"QRHardwareWalletSteps1Title": {
|
||||
"message": "QR-based HW Wallet"
|
||||
},
|
||||
"QRHardwareWalletSteps2Description": {
|
||||
"message": "AirGap Vault & Ngrave (Coming Soon)"
|
||||
},
|
||||
"about": {
|
||||
"message": "About"
|
||||
},
|
||||
@ -49,6 +94,9 @@
|
||||
"addANetwork": {
|
||||
"message": "Add a network"
|
||||
},
|
||||
"addANickname": {
|
||||
"message": "Add a nickname"
|
||||
},
|
||||
"addAcquiredTokens": {
|
||||
"message": "Add the tokens you've acquired using MetaMask"
|
||||
},
|
||||
@ -85,9 +133,15 @@
|
||||
"addFriendsAndAddresses": {
|
||||
"message": "Add friends and addresses you trust"
|
||||
},
|
||||
"addMemo": {
|
||||
"message": "Add memo"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Add NFT"
|
||||
},
|
||||
"addNFTLowerCase": {
|
||||
"message": "add NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Add Network"
|
||||
},
|
||||
@ -112,12 +166,21 @@
|
||||
"advanced": {
|
||||
"message": "Advanced"
|
||||
},
|
||||
"advancedBaseGasFeeToolTip": {
|
||||
"message": "Any difference between your max base fee and the current base fee will be refunded after completion."
|
||||
},
|
||||
"advancedGasFeeModalTitle": {
|
||||
"message": "Advanced gas fee"
|
||||
},
|
||||
"advancedGasPriceTitle": {
|
||||
"message": "Gas price"
|
||||
},
|
||||
"advancedOptions": {
|
||||
"message": "Advanced Options"
|
||||
},
|
||||
"advancedPriorityFeeToolTip": {
|
||||
"message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction."
|
||||
},
|
||||
"advancedSettingsDescription": {
|
||||
"message": "Access developer features, download State Logs, Reset Account, setup test networks and custom RPC"
|
||||
},
|
||||
@ -317,6 +380,9 @@
|
||||
"builtAroundTheWorld": {
|
||||
"message": "MetaMask is designed and built around the world."
|
||||
},
|
||||
"busy": {
|
||||
"message": "Busy"
|
||||
},
|
||||
"buy": {
|
||||
"message": "Buy"
|
||||
},
|
||||
@ -347,6 +413,9 @@
|
||||
"cancelPopoverTitle": {
|
||||
"message": "Cancel transaction"
|
||||
},
|
||||
"cancelSpeedUp": {
|
||||
"message": "cancel or speed up a tranaction."
|
||||
},
|
||||
"cancellationGasFee": {
|
||||
"message": "Cancellation Gas Fee"
|
||||
},
|
||||
@ -560,6 +629,9 @@
|
||||
"currentLanguage": {
|
||||
"message": "Current Language"
|
||||
},
|
||||
"currentTitle": {
|
||||
"message": "Current:"
|
||||
},
|
||||
"currentlyUnavailable": {
|
||||
"message": "Unavailable on this network"
|
||||
},
|
||||
@ -581,6 +653,9 @@
|
||||
"dappSuggested": {
|
||||
"message": "Site suggested"
|
||||
},
|
||||
"dappSuggestedShortLabel": {
|
||||
"message": "Site"
|
||||
},
|
||||
"dappSuggestedTooltip": {
|
||||
"message": "$1 has recommended this price.",
|
||||
"description": "$1 represents the Dapp's origin"
|
||||
@ -689,6 +764,9 @@
|
||||
"edit": {
|
||||
"message": "Edit"
|
||||
},
|
||||
"editAddressNickname": {
|
||||
"message": "Edit address nickname"
|
||||
},
|
||||
"editContact": {
|
||||
"message": "Edit Contact"
|
||||
},
|
||||
@ -710,6 +788,9 @@
|
||||
"editGasEducationModalTitle": {
|
||||
"message": "How to choose?"
|
||||
},
|
||||
"editGasFeeModalTitle": {
|
||||
"message": "Edit gas fee"
|
||||
},
|
||||
"editGasHigh": {
|
||||
"message": "High"
|
||||
},
|
||||
@ -755,21 +836,12 @@
|
||||
"editGasPriceTooltip": {
|
||||
"message": "This network requires a “Gas price” field when submitting a transaction. Gas price is the amount you will pay pay per unit of gas."
|
||||
},
|
||||
"editGasSubTextAmount": {
|
||||
"message": "$1 $2",
|
||||
"description": "$1 will be passed the editGasSubTextAmountLabel and $2 will be passed the amount in either cryptocurrency or fiat"
|
||||
},
|
||||
"editGasSubTextAmountLabel": {
|
||||
"message": "Max amount:",
|
||||
"description": "This is meant to be used as the $1 substitution editGasSubTextAmount"
|
||||
},
|
||||
"editGasSubTextFee": {
|
||||
"message": "$1 $2",
|
||||
"description": "$1 will be passed the editGasSubTextFeeLabel and $2 will be passed the fee amount in either cryptocurrency or fiat"
|
||||
},
|
||||
"editGasSubTextFeeLabel": {
|
||||
"message": "Max fee:",
|
||||
"description": "$1 represents a dollar amount"
|
||||
"message": "Max fee:"
|
||||
},
|
||||
"editGasTitle": {
|
||||
"message": "Edit priority"
|
||||
@ -783,6 +855,12 @@
|
||||
"editGasTooLowWarningTooltip": {
|
||||
"message": "This lowers your maximum fee but if network traffic increases your transaction may be delayed or fail."
|
||||
},
|
||||
"editInGwei": {
|
||||
"message": "Edit in GWEI"
|
||||
},
|
||||
"editInMultiplier": {
|
||||
"message": "Edit in multiplier"
|
||||
},
|
||||
"editNonceField": {
|
||||
"message": "Edit Nonce"
|
||||
},
|
||||
@ -795,6 +873,12 @@
|
||||
"enableFromSettings": {
|
||||
"message": " Enable it from Settings."
|
||||
},
|
||||
"enableOpenSeaAPI": {
|
||||
"message": "Enable OpenSea API"
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"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"
|
||||
@ -1011,6 +1095,9 @@
|
||||
"message": "Gas limit must be at least $1",
|
||||
"description": "$1 is the custom gas limit, in decimal."
|
||||
},
|
||||
"gasOption": {
|
||||
"message": "Gas option"
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Gas Price (GWEI)"
|
||||
},
|
||||
@ -1029,13 +1116,18 @@
|
||||
"gasPriceInfoTooltipContent": {
|
||||
"message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
|
||||
},
|
||||
"gasPriceLabel": {
|
||||
"message": "Gas price"
|
||||
"gasTimingHoursShort": {
|
||||
"message": "$1 hrs",
|
||||
"description": "$1 represents a number of hours"
|
||||
},
|
||||
"gasTimingMinutes": {
|
||||
"message": "$1 minutes",
|
||||
"description": "$1 represents a number of minutes"
|
||||
},
|
||||
"gasTimingMinutesShort": {
|
||||
"message": "$1 min",
|
||||
"description": "$1 represents a number of minutes"
|
||||
},
|
||||
"gasTimingNegative": {
|
||||
"message": "Maybe in $1",
|
||||
"description": "$1 represents an amount of time"
|
||||
@ -1048,6 +1140,10 @@
|
||||
"message": "$1 seconds",
|
||||
"description": "$1 represents a number of seconds"
|
||||
},
|
||||
"gasTimingSecondsShort": {
|
||||
"message": "$1 sec",
|
||||
"description": "$1 represents a number of seconds"
|
||||
},
|
||||
"gasTimingVeryPositive": {
|
||||
"message": "Very likely in < $1",
|
||||
"description": "$1 represents an amount of time"
|
||||
@ -1276,6 +1372,12 @@
|
||||
"message": "JSON File",
|
||||
"description": "format for importing an account"
|
||||
},
|
||||
"keystone": {
|
||||
"message": "Keystone"
|
||||
},
|
||||
"keystoneTutorial": {
|
||||
"message": " (Tutorials)"
|
||||
},
|
||||
"knownAddressRecipient": {
|
||||
"message": "Known contract address."
|
||||
},
|
||||
@ -1291,8 +1393,15 @@
|
||||
"layer1Fees": {
|
||||
"message": "Layer 1 fees"
|
||||
},
|
||||
"learmMoreAboutGas": {
|
||||
"message": "Want to $1 about gas?"
|
||||
},
|
||||
"learnCancelSpeeedup": {
|
||||
"message": "Learn how to $1",
|
||||
"description": "$1 is link to cancel or speed up transactions"
|
||||
},
|
||||
"learnMore": {
|
||||
"message": "Learn more"
|
||||
"message": "learn more"
|
||||
},
|
||||
"learnScamRisk": {
|
||||
"message": "scams and security risks."
|
||||
@ -1496,6 +1605,9 @@
|
||||
"mobileSyncWarning": {
|
||||
"message": "The 'Sync with extension' feature is temporarily disabled. If you want to use your extension wallet on MetaMask mobile, then on your mobile app: go back to the wallet setup options and select the 'Import with Secret Recovery Phrase' option. Use your extension wallet's secret phrase to then import your wallet into mobile."
|
||||
},
|
||||
"multiplier": {
|
||||
"message": "multiplier"
|
||||
},
|
||||
"mustSelectOne": {
|
||||
"message": "Must select at least 1 token."
|
||||
},
|
||||
@ -1558,6 +1670,9 @@
|
||||
"networkSettingsDescription": {
|
||||
"message": "Add and edit custom RPC networks"
|
||||
},
|
||||
"networkStatus": {
|
||||
"message": "Network status"
|
||||
},
|
||||
"networkURL": {
|
||||
"message": "Network URL"
|
||||
},
|
||||
@ -1580,12 +1695,24 @@
|
||||
"message": "Account $1",
|
||||
"description": "Default name of next account to be created on create account screen"
|
||||
},
|
||||
"newCollectibleAddFailed": {
|
||||
"message": "Collectible was not added because: $1"
|
||||
},
|
||||
"newCollectibleAddedMessage": {
|
||||
"message": "Collectible was successfully added!"
|
||||
},
|
||||
"newContact": {
|
||||
"message": "New Contact"
|
||||
},
|
||||
"newContract": {
|
||||
"message": "New Contract"
|
||||
},
|
||||
"newNFTsDetected": {
|
||||
"message": "New NFTs detected"
|
||||
},
|
||||
"newNFTsDetectedInfo": {
|
||||
"message": "One or more new NFTs were detected in your wallet."
|
||||
},
|
||||
"newNetworkAdded": {
|
||||
"message": "“$1” was successfully added!"
|
||||
},
|
||||
@ -1614,6 +1741,9 @@
|
||||
"nfts": {
|
||||
"message": "NFTs"
|
||||
},
|
||||
"nickname": {
|
||||
"message": "Nickname"
|
||||
},
|
||||
"noAccountsFound": {
|
||||
"message": "No accounts found for the given search query"
|
||||
},
|
||||
@ -1653,6 +1783,9 @@
|
||||
"nonceFieldHeading": {
|
||||
"message": "Custom Nonce"
|
||||
},
|
||||
"notBusy": {
|
||||
"message": "Not busy"
|
||||
},
|
||||
"notCurrentAccount": {
|
||||
"message": "Is this the correct account? It's different from the currently selected account in your wallet"
|
||||
},
|
||||
@ -1861,6 +1994,16 @@
|
||||
"pending": {
|
||||
"message": "Pending"
|
||||
},
|
||||
"pendingTransactionInfo": {
|
||||
"message": "This transaction will not process until that one is complete."
|
||||
},
|
||||
"pendingTransactionMultiple": {
|
||||
"message": "You have ($1) pending transactions."
|
||||
},
|
||||
"pendingTransactionSingle": {
|
||||
"message": "You have (1) pending transaction.",
|
||||
"description": "$1 is count of pending transactions"
|
||||
},
|
||||
"permissionCheckedIconDescription": {
|
||||
"message": "You have approved this permission"
|
||||
},
|
||||
@ -1893,6 +2036,9 @@
|
||||
"primaryCurrencySettingDescription": {
|
||||
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
|
||||
},
|
||||
"priorityFee": {
|
||||
"message": "Priority Fee"
|
||||
},
|
||||
"privacyMsg": {
|
||||
"message": "Privacy Policy"
|
||||
},
|
||||
@ -1906,6 +2052,9 @@
|
||||
"privateNetwork": {
|
||||
"message": "Private Network"
|
||||
},
|
||||
"proceedWithTransaction": {
|
||||
"message": "I want to proceed anyway"
|
||||
},
|
||||
"proposedApprovalLimit": {
|
||||
"message": "Proposed Approval Limit"
|
||||
},
|
||||
@ -2180,6 +2329,9 @@
|
||||
"selectHdPath": {
|
||||
"message": "Select HD Path"
|
||||
},
|
||||
"selectNFTPrivacyPreference": {
|
||||
"message": "Select NFT privacy preference"
|
||||
},
|
||||
"selectPathHelp": {
|
||||
"message": "If you don't see the accounts you expect, try switching the HD path."
|
||||
},
|
||||
@ -2287,6 +2439,12 @@
|
||||
"signed": {
|
||||
"message": "Signed"
|
||||
},
|
||||
"simulationErrorMessage": {
|
||||
"message": "This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended."
|
||||
},
|
||||
"simulationErrorMessageV2": {
|
||||
"message": "We were not able to estimate gas. There might be an error in the contract and this transaction may fail."
|
||||
},
|
||||
"skip": {
|
||||
"message": "Skip"
|
||||
},
|
||||
@ -2339,6 +2497,9 @@
|
||||
"spendLimitTooLarge": {
|
||||
"message": "Spend limit too large"
|
||||
},
|
||||
"stable": {
|
||||
"message": "Stable"
|
||||
},
|
||||
"stateLogError": {
|
||||
"message": "Error in retrieving state logs."
|
||||
},
|
||||
@ -2793,6 +2954,9 @@
|
||||
"thisWillCreate": {
|
||||
"message": "This will create a new wallet and Secret Recovery Phrase"
|
||||
},
|
||||
"time": {
|
||||
"message": "Time"
|
||||
},
|
||||
"tips": {
|
||||
"message": "Tips"
|
||||
},
|
||||
@ -2947,9 +3111,15 @@
|
||||
"tryAgain": {
|
||||
"message": "Try again"
|
||||
},
|
||||
"tryAnywayOption": {
|
||||
"message": "I will try anyway"
|
||||
},
|
||||
"turnOnTokenDetection": {
|
||||
"message": "Turn on enhanced token detection"
|
||||
},
|
||||
"twelveHrTitle": {
|
||||
"message": "12hr:"
|
||||
},
|
||||
"typePassword": {
|
||||
"message": "Type your MetaMask password"
|
||||
},
|
||||
@ -3008,6 +3178,12 @@
|
||||
"urlExistsErrorMsg": {
|
||||
"message": "This URL is currently used by the $1 network."
|
||||
},
|
||||
"useCollectibleDetection": {
|
||||
"message": "Autodetect NFTs"
|
||||
},
|
||||
"useCollectibleDetectionDescription": {
|
||||
"message": "Displaying NFTs media & data may expose your IP address to centralized servers. Third-party APIs (like OpenSea) are used to detect NFTs in your wallet. This exposes your account address with those services. Leave this disabled if you don’t want the app to pull data from those those services."
|
||||
},
|
||||
"usePhishingDetection": {
|
||||
"message": "Use Phishing Detection"
|
||||
},
|
||||
@ -3053,6 +3229,9 @@
|
||||
"viewMore": {
|
||||
"message": "View More"
|
||||
},
|
||||
"viewOnBlockExplorer": {
|
||||
"message": "View on block explorer"
|
||||
},
|
||||
"viewOnCustomBlockExplorer": {
|
||||
"message": "View $1 at $2",
|
||||
"description": "$1 is the action type. e.g (Account, Transaction, Swap) and $2 is the Custom Block Exporer URL"
|
||||
|
@ -1,4 +1,46 @@
|
||||
{
|
||||
"QRHardwareInvalidTransactionTitle": {
|
||||
"message": "非法交易"
|
||||
},
|
||||
"QRHardwareMismatchedSignId": {
|
||||
"message": "扫描的签名二维码不属于当前交易,请检查交易详情后重试。"
|
||||
},
|
||||
"QRHardwarePubkeyAccountOutOfRange": {
|
||||
"message": "暂无更多账户,若想切换到其他账户,请在硬件钱包中选择想要的账户重新同步。"
|
||||
},
|
||||
"QRHardwareScanInstructions": {
|
||||
"message": "为了保护您的隐私,屏幕是模糊的,但不影响对二维码的读取。"
|
||||
},
|
||||
"QRHardwareSignRequestCancel": {
|
||||
"message": "拒绝该交易"
|
||||
},
|
||||
"QRHardwareSignRequestDescription": {
|
||||
"message": "硬件钱包扫描上方二维码完成签名后,点击“获取签名”按钮扫描已签名的二维码"
|
||||
},
|
||||
"QRHardwareSignRequestGetSignature": {
|
||||
"message": "获取签名"
|
||||
},
|
||||
"QRHardwareSignRequestSubtitle": {
|
||||
"message": "用硬件钱包扫描二维码"
|
||||
},
|
||||
"QRHardwareSignRequestTitle": {
|
||||
"message": "获取签名"
|
||||
},
|
||||
"QRHardwareUnknownQRCodeTitle": {
|
||||
"message": "非法二维码"
|
||||
},
|
||||
"QRHardwareUnknownWalletQRCode": {
|
||||
"message": "请扫描硬件钱包的同步二维码。"
|
||||
},
|
||||
"QRHardwareWalletImporterTitle": {
|
||||
"message": "扫描二维码"
|
||||
},
|
||||
"QRHardwareWalletSteps1Description": {
|
||||
"message": "该类硬件钱包通过二维码实现通讯交互,做到完全脱网。官方支持的钱包有:"
|
||||
},
|
||||
"QRHardwareWalletSteps2Description": {
|
||||
"message": "AirGap Vault & Ngrave (即将上线)"
|
||||
},
|
||||
"about": {
|
||||
"message": "关于"
|
||||
},
|
||||
@ -835,6 +877,12 @@
|
||||
"message": "JSON 文件",
|
||||
"description": "format for importing an account"
|
||||
},
|
||||
"keystone": {
|
||||
"message": "铠石钱包"
|
||||
},
|
||||
"keystoneTutorial": {
|
||||
"message": " (使用教程)"
|
||||
},
|
||||
"knownAddressRecipient": {
|
||||
"message": "已知接收方地址。"
|
||||
},
|
||||
|
1
app/images/high-arrow.svg
Normal file
1
app/images/high-arrow.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.022 4.82c0 .275.22.469.483.482l3.26-.082L3.8 9.183a.451.451 0 0 0 0 .663l.442.442c.18.18.47.193.663 0l3.963-3.964-.082 3.232a.49.49 0 0 0 .47.497h.607a.484.484 0 0 0 .47-.47V4.199a.46.46 0 0 0-.456-.456H4.49a.484.484 0 0 0-.47.47v.607Z" fill="#219E37"/></svg>
|
After Width: | Height: | Size: 347 B |
1
app/images/low-arrow.svg
Normal file
1
app/images/low-arrow.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="13" height="13" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.296 8.42c0-.276-.22-.47-.483-.483l-3.26.083 3.964-3.964a.451.451 0 0 0 0-.663l-.442-.442a.463.463 0 0 0-.662 0L4.449 6.915l.083-3.232a.49.49 0 0 0-.47-.497h-.607a.484.484 0 0 0-.47.47v5.386a.46.46 0 0 0 .456.456h5.386a.484.484 0 0 0 .47-.47V8.42Z" fill="#D73A49"/></svg>
|
After Width: | Height: | Size: 358 B |
56
app/images/qrcode-wallet-demo.svg
Normal file
56
app/images/qrcode-wallet-demo.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="297px" height="101px" viewBox="0 0 297 101" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>png-chahua</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="png-chahua" transform="translate(1.000000, 0.000000)">
|
||||
<g id="P-1">
|
||||
<g id="png-key" transform="translate(0.000000, 22.000000)">
|
||||
<circle id="oval" stroke="#3098DC" fill="#D9F0FF" cx="32" cy="32" r="32"></circle>
|
||||
<g id="png_logo" transform="translate(15.850003, 11.617735)">
|
||||
<path d="M13.4677287,26.0869239 C13.6814978,26.2099569 13.8780148,26.3607837 14.0521451,26.5354635 L20.8323207,33.3370367 C22.2800205,34.7893053 22.3263651,37.1092074 20.9740009,38.6170536 L20.8237527,38.7754247 C20.8235032,38.7756734 20.8232537,38.7759221 20.8230041,38.7761707 L19.8377351,39.7578004 C18.7942353,40.7974458 17.2102004,41.0696737 15.8794816,40.438055 L8.37983921,36.8783886 C6.63920381,36.052205 5.89789413,33.9713871 6.7240777,32.2307517 C6.76351709,32.1476593 6.80622708,32.066159 6.85210754,31.986442 L9.6565953,27.1136614 C10.425485,25.7777203 12.1317875,25.3180342 13.4677287,26.0869239 Z M16.4259709,0.276152586 C16.7491885,0.460847609 17.0173036,0.728398213 17.2026794,1.05122584 L18.5283228,3.35980278 C19.1466826,4.43666186 19.1460308,5.76104031 18.5266115,6.83729026 L3.68360588,32.6272236 C3.39176095,33.1343083 2.7441005,33.3087947 2.23701577,33.0169497 C2.07620665,32.9243985 1.94252531,32.7912356 1.8493499,32.6307873 C1.23868219,31.579216 0.773046665,30.4498818 0.465104842,29.2734937 L0.222743083,28.3476321 C-0.230049649,26.6178897 0.0132040067,24.7795765 0.900309637,23.2271354 L13.5700193,1.05505383 C14.1435809,0.0513169242 15.422234,-0.297409042 16.4259709,0.276152586 Z M22.314756,10.7149284 C22.7424128,10.9610537 23.0971746,11.3161563 23.3428892,11.7440492 L26.2513718,16.8089476 C27.0189573,18.1456385 26.5576062,19.8514916 25.2209153,20.6190771 C24.7979802,20.8619446 24.3187864,20.9897452 23.8310788,20.9897452 L17.4043388,20.9897452 C16.0556092,20.9897452 14.9622479,19.8963838 14.9622479,18.5476542 C14.9622479,18.1201149 15.0744902,17.7000673 15.2877511,17.3295143 L18.5036388,11.7417258 C19.2725074,10.4057726 20.9788028,9.94605968 22.314756,10.7149284 Z" id="combine" fill="#000000"></path>
|
||||
<path d="M31.1744255,25.3821635 C31.8384048,26.538444 31.6433147,27.9964878 30.6987072,28.937535 L27.4928953,32.1312641 C26.2639847,33.3541704 24.2764867,33.3507256 23.0518226,32.1235666 L17.3441941,26.3978915 C16.6640589,25.7156057 16.6658022,24.6111453 17.348088,23.9310101 C17.6750575,23.6050712 18.1179052,23.422047 18.5795817,23.422047 L27.7885506,23.422047 C29.1867243,23.422047 30.4781713,24.1696784 31.1744255,25.3821635 Z" id="path" fill="#2161FF"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M83,17.0235687 L73.4978847,30 L64.0002855,17.0235687 L73.4973317,22.4634417 L73.4978847,22.4646936 L83,17.0235687 Z M73.4973317,11.0923553 L82.9936676,15.2782188 L73.4978847,20.720593 L64.0002855,15.2782188 L73.4973317,11.0923553 Z M73.4970462,0 L82.9936676,15.2781544 L73.4978847,11.0897901 L64,15.2781544 L73.4970462,0 Z" id="combine" fill="#3098DC" fill-rule="nonzero"></path>
|
||||
<g id="png-key" transform="translate(64.000000, 77.000000)" fill-rule="nonzero">
|
||||
<g id="Airgap_Logo_sideways_color-xs">
|
||||
<path d="M9.79205788,0.504426266 C9.79205788,0.0716219234 9.47417837,-0.113865652 9.09272297,0.0716219234 C9.09272297,0.0716219234 7.56690134,0.937230609 6.35895922,1.49369334 C4.96028939,2.11198525 2.73513285,2.73027717 1.33646303,2.97759394 C0.446400413,3.16308152 0.319248611,3.71954424 0.25567271,4.21417778 C0.192096809,5.01795727 0.128520908,6.00722434 0.128520908,6.81100384 C0.0649450067,8.91319636 -0.125782697,11.0772181 0.128520908,13.1175814 C0.25567271,14.3541652 0.637128117,15.7762367 1.20931123,16.8891621 C2.09937384,18.4967211 3.24374006,19.9806217 4.57883399,21.2172055 C5.97750381,22.4537894 9.02914707,23.8758608 9.02914707,23.8758608 C9.47417837,24.0613484 9.79205788,23.8758608 9.79205788,23.3812273 L9.79205788,0.504426266 Z" id="path" fill="#3098DC"></path>
|
||||
<path d="M12.2079421,23.4430564 C12.2079421,23.8758608 12.5258216,24.1231776 12.9708529,23.93769 C12.9708529,23.93769 16.0224962,22.5156186 17.421166,21.2790347 C18.8198358,20.0424509 19.9642021,18.5585503 20.7906888,16.9509913 C21.3628719,15.8380658 21.7443273,14.4159944 21.8714791,13.1794106 C22.1257827,11.1390473 21.935055,8.91319636 21.8714791,6.87283303 C21.8714791,6.06905353 21.8079032,5.07978646 21.7443273,4.27600697 C21.6807514,3.71954424 21.5535996,3.16308152 20.663537,3.03942313 C19.2648671,2.79210637 17.0397106,2.17381445 15.6410408,1.55552253 C14.4966746,0.999059801 12.907277,0.133451115 12.907277,0.133451115 C12.5258216,-0.113865652 12.2079421,0.0716219234 12.2079421,0.566255458 L12.2079421,23.4430564 L12.2079421,23.4430564 Z" id="path" fill="#3098DC"></path>
|
||||
<path d="M9.85563378,1.18454738 L9.8542943,3.37036304 C9.98221357,3.6905739 10.2954143,3.90503182 10.6821205,3.90503182 L10.6821205,3.90503182 L11.3814554,3.90503182 L11.3814554,4.09051939 L9.85479032,4.08954738 L9.85479032,8.54154738 L12.8437011,8.54222121 L12.8437011,13.7358733 L9.85479032,13.7355474 L9.85479032,17.8165474 L12.907277,17.8166 C13.8609156,17.8166 14.5602505,18.5585503 14.5602505,19.424159 C14.5602505,20.2897677 13.8609156,21.031718 12.907277,21.031718 L9.85479032,21.0315474 L9.85563378,22.9484229 L9.53775427,22.8247645 C8.01193265,22.1446434 6.42253512,21.4026931 5.214593,20.2897677 C3.94307498,19.1768422 2.92586056,17.6929416 2.16294974,16.2708702 C1.65434253,15.2816031 1.33646303,14.0450193 1.20931123,12.9320938 C0.955007622,11.0772181 1.14573533,9.16051313 1.20931123,7.30563737 C1.20931123,6.3163703 1.27288713,5.26527404 1.40003893,4.21417778 C1.40003893,4.21417778 4.26095448,3.53405667 6.29538332,2.79210637 C7.50332544,2.29747283 8.64769166,1.8028393 9.85563378,1.18454738 Z" id="combine" fill="#FFFFFF"></path>
|
||||
<path d="M12.3350939,1.18454738 C13.4158842,1.8028393 14.5602505,2.35930202 15.7681926,2.79210637 C17.7390455,3.59588586 20.663537,4.21417778 20.663537,4.21417778 L20.663537,4.21417778 L20.8542647,7.36746656 C20.9178406,9.22234232 21.0449924,11.1390473 20.8542647,12.993923 C20.7271129,14.1068485 20.4092334,15.3434323 19.9006262,16.3326994 C19.0741394,17.7547708 18.1205009,19.2386714 16.8489829,20.3515969 C15.5774649,21.4645223 14.0516433,22.2064726 12.5258216,22.8865937 L12.5258216,22.8865937 L12.3350939,23.0102521 L12.3343299,17.8165474 L8.32981215,17.8166 C7.56690134,17.8166 6.99471823,17.1983081 6.99471823,16.4563578 C6.99471823,15.7144075 7.63047724,15.1579447 8.32981215,15.1579447 L12.3343299,15.1575474 L12.3343299,12.3135474 L10.8728482,12.3138019 C10.3006651,12.3138019 9.85563378,11.8191684 9.85563378,11.2627056 C9.85563378,10.9631464 9.98460231,10.699423 10.1929484,10.5197638 L8.39338805,10.5207553 C7.24902183,10.5207553 6.35895922,9.59331747 6.42253512,8.54222121 C6.42253512,7.49112495 7.31259773,6.62551626 8.39338805,6.62551626 L12.3343299,6.62454738 Z" id="combine" fill="#3098DC"></path>
|
||||
<path d="M14.5602505,6.56368707 L4.83313759,6.56368707 C4.07022678,6.56368707 3.49804367,5.94539515 3.49804367,5.20344485 C3.49804367,4.46149455 4.13380268,3.90503182 4.83313759,3.90503182 L14.5602505,3.90503182 C15.3231613,3.90503182 15.8953444,4.52332374 15.8953444,5.26527404 C15.8317685,6.00722434 15.2595854,6.56368707 14.5602505,6.56368707 Z" id="path" fill="#FFFFFF"></path>
|
||||
<path d="M14.0516433,15.1579447 L4.83313759,15.1579447 C4.00665088,15.1579447 3.37089187,14.5396528 3.37089187,13.7358733 C3.37089187,12.9320938 4.00665088,12.3138019 4.83313759,12.3138019 L14.1152192,12.3138019 C14.9417059,12.3138019 15.5774649,12.9320938 15.5774649,13.7358733 C15.5774649,14.5396528 14.87813,15.1579447 14.0516433,15.1579447 L14.0516433,15.1579447 Z" id="path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group" transform="translate(187.000000, 17.000000)">
|
||||
<g id="cp" stroke="#3098DC">
|
||||
<path d="M10,0 L98,0 C100.209139,-4.05812251e-16 102,1.790861 102,4 L102,69 L102,69 L6,69 L6,4 C6,1.790861 7.790861,2.18216909e-15 10,0 Z" id="rectangle" fill="#FFFFFF"></path>
|
||||
<rect id="rectangle" fill="#FFFFFF" x="10.5" y="4.5" width="87" height="57"></rect>
|
||||
<rect id="rectangle" fill="#D9F0FF" x="0" y="66" width="108" height="7" rx="1"></rect>
|
||||
</g>
|
||||
<g id="5" transform="translate(35.000000, 17.000000)" fill-rule="nonzero">
|
||||
<g id="4">
|
||||
<path d="M35.7940726,0.865647154 L37.3076975,5.33187939 L36.0572619,11.2664578 L35.3130052,12.3918268 L36.2488109,13.108213 L35.0978726,14.1466087 L35.8822714,14.7050297 L34.478956,16.3199413 L37.0166977,24.0476925 L34.9619918,30.9406553 L27.2570674,28.8445942 L25.4588663,30.2992958 L22.489449,32.3310811 L15.4970666,32.3310811 L12.5415596,30.2997394 L10.7428766,28.8445914 L3.03908343,30.9403669 L0.996819559,24.0470926 L3.50539453,16.3206129 L2.11516087,14.7068706 L2.90215816,14.146619 L1.74643066,13.1038982 L2.66706954,12.387498 L1.5590983,11.552182 L0.692123065,5.33103555 L2.19338019,0.865960951 L13.1228366,4.89862071 L24.8771779,4.89862071 L35.7940726,0.865647154 Z" id="symbol" stroke="#3098DC" stroke-width="1.33783784"></path>
|
||||
<path d="M20.5,27.5 L21,30 L16.5,30 L17,27.5 L20.5,27.5 Z M14.3439693,19.0321194 L15.8156414,22.0321194 L10.8156414,20.6010552 L14.3439693,19.0321194 Z M23.2820965,19.0321194 L26.8156414,20.6010552 L21.8156414,22.0321194 L23.2820965,19.0321194 Z" id="combine" fill="#3098DC"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="code" transform="translate(82.000000, 37.000000)">
|
||||
<g id="p-2" transform="translate(26.000000, 0.000000)" fill="#3098DC" fill-rule="nonzero">
|
||||
<g id="qr_code">
|
||||
<path d="M1.94444444,11.6666667 L9.72222222,11.6666667 C10.7961092,11.6666667 11.6666667,10.7961092 11.6666667,9.72222222 L11.6666667,1.94444444 C11.6666667,0.870557431 10.7961092,0 9.72222222,0 L1.94444444,0 C0.870557431,0 0,0.870557431 0,1.94444444 L0,9.72222222 C0,10.7961092 0.870557431,11.6666667 1.94444444,11.6666667 Z M3.88888889,3.88888889 L7.77777778,3.88888889 L7.77777778,7.77777778 L3.88888889,7.77777778 L3.88888889,3.88888889 Z M0.972222222,19.4444444 L2.91666667,19.4444444 C3.45361017,19.4444444 3.88888889,19.0091657 3.88888889,18.4722222 L3.88888889,16.5277778 C3.88888889,15.9908343 3.45361017,15.5555556 2.91666667,15.5555556 L0.972222222,15.5555556 C0.435278715,15.5555556 0,15.9908343 0,16.5277778 L0,18.4722222 C0,19.0091657 0.435278715,19.4444444 0.972222222,19.4444444 Z M9.72222222,23.3333333 L1.94444444,23.3333333 C0.870557431,23.3333333 0,24.2038908 0,25.2777778 L0,33.0555556 C0,34.1294426 0.870557431,35 1.94444444,35 L9.72222222,35 C10.7961092,35 11.6666667,34.1294426 11.6666667,33.0555556 L11.6666667,25.2777778 C11.6666667,24.2038908 10.7961092,23.3333333 9.72222222,23.3333333 Z M7.77777778,31.1111111 L3.88888889,31.1111111 L3.88888889,27.2222222 L7.77777778,27.2222222 L7.77777778,31.1111111 Z M28.1944444,35 L34.0277778,35 C34.5647213,35 35,34.5647213 35,34.0277778 L35,28.1944444 C35,27.6575009 34.5647213,27.2222222 34.0277778,27.2222222 L32.0833333,27.2222222 C31.5463898,27.2222222 31.1111111,27.6575009 31.1111111,28.1944444 L31.1111111,31.1111111 L27.2222222,31.1111111 L27.2222222,34.0277778 C27.2222222,34.5647213 27.6575009,35 28.1944444,35 Z M33.0555556,0 L25.2777778,0 C24.2038908,0 23.3333333,0.870557431 23.3333333,1.94444444 L23.3333333,9.72222222 C23.3333333,10.7961092 24.2038908,11.6666667 25.2777778,11.6666667 L33.0555556,11.6666667 C34.1294426,11.6666667 35,10.7961092 35,9.72222222 L35,1.94444444 C35,0.870557431 34.1294426,0 33.0555556,0 Z M31.1111111,7.77777778 L27.2222222,7.77777778 L27.2222222,3.88888889 L31.1111111,3.88888889 L31.1111111,7.77777778 Z M8.75,15.5555556 C8.21305649,15.5555556 7.77777778,15.9908343 7.77777778,16.5277778 L7.77777778,18.4722222 C7.77777778,19.0091657 8.21305649,19.4444444 8.75,19.4444444 L15.5555556,19.4444444 L15.5555556,15.5555556 L8.75,15.5555556 Z M15.5555556,22.3611111 C15.5555556,22.8980546 15.9908343,23.3333333 16.5277778,23.3333333 L19.4444444,23.3333333 L19.4444444,26.25 C19.4444444,26.7869435 19.8797232,27.2222222 20.4166667,27.2222222 L23.3333333,27.2222222 L23.3333333,19.4444444 L15.5555556,19.4444444 L15.5555556,22.3611111 Z M15.5555556,32.0833333 L15.5555556,34.0277778 C15.5555556,34.5647213 15.9908343,35 16.5277778,35 L22.3611111,35 C22.8980546,35 23.3333333,34.5647213 23.3333333,34.0277778 L23.3333333,31.1111111 L16.5277778,31.1111111 C15.9908343,31.1111111 15.5555556,31.5463898 15.5555556,32.0833333 Z M34.0277778,15.5555556 L24.3055556,15.5555556 C23.768612,15.5555556 23.3333333,15.9908343 23.3333333,16.5277778 L23.3333333,19.4444444 L27.2222222,19.4444444 L27.2222222,22.3611111 C27.2222222,22.8980546 27.6575009,23.3333333 28.1944444,23.3333333 L30.1388889,23.3333333 C30.6758324,23.3333333 31.1111111,22.8980546 31.1111111,22.3611111 L31.1111111,19.4444444 L34.0277778,19.4444444 C34.5647213,19.4444444 35,19.0091657 35,18.4722222 L35,16.5277778 C35,15.9908343 34.5647213,15.5555556 34.0277778,15.5555556 Z M27.2222222,31.1111111 L27.2222222,27.2222222 L23.3333333,27.2222222 L23.3333333,31.1111111 L27.2222222,31.1111111 Z M16.5277778,7.77777778 L18.4722222,7.77777778 C19.0091657,7.77777778 19.4444444,7.34249906 19.4444444,6.80555556 L19.4444444,0.972222222 C19.4444444,0.435278715 19.0091657,0 18.4722222,0 L16.5277778,0 C15.9908343,0 15.5555556,0.435278715 15.5555556,0.972222222 L15.5555556,6.80555556 C15.5555556,7.34249906 15.9908343,7.77777778 16.5277778,7.77777778 Z M19.4444444,14.5833333 L19.4444444,12.6388889 C19.4444444,12.1019454 19.0091657,11.6666667 18.4722222,11.6666667 L16.5277778,11.6666667 C15.9908343,11.6666667 15.5555556,12.1019454 15.5555556,12.6388889 L15.5555556,15.5555556 L18.4722222,15.5555556 C19.0091657,15.5555556 19.4444444,15.1202768 19.4444444,14.5833333 Z" id="Icon-color"></path>
|
||||
</g>
|
||||
</g>
|
||||
<g id="4" transform="translate(73.000000, 12.000000)">
|
||||
<rect id="path" fill="#3098DC" x="0" y="5" width="14" height="1"></rect>
|
||||
<polyline id="path" stroke="#3098DC" points="8 0 14 5.5 8 10.5"></polyline>
|
||||
</g>
|
||||
<g id="4" transform="translate(7.000000, 17.250000) scale(-1, 1) translate(-7.000000, -17.250000) translate(0.000000, 12.000000)">
|
||||
<rect id="path" fill="#3098DC" x="0" y="5" width="14" height="1"></rect>
|
||||
<polyline id="path" stroke="#3098DC" points="8 0 14 5.5 8 10.5"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
11
app/images/qrcode-wallet-logo.svg
Normal file
11
app/images/qrcode-wallet-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 12 KiB |
@ -16,6 +16,7 @@ export default class AppStateController extends EventEmitter {
|
||||
onInactiveTimeout,
|
||||
showUnlockRequest,
|
||||
preferencesStore,
|
||||
qrHardwareStore,
|
||||
} = opts;
|
||||
super();
|
||||
|
||||
@ -31,7 +32,9 @@ export default class AppStateController extends EventEmitter {
|
||||
recoveryPhraseReminderHasBeenShown: false,
|
||||
recoveryPhraseReminderLastShown: new Date().getTime(),
|
||||
showTestnetMessageInDropdown: true,
|
||||
trezorModel: null,
|
||||
...initState,
|
||||
qrHardware: {},
|
||||
});
|
||||
this.timer = null;
|
||||
|
||||
@ -48,6 +51,10 @@ export default class AppStateController extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
qrHardwareStore.subscribe((state) => {
|
||||
this.store.updateState({ qrHardware: state });
|
||||
});
|
||||
|
||||
const { preferences } = preferencesStore.getState();
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
||||
}
|
||||
@ -237,4 +244,12 @@ export default class AppStateController extends EventEmitter {
|
||||
setShowTestnetMessageInDropdown(showTestnetMessageInDropdown) {
|
||||
this.store.updateState({ showTestnetMessageInDropdown });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property indicating the model of the user's Trezor hardware wallet
|
||||
* @returns {void}
|
||||
*/
|
||||
setTrezorModel(trezorModel) {
|
||||
this.store.updateState({ trezorModel });
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ export default class PreferencesController {
|
||||
// set to true means the dynamic list from the API is being used
|
||||
// set to false will be using the static list from contract-metadata
|
||||
useTokenDetection: false,
|
||||
useCollectibleDetection: false,
|
||||
openSeaEnabled: false,
|
||||
advancedGasFee: null,
|
||||
|
||||
// WARNING: Do not use feature flags for security-sensitive things.
|
||||
@ -130,6 +132,35 @@ export default class PreferencesController {
|
||||
this.store.updateState({ useTokenDetection: val });
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `useCollectibleDetection` property
|
||||
*
|
||||
* @param {boolean} val - Whether or not the user prefers to autodetect collectibles.
|
||||
*
|
||||
*/
|
||||
setUseCollectibleDetection(val) {
|
||||
const { openSeaEnabled } = this.store.getState();
|
||||
if (val && !openSeaEnabled) {
|
||||
throw new Error(
|
||||
'useCollectibleDetection cannot be enabled if openSeaEnabled is false',
|
||||
);
|
||||
}
|
||||
this.store.updateState({ useCollectibleDetection: val });
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `openSeaEnabled` property
|
||||
*
|
||||
* @param {boolean} val - Whether or not the user prefers to use the OpenSea API for collectibles data.
|
||||
*
|
||||
*/
|
||||
setOpenSeaEnabled(val) {
|
||||
this.store.updateState({ openSeaEnabled: val });
|
||||
if (!val) {
|
||||
this.store.updateState({ useCollectibleDetection: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the `advancedGasFee` property
|
||||
*
|
||||
|
@ -267,6 +267,42 @@ describe('preferences controller', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setUseCollectibleDetection', function () {
|
||||
it('should default to false', function () {
|
||||
const state = preferencesController.store.getState();
|
||||
assert.equal(state.useCollectibleDetection, false);
|
||||
});
|
||||
|
||||
it('should set the useCollectibleDetection property in state', function () {
|
||||
assert.equal(
|
||||
preferencesController.store.getState().useCollectibleDetection,
|
||||
false,
|
||||
);
|
||||
preferencesController.setOpenSeaEnabled(true);
|
||||
preferencesController.setUseCollectibleDetection(true);
|
||||
assert.equal(
|
||||
preferencesController.store.getState().useCollectibleDetection,
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOpenSeaEnabled', function () {
|
||||
it('should default to false', function () {
|
||||
const state = preferencesController.store.getState();
|
||||
assert.equal(state.openSeaEnabled, false);
|
||||
});
|
||||
|
||||
it('should set the openSeaEnabled property in state', function () {
|
||||
assert.equal(
|
||||
preferencesController.store.getState().openSeaEnabled,
|
||||
false,
|
||||
);
|
||||
preferencesController.setOpenSeaEnabled(true);
|
||||
assert.equal(preferencesController.store.getState().openSeaEnabled, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAdvancedGasFee', function () {
|
||||
it('should default to null', function () {
|
||||
const state = preferencesController.store.getState();
|
||||
|
@ -79,7 +79,6 @@ const initialState = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
saveFetchedQuotes: false,
|
||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
@ -123,9 +122,9 @@ export default class SwapsController {
|
||||
});
|
||||
}
|
||||
|
||||
async fetchSwapsRefreshRates(chainId, useNewSwapsApi) {
|
||||
async fetchSwapsRefreshRates(chainId) {
|
||||
const response = await fetchWithCache(
|
||||
getBaseApi('network', chainId, useNewSwapsApi),
|
||||
getBaseApi('network', chainId),
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
);
|
||||
@ -149,13 +148,9 @@ export default class SwapsController {
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsRefreshRates() {
|
||||
const chainId = this._getCurrentChainId();
|
||||
const { swapsState } = this.store.getState();
|
||||
let swapsRefreshRates;
|
||||
try {
|
||||
swapsRefreshRates = await this.fetchSwapsRefreshRates(
|
||||
chainId,
|
||||
swapsState.useNewSwapsApi,
|
||||
);
|
||||
swapsRefreshRates = await this.fetchSwapsRefreshRates(chainId);
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e);
|
||||
}
|
||||
@ -210,11 +205,7 @@ export default class SwapsController {
|
||||
) {
|
||||
const { chainId } = fetchParamsMetaData;
|
||||
const {
|
||||
swapsState: {
|
||||
useNewSwapsApi,
|
||||
quotesPollingLimitEnabled,
|
||||
saveFetchedQuotes,
|
||||
},
|
||||
swapsState: { quotesPollingLimitEnabled, saveFetchedQuotes },
|
||||
} = this.store.getState();
|
||||
|
||||
if (!fetchParams) {
|
||||
@ -242,7 +233,6 @@ export default class SwapsController {
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams, {
|
||||
...fetchParamsMetaData,
|
||||
useNewSwapsApi,
|
||||
}),
|
||||
this._setSwapsRefreshRates(),
|
||||
]);
|
||||
@ -574,9 +564,9 @@ export default class SwapsController {
|
||||
|
||||
setSwapsLiveness(swapsLiveness) {
|
||||
const { swapsState } = this.store.getState();
|
||||
const { swapsFeatureIsLive, useNewSwapsApi } = swapsLiveness;
|
||||
const { swapsFeatureIsLive } = swapsLiveness;
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive, useNewSwapsApi },
|
||||
swapsState: { ...swapsState, swapsFeatureIsLive },
|
||||
});
|
||||
}
|
||||
|
||||
@ -588,7 +578,6 @@ export default class SwapsController {
|
||||
tokens: swapsState.tokens,
|
||||
fetchParams: swapsState.fetchParams,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
useNewSwapsApi: swapsState.useNewSwapsApi,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
swapsQuotePrefetchingRefreshTime:
|
||||
swapsState.swapsQuotePrefetchingRefreshTime,
|
||||
|
@ -131,7 +131,6 @@ const EMPTY_INIT_STATE = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
swapsQuoteRefreshTime: 60000,
|
||||
swapsQuotePrefetchingRefreshTime: 60000,
|
||||
swapsUserFeeLevel: '',
|
||||
@ -707,7 +706,6 @@ describe('SwapsController', function () {
|
||||
assert.strictEqual(
|
||||
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS, {
|
||||
...MOCK_FETCH_METADATA,
|
||||
useNewSwapsApi: false,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@ -885,7 +883,6 @@ describe('SwapsController', function () {
|
||||
const tokens = 'test';
|
||||
const fetchParams = 'test';
|
||||
const swapsFeatureIsLive = false;
|
||||
const useNewSwapsApi = false;
|
||||
const swapsQuoteRefreshTime = 0;
|
||||
const swapsQuotePrefetchingRefreshTime = 0;
|
||||
swapsController.store.updateState({
|
||||
@ -893,7 +890,6 @@ describe('SwapsController', function () {
|
||||
tokens,
|
||||
fetchParams,
|
||||
swapsFeatureIsLive,
|
||||
useNewSwapsApi,
|
||||
swapsQuoteRefreshTime,
|
||||
swapsQuotePrefetchingRefreshTime,
|
||||
},
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
GAS_ESTIMATE_TYPES,
|
||||
GAS_RECOMMENDATIONS,
|
||||
CUSTOM_GAS_ESTIMATE,
|
||||
PRIORITY_LEVELS,
|
||||
} from '../../../../shared/constants/gas';
|
||||
import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
@ -438,7 +439,11 @@ export default class TransactionController extends EventEmitter {
|
||||
) {
|
||||
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
|
||||
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
|
||||
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
|
||||
if (process.env.EIP_1559_V2) {
|
||||
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
|
||||
} else {
|
||||
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(defaultMaxFeePerGas &&
|
||||
@ -448,6 +453,8 @@ export default class TransactionController extends EventEmitter {
|
||||
txMeta.origin === 'metamask'
|
||||
) {
|
||||
txMeta.userFeeLevel = GAS_RECOMMENDATIONS.MEDIUM;
|
||||
} else if (process.env.EIP_1559_V2) {
|
||||
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
|
||||
} else {
|
||||
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
|
||||
}
|
||||
|
@ -79,9 +79,9 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {promise} after signature has been
|
||||
*
|
||||
*/
|
||||
addUnapprovedMessageAsync(msgParams, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const msgId = this.addUnapprovedMessage(msgParams, req);
|
||||
async addUnapprovedMessageAsync(msgParams, req) {
|
||||
const msgId = this.addUnapprovedMessage(msgParams, req);
|
||||
return await new Promise((resolve, reject) => {
|
||||
// await finished
|
||||
this.once(`${msgId}:finished`, (data) => {
|
||||
switch (data.status) {
|
||||
@ -93,6 +93,10 @@ export default class MessageManager extends EventEmitter {
|
||||
'MetaMask Message Signature: User denied message signature.',
|
||||
),
|
||||
);
|
||||
case 'errored':
|
||||
return reject(
|
||||
new Error(`MetaMask Message Signature: ${data.error}`),
|
||||
);
|
||||
default:
|
||||
return reject(
|
||||
new Error(
|
||||
@ -233,6 +237,19 @@ export default class MessageManager extends EventEmitter {
|
||||
this._setMsgStatus(msgId, 'rejected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId - The id of the Message to error
|
||||
*
|
||||
*/
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId);
|
||||
msg.error = error;
|
||||
this._updateMsg(msg);
|
||||
this._setMsgStatus(msgId, 'errored');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unapproved messages from memory.
|
||||
*/
|
||||
@ -304,7 +321,7 @@ export default class MessageManager extends EventEmitter {
|
||||
* @returns {string} A hex string conversion of the buffer data
|
||||
*
|
||||
*/
|
||||
function normalizeMsgData(data) {
|
||||
export function normalizeMsgData(data) {
|
||||
if (data.slice(0, 2) === '0x') {
|
||||
// data is already hex
|
||||
return data;
|
||||
|
@ -107,6 +107,9 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
),
|
||||
);
|
||||
return;
|
||||
case 'errored':
|
||||
reject(new Error(`MetaMask Message Signature: ${data.error}`));
|
||||
return;
|
||||
default:
|
||||
reject(
|
||||
new Error(
|
||||
@ -254,6 +257,19 @@ export default class PersonalMessageManager extends EventEmitter {
|
||||
this._setMsgStatus(msgId, 'rejected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId - The id of the Message to error
|
||||
*
|
||||
*/
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId);
|
||||
msg.error = error;
|
||||
this._updateMsg(msg);
|
||||
this._setMsgStatus(msgId, 'errored');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unapproved messages from memory.
|
||||
*/
|
||||
|
@ -15,8 +15,10 @@ import log from 'loglevel';
|
||||
import TrezorKeyring from 'eth-trezor-keyring';
|
||||
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
|
||||
import LatticeKeyring from 'eth-lattice-keyring';
|
||||
import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring';
|
||||
import EthQuery from 'eth-query';
|
||||
import nanoid from 'nanoid';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import { captureException } from '@sentry/browser';
|
||||
import {
|
||||
AddressBookController,
|
||||
@ -29,6 +31,9 @@ import {
|
||||
TokenListController,
|
||||
TokensController,
|
||||
TokenRatesController,
|
||||
CollectiblesController,
|
||||
AssetsContractController,
|
||||
CollectibleDetectionController,
|
||||
} from '@metamask/controllers';
|
||||
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
|
||||
import {
|
||||
@ -37,7 +42,10 @@ import {
|
||||
SWAPS_CLIENT_ID,
|
||||
} from '../../shared/constants/swaps';
|
||||
import { MAINNET_CHAIN_ID } from '../../shared/constants/network';
|
||||
import { KEYRING_TYPES } from '../../shared/constants/hardware-wallets';
|
||||
import {
|
||||
DEVICE_NAMES,
|
||||
KEYRING_TYPES,
|
||||
} from '../../shared/constants/hardware-wallets';
|
||||
import { UI_NOTIFICATIONS } from '../../shared/notifications';
|
||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||
import { MILLISECOND } from '../../shared/constants/time';
|
||||
@ -61,7 +69,7 @@ import AlertController from './controllers/alert';
|
||||
import OnboardingController from './controllers/onboarding';
|
||||
import ThreeBoxController from './controllers/threebox';
|
||||
import IncomingTransactionsController from './controllers/incoming-transactions';
|
||||
import MessageManager from './lib/message-manager';
|
||||
import MessageManager, { normalizeMsgData } from './lib/message-manager';
|
||||
import DecryptMessageManager from './lib/decrypt-message-manager';
|
||||
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager';
|
||||
import PersonalMessageManager from './lib/personal-message-manager';
|
||||
@ -175,6 +183,59 @@ export default class MetamaskController extends EventEmitter {
|
||||
state: initState.TokensController,
|
||||
});
|
||||
|
||||
this.assetsContractController = new AssetsContractController({
|
||||
provider: this.provider,
|
||||
});
|
||||
|
||||
this.collectiblesController = new CollectiblesController({
|
||||
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
|
||||
this.preferencesController.store,
|
||||
),
|
||||
onNetworkStateChange: this.networkController.store.subscribe.bind(
|
||||
this.networkController.store,
|
||||
),
|
||||
getAssetName: this.assetsContractController.getAssetName.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getAssetSymbol: this.assetsContractController.getAssetSymbol.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getCollectibleTokenURI: this.assetsContractController.getCollectibleTokenURI.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getOwnerOf: this.assetsContractController.getOwnerOf.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
balanceOfERC1155Collectible: this.assetsContractController.balanceOfERC1155Collectible.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
uriERC1155Collectible: this.assetsContractController.uriERC1155Collectible.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
});
|
||||
|
||||
process.env.COLLECTIBLES_V1 &&
|
||||
(this.collectibleDetectionController = new CollectibleDetectionController(
|
||||
{
|
||||
onCollectiblesStateChange: (listener) =>
|
||||
this.collectiblesController.subscribe(listener),
|
||||
onPreferencesStateChange: this.preferencesController.store.subscribe.bind(
|
||||
this.preferencesController.store,
|
||||
),
|
||||
onNetworkStateChange: this.networkController.store.subscribe.bind(
|
||||
this.networkController.store,
|
||||
),
|
||||
getOpenSeaApiKey: () => this.collectiblesController.openSeaApiKey,
|
||||
getBalancesInSingleCall: this.assetsContractController.getBalancesInSingleCall.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
addCollectible: this.collectiblesController.addCollectible.bind(
|
||||
this.collectiblesController,
|
||||
),
|
||||
getCollectiblesState: () => this.collectiblesController.state,
|
||||
},
|
||||
));
|
||||
|
||||
this.metaMetricsController = new MetaMetricsController({
|
||||
segment,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
@ -231,6 +292,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
},
|
||||
});
|
||||
|
||||
this.qrHardwareKeyring = new QRHardwareKeyring();
|
||||
|
||||
this.appStateController = new AppStateController({
|
||||
addUnlockListener: this.on.bind(this, 'unlock'),
|
||||
isUnlocked: this.isUnlocked.bind(this),
|
||||
@ -238,6 +301,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
onInactiveTimeout: () => this.setLocked(),
|
||||
showUnlockRequest: opts.showUserConfirmation,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
qrHardwareStore: this.qrHardwareKeyring.getMemStore(),
|
||||
});
|
||||
|
||||
const currencyRateMessenger = this.controllerMessenger.getRestricted({
|
||||
@ -377,6 +441,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
TrezorKeyring,
|
||||
LedgerBridgeKeyring,
|
||||
LatticeKeyring,
|
||||
QRHardwareKeyring,
|
||||
];
|
||||
this.keyringController = new KeyringController({
|
||||
keyringTypes: additionalKeyrings,
|
||||
@ -525,6 +590,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
this.networkController.lookupNetwork();
|
||||
this.messageManager = new MessageManager({
|
||||
metricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
@ -611,6 +677,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
GasFeeController: this.gasFeeController,
|
||||
TokenListController: this.tokenListController,
|
||||
TokensController: this.tokensController,
|
||||
CollectiblesController: this.collectiblesController,
|
||||
});
|
||||
|
||||
this.memStore = new ComposableObservableStore({
|
||||
@ -645,6 +712,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
GasFeeController: this.gasFeeController,
|
||||
TokenListController: this.tokenListController,
|
||||
TokensController: this.tokensController,
|
||||
CollectiblesController: this.collectiblesController,
|
||||
},
|
||||
controllerMessenger: this.controllerMessenger,
|
||||
});
|
||||
@ -826,6 +894,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
threeBoxController,
|
||||
txController,
|
||||
tokensController,
|
||||
collectiblesController,
|
||||
} = this;
|
||||
|
||||
return {
|
||||
@ -843,6 +912,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.preferencesController.setUseTokenDetection,
|
||||
this.preferencesController,
|
||||
),
|
||||
setUseCollectibleDetection: nodeify(
|
||||
this.preferencesController.setUseCollectibleDetection,
|
||||
this.preferencesController,
|
||||
),
|
||||
setOpenSeaEnabled: nodeify(
|
||||
this.preferencesController.setOpenSeaEnabled,
|
||||
this.preferencesController,
|
||||
),
|
||||
setIpfsGateway: this.setIpfsGateway.bind(this),
|
||||
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
|
||||
setCurrentLocale: this.setCurrentLocale.bind(this),
|
||||
@ -880,6 +957,28 @@ export default class MetamaskController extends EventEmitter {
|
||||
this,
|
||||
),
|
||||
|
||||
// qr hardware devices
|
||||
submitQRHardwareCryptoHDKey: nodeify(
|
||||
this.qrHardwareKeyring.submitCryptoHDKey,
|
||||
this.qrHardwareKeyring,
|
||||
),
|
||||
submitQRHardwareCryptoAccount: nodeify(
|
||||
this.qrHardwareKeyring.submitCryptoAccount,
|
||||
this.qrHardwareKeyring,
|
||||
),
|
||||
cancelSyncQRHardware: nodeify(
|
||||
this.qrHardwareKeyring.cancelSync,
|
||||
this.qrHardwareKeyring,
|
||||
),
|
||||
submitQRHardwareSignature: nodeify(
|
||||
this.qrHardwareKeyring.submitSignature,
|
||||
this.qrHardwareKeyring,
|
||||
),
|
||||
cancelQRHardwareSignRequest: nodeify(
|
||||
this.qrHardwareKeyring.cancelSignRequest,
|
||||
this.qrHardwareKeyring,
|
||||
),
|
||||
|
||||
// mobile
|
||||
fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
|
||||
|
||||
@ -948,6 +1047,27 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesController,
|
||||
),
|
||||
|
||||
// CollectiblesController
|
||||
addCollectible: nodeify(
|
||||
collectiblesController.addCollectible,
|
||||
collectiblesController,
|
||||
),
|
||||
|
||||
addCollectibleVerifyOwnership: nodeify(
|
||||
collectiblesController.addCollectibleVerifyOwnership,
|
||||
collectiblesController,
|
||||
),
|
||||
|
||||
removeAndIgnoreCollectible: nodeify(
|
||||
collectiblesController.removeAndIgnoreCollectible,
|
||||
collectiblesController,
|
||||
),
|
||||
|
||||
removeCollectible: nodeify(
|
||||
collectiblesController.removeCollectible,
|
||||
collectiblesController,
|
||||
),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: nodeify(
|
||||
this.addressBookController.set,
|
||||
@ -1008,9 +1128,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
),
|
||||
createCancelTransaction: nodeify(this.createCancelTransaction, this),
|
||||
createSpeedUpTransaction: nodeify(this.createSpeedUpTransaction, this),
|
||||
isNonceTaken: nodeify(txController.isNonceTaken, txController),
|
||||
estimateGas: nodeify(this.estimateGas, this),
|
||||
getPendingNonce: nodeify(this.getPendingNonce, this),
|
||||
getNextNonce: nodeify(this.getNextNonce, this),
|
||||
addUnapprovedTransaction: nodeify(
|
||||
txController.addUnapprovedTransaction,
|
||||
@ -1094,13 +1212,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
permissionsController.approvePermissionsRequest,
|
||||
permissionsController,
|
||||
),
|
||||
clearPermissions: permissionsController.clearPermissions.bind(
|
||||
permissionsController,
|
||||
),
|
||||
getApprovedAccounts: nodeify(
|
||||
permissionsController.getAccounts,
|
||||
permissionsController,
|
||||
),
|
||||
rejectPermissionsRequest: nodeify(
|
||||
permissionsController.rejectPermissionsRequest,
|
||||
permissionsController,
|
||||
@ -1255,6 +1366,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.detectTokensController.detectNewTokens,
|
||||
this.detectTokensController,
|
||||
),
|
||||
|
||||
// DetectCollectibleController
|
||||
detectCollectibles: process.env.COLLECTIBLES_V1
|
||||
? nodeify(
|
||||
this.collectibleDetectionController.detectCollectibles,
|
||||
this.collectibleDetectionController,
|
||||
)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1574,13 +1693,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
async getKeyringForDevice(deviceName, hdPath = null) {
|
||||
let keyringName = null;
|
||||
switch (deviceName) {
|
||||
case 'trezor':
|
||||
case DEVICE_NAMES.TREZOR:
|
||||
keyringName = TrezorKeyring.type;
|
||||
break;
|
||||
case 'ledger':
|
||||
case DEVICE_NAMES.LEDGER:
|
||||
keyringName = LedgerBridgeKeyring.type;
|
||||
break;
|
||||
case 'lattice':
|
||||
case DEVICE_NAMES.QR:
|
||||
keyringName = QRHardwareKeyring.type;
|
||||
break;
|
||||
case DEVICE_NAMES.LATTICE:
|
||||
keyringName = LatticeKeyring.type;
|
||||
break;
|
||||
default:
|
||||
@ -1597,9 +1719,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
if (hdPath && keyring.setHdPath) {
|
||||
keyring.setHdPath(hdPath);
|
||||
}
|
||||
if (deviceName === 'lattice') {
|
||||
if (deviceName === DEVICE_NAMES.LATTICE) {
|
||||
keyring.appName = 'MetaMask';
|
||||
}
|
||||
if (deviceName === 'trezor') {
|
||||
const model = keyring.getModel();
|
||||
this.appStateController.setTrezorModel(model);
|
||||
}
|
||||
|
||||
keyring.network = this.networkController.getProviderConfig().type;
|
||||
|
||||
return keyring;
|
||||
@ -1667,6 +1794,18 @@ export default class MetamaskController extends EventEmitter {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get hardware account label
|
||||
*
|
||||
* @return string label
|
||||
* */
|
||||
|
||||
getAccountLabel(name, index, hdPathDescription) {
|
||||
return `${name[0].toUpperCase()}${name.slice(1)} ${
|
||||
parseInt(index, 10) + 1
|
||||
} ${hdPathDescription || ''}`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports an account from a Trezor or Ledger device.
|
||||
*
|
||||
@ -1687,10 +1826,12 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.preferencesController.setAddresses(newAccounts);
|
||||
newAccounts.forEach((address) => {
|
||||
if (!oldAccounts.includes(address)) {
|
||||
const label = `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${
|
||||
parseInt(index, 10) + 1
|
||||
} ${hdPathDescription || ''}`.trim();
|
||||
// Set the account label to Trezor 1 / Ledger 1, etc
|
||||
const label = this.getAccountLabel(
|
||||
deviceName === DEVICE_NAMES.QR ? keyring.getName() : deviceName,
|
||||
index,
|
||||
hdPathDescription,
|
||||
);
|
||||
// Set the account label to Trezor 1 / Ledger 1 / QR Hardware 1, etc
|
||||
this.preferencesController.setAccountLabel(address, label);
|
||||
// Select the account
|
||||
this.preferencesController.setSelectedAddress(address);
|
||||
@ -1852,14 +1993,22 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @param {Object} msgParams - The params passed to eth_sign.
|
||||
* @param {Function} cb - The callback function called with the signature.
|
||||
*/
|
||||
newUnsignedMessage(msgParams, req) {
|
||||
const promise = this.messageManager.addUnapprovedMessageAsync(
|
||||
msgParams,
|
||||
req,
|
||||
);
|
||||
this.sendUpdate();
|
||||
this.opts.showUserConfirmation();
|
||||
return promise;
|
||||
async newUnsignedMessage(msgParams, req) {
|
||||
const data = normalizeMsgData(msgParams.data);
|
||||
let promise;
|
||||
// 64 hex + "0x" at the beginning
|
||||
// This is needed because Ethereum's EcSign works only on 32 byte numbers
|
||||
// For 67 length see: https://github.com/MetaMask/metamask-extension/pull/12679/files#r749479607
|
||||
if (data.length === 66 || data.length === 67) {
|
||||
promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req);
|
||||
this.sendUpdate();
|
||||
this.opts.showUserConfirmation();
|
||||
} else {
|
||||
throw ethErrors.rpc.invalidParams(
|
||||
'eth_sign requires 32 byte message hash',
|
||||
);
|
||||
}
|
||||
return await promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1868,24 +2017,23 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @param {Object} msgParams - The params passed to eth_call.
|
||||
* @returns {Promise<Object>} Full state update.
|
||||
*/
|
||||
signMessage(msgParams) {
|
||||
async signMessage(msgParams) {
|
||||
log.info('MetaMaskController - signMessage');
|
||||
const msgId = msgParams.metamaskId;
|
||||
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for signing
|
||||
return this.messageManager
|
||||
.approveMessage(msgParams)
|
||||
.then((cleanMsgParams) => {
|
||||
// signs the message
|
||||
return this.keyringController.signMessage(cleanMsgParams);
|
||||
})
|
||||
.then((rawSig) => {
|
||||
// tells the listener that the message has been signed
|
||||
// and can be returned to the dapp
|
||||
this.messageManager.setMsgStatusSigned(msgId, rawSig);
|
||||
return this.getState();
|
||||
});
|
||||
try {
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for signing
|
||||
const cleanMsgParams = await this.messageManager.approveMessage(
|
||||
msgParams,
|
||||
);
|
||||
const rawSig = await this.keyringController.signMessage(cleanMsgParams);
|
||||
this.messageManager.setMsgStatusSigned(msgId, rawSig);
|
||||
return this.getState();
|
||||
} catch (error) {
|
||||
log.info('MetaMaskController - eth_sign failed', error);
|
||||
this.messageManager.errorMessage(msgId, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1932,23 +2080,27 @@ export default class MetamaskController extends EventEmitter {
|
||||
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @returns {Promise<Object>} A full state update.
|
||||
*/
|
||||
signPersonalMessage(msgParams) {
|
||||
async signPersonalMessage(msgParams) {
|
||||
log.info('MetaMaskController - signPersonalMessage');
|
||||
const msgId = msgParams.metamaskId;
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for signing
|
||||
return this.personalMessageManager
|
||||
.approveMessage(msgParams)
|
||||
.then((cleanMsgParams) => {
|
||||
// signs the message
|
||||
return this.keyringController.signPersonalMessage(cleanMsgParams);
|
||||
})
|
||||
.then((rawSig) => {
|
||||
// tells the listener that the message has been signed
|
||||
// and can be returned to the dapp
|
||||
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig);
|
||||
return this.getState();
|
||||
});
|
||||
try {
|
||||
const cleanMsgParams = await this.personalMessageManager.approveMessage(
|
||||
msgParams,
|
||||
);
|
||||
const rawSig = await this.keyringController.signPersonalMessage(
|
||||
cleanMsgParams,
|
||||
);
|
||||
// tells the listener that the message has been signed
|
||||
// and can be returned to the dapp
|
||||
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig);
|
||||
return this.getState();
|
||||
} catch (error) {
|
||||
log.info('MetaMaskController - eth_personalSign failed', error);
|
||||
this.personalMessageManager.errorMessage(msgId, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2095,6 +2247,12 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
case KEYRING_TYPES.QR: {
|
||||
return Promise.reject(
|
||||
new Error('QR hardware does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(
|
||||
msgParams,
|
||||
@ -2237,7 +2395,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
const address =
|
||||
fromAddress || this.preferencesController.getSelectedAddress();
|
||||
const keyring = await this.keyringController.getKeyringForAccount(address);
|
||||
return keyring.type !== KEYRING_TYPES.TREZOR;
|
||||
if (keyring.type === KEYRING_TYPES.TREZOR) {
|
||||
const model = keyring.getModel();
|
||||
return model === 'T';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
|
@ -607,12 +607,12 @@ describe('MetaMaskController', function () {
|
||||
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress');
|
||||
sinon.spy(metamaskController.preferencesController, 'setAccountLabel');
|
||||
await metamaskController
|
||||
.connectHardware('trezor', 0, `m/44/0'/0'`)
|
||||
.connectHardware('trezor', 0, `m/44'/1'/0'/0`)
|
||||
.catch(() => null);
|
||||
await metamaskController.unlockHardwareWalletAccount(
|
||||
accountToUnlock,
|
||||
'trezor',
|
||||
`m/44/0'/0'`,
|
||||
`m/44'/1'/0'/0`,
|
||||
);
|
||||
});
|
||||
|
||||
@ -835,7 +835,8 @@ describe('MetaMaskController', function () {
|
||||
let msgParams, metamaskMsgs, messages, msgId;
|
||||
|
||||
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
|
||||
const data = '0x43727970746f6b697474696573';
|
||||
const data =
|
||||
'0x0000000000000000000000000000000000000043727970746f6b697474696573';
|
||||
|
||||
beforeEach(async function () {
|
||||
sandbox.stub(metamaskController, 'getBalance');
|
||||
@ -885,6 +886,19 @@ describe('MetaMaskController', function () {
|
||||
assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED);
|
||||
});
|
||||
|
||||
it('checks message length', async function () {
|
||||
msgParams = {
|
||||
from: address,
|
||||
data: '0xDEADBEEF',
|
||||
};
|
||||
|
||||
try {
|
||||
await metamaskController.newUnsignedMessage(msgParams);
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'eth_sign requires 32 byte message hash');
|
||||
}
|
||||
});
|
||||
|
||||
it('errors when signing a message', async function () {
|
||||
try {
|
||||
await metamaskController.signMessage(messages[0].msgParams);
|
||||
|
@ -118,13 +118,14 @@ export default class ExtensionPlatform {
|
||||
) {
|
||||
let extensionURL = extension.runtime.getURL('home.html');
|
||||
|
||||
if (route) {
|
||||
extensionURL += `#${route}`;
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
extensionURL += `?${queryString}`;
|
||||
}
|
||||
|
||||
if (route) {
|
||||
extensionURL += `#${route}`;
|
||||
}
|
||||
this.openTab({ url: extensionURL });
|
||||
if (
|
||||
getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND &&
|
||||
|
@ -41,11 +41,16 @@ class RemoveFencedCodeTransform extends Transform {
|
||||
// stream, immediately before the "end" event is emitted.
|
||||
// It applies the transform to the concatenated file contents.
|
||||
_flush(end) {
|
||||
const [fileContent, didModify] = removeFencedCode(
|
||||
this.filePath,
|
||||
this.buildType,
|
||||
Buffer.concat(this._fileBuffers).toString('utf8'),
|
||||
);
|
||||
let fileContent, didModify;
|
||||
try {
|
||||
[fileContent, didModify] = removeFencedCode(
|
||||
this.filePath,
|
||||
this.buildType,
|
||||
Buffer.concat(this._fileBuffers).toString('utf8'),
|
||||
);
|
||||
} catch (error) {
|
||||
return end(error);
|
||||
}
|
||||
|
||||
const pushAndEnd = () => {
|
||||
this.push(fileContent);
|
||||
@ -53,12 +58,11 @@ class RemoveFencedCodeTransform extends Transform {
|
||||
};
|
||||
|
||||
if (this.shouldLintTransformedFiles && didModify) {
|
||||
lintTransformedFile(fileContent, this.filePath)
|
||||
return lintTransformedFile(fileContent, this.filePath)
|
||||
.then(pushAndEnd)
|
||||
.catch((error) => end(error));
|
||||
} else {
|
||||
pushAndEnd();
|
||||
}
|
||||
return pushAndEnd();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,6 +161,28 @@ describe('build/transforms/remove-fenced-code', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles error during code fence removal or parsing', async () => {
|
||||
const fileContent = getMinimalFencedCode().concat(
|
||||
'///: END:ONLY_INCLUDE_IN',
|
||||
);
|
||||
|
||||
const stream = createRemoveFencedCodeTransform('main')(mockJsFileName);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
stream.on('error', (error) => {
|
||||
expect(error.message).toStrictEqual(
|
||||
expect.stringContaining(
|
||||
'A valid fence consists of two fence lines, but the file contains an uneven number, "3", of fence lines.',
|
||||
),
|
||||
);
|
||||
expect(lintTransformedFileMock).toHaveBeenCalledTimes(0);
|
||||
resolve();
|
||||
});
|
||||
|
||||
stream.end(fileContent);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles transformed file lint failure', async () => {
|
||||
lintTransformedFileMock.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error('lint failure')),
|
||||
|
@ -13,7 +13,7 @@ Follow this instructions: https://github.com/trezor/trezor-core/blob/master/docs
|
||||
|
||||
## 3 - Restart the bridge with emulator support (Mac OSx instructions)
|
||||
|
||||
`
|
||||
```
|
||||
# stop any existing instance of trezord
|
||||
killall trezord
|
||||
|
||||
@ -22,4 +22,4 @@ Follow this instructions: https://github.com/trezor/trezor-core/blob/master/docs
|
||||
|
||||
# launch the emulator
|
||||
~/trezor-core/emu.sh
|
||||
`
|
||||
````
|
||||
|
@ -23,4 +23,8 @@ module.exports = {
|
||||
'<rootDir>/app/scripts/platforms/*.test.js',
|
||||
],
|
||||
testTimeout: 2500,
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
'^.+\\.mdx$': '@storybook/addon-docs/jest-transform-mdx',
|
||||
},
|
||||
};
|
||||
|
16
jest.stories.config.js
Normal file
16
jest.stories.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* eslint-disable import/unambiguous */
|
||||
module.exports = {
|
||||
coverageDirectory: './jest-coverage/storybook',
|
||||
coverageReporters: ['json', 'lcov', 'text', 'clover'],
|
||||
// TODO: enable resetMocks
|
||||
// resetMocks: true,
|
||||
restoreMocks: true,
|
||||
setupFiles: ['<rootDir>/test/setup.js', '<rootDir>/test/env.js'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
|
||||
testMatch: ['<rootDir>/ui/**/*stories.test.js'],
|
||||
testTimeout: 2500,
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
'^.+\\.mdx$': '@storybook/addon-docs/jest-transform-mdx',
|
||||
},
|
||||
};
|
@ -412,6 +412,47 @@
|
||||
"Intl.getCanonicalLocales": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/base-eth-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"@ngraveio/bc-ur": true,
|
||||
"bs58check": true,
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry-eth": {
|
||||
"packages": {
|
||||
"@keystonehq/bc-ur-registry": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/metamask-airgapped-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/base-eth-keyring": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"@metamask/obs-store": true,
|
||||
"buffer": true,
|
||||
"events": true,
|
||||
"rlp": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@material-ui/core": {
|
||||
"globals": {
|
||||
"Image": true,
|
||||
@ -526,6 +567,7 @@
|
||||
"ethjs-util": true,
|
||||
"events": true,
|
||||
"human-standard-collectible-abi": true,
|
||||
"human-standard-multi-collectible-abi": true,
|
||||
"human-standard-token-abi": true,
|
||||
"immer": true,
|
||||
"isomorphic-fetch": true,
|
||||
@ -618,6 +660,18 @@
|
||||
"events": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
"packages": {
|
||||
"@apocentre/alias-sampling": true,
|
||||
"assert": true,
|
||||
"bignumber.js": true,
|
||||
"buffer": true,
|
||||
"cbor-sync": true,
|
||||
"crc": true,
|
||||
"jsbi": true,
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"globals": {
|
||||
"Element": true,
|
||||
@ -737,6 +791,23 @@
|
||||
"util": 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,
|
||||
@ -974,6 +1045,9 @@
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
},
|
||||
"packages": {
|
||||
"browser-resolve": true
|
||||
}
|
||||
@ -1098,6 +1172,20 @@
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"get-intrinsic": true
|
||||
}
|
||||
},
|
||||
"cbor-sync": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"cids": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
@ -1159,7 +1247,7 @@
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"globals": {
|
||||
"clipboardData.setData": true,
|
||||
"clipboardData": true,
|
||||
"console.error": true,
|
||||
"console.warn": true,
|
||||
"document.body.appendChild": true,
|
||||
@ -1190,6 +1278,11 @@
|
||||
"is-buffer": true
|
||||
}
|
||||
},
|
||||
"crc": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"crc-32": {
|
||||
"globals": {
|
||||
"DO_NOT_EXPORT_CRC": true,
|
||||
@ -1489,12 +1582,6 @@
|
||||
"prr": true
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"eth-block-tracker": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1933,6 +2020,18 @@
|
||||
"webkitRTCSessionDescription": true
|
||||
}
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"globals": {
|
||||
"AggregateError": true,
|
||||
"FinalizationRegistry": true,
|
||||
"WeakRef": true
|
||||
},
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"get-params": {
|
||||
"globals": {
|
||||
"GetParams": "write"
|
||||
@ -1975,6 +2074,11 @@
|
||||
"sparse-array": true
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"packages": {
|
||||
"function-bind": true
|
||||
}
|
||||
},
|
||||
"has-binary2": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -2006,6 +2110,7 @@
|
||||
"hdkey": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"bs58check": true,
|
||||
"coinstring": true,
|
||||
"crypto-browserify": true,
|
||||
"safe-buffer": true,
|
||||
@ -2497,6 +2602,7 @@
|
||||
},
|
||||
"is-regex": {
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
@ -2558,6 +2664,11 @@
|
||||
"console.warn": true
|
||||
}
|
||||
},
|
||||
"jsbi": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"json-rpc-engine": {
|
||||
"packages": {
|
||||
"@metamask/safe-event-emitter": true,
|
||||
@ -3768,6 +3879,17 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"qrcode.react": {
|
||||
"globals": {
|
||||
"Path2D": true,
|
||||
"devicePixelRatio": true
|
||||
},
|
||||
"packages": {
|
||||
"prop-types": true,
|
||||
"qr.js": true,
|
||||
"react": true
|
||||
}
|
||||
},
|
||||
"rabin-wasm": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -4074,8 +4196,7 @@
|
||||
"console": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/runtime": true,
|
||||
"symbol-observable": true
|
||||
"@babel/runtime": true
|
||||
}
|
||||
},
|
||||
"redux-devtools-core": {
|
||||
@ -4114,8 +4235,8 @@
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"packages": {
|
||||
"define-properties": true,
|
||||
"es-abstract": true
|
||||
"call-bind": true,
|
||||
"define-properties": true
|
||||
}
|
||||
},
|
||||
"relative-url": {
|
||||
|
@ -412,6 +412,47 @@
|
||||
"Intl.getCanonicalLocales": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/base-eth-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"@ngraveio/bc-ur": true,
|
||||
"bs58check": true,
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry-eth": {
|
||||
"packages": {
|
||||
"@keystonehq/bc-ur-registry": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/metamask-airgapped-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/base-eth-keyring": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"@metamask/obs-store": true,
|
||||
"buffer": true,
|
||||
"events": true,
|
||||
"rlp": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@material-ui/core": {
|
||||
"globals": {
|
||||
"Image": true,
|
||||
@ -526,6 +567,7 @@
|
||||
"ethjs-util": true,
|
||||
"events": true,
|
||||
"human-standard-collectible-abi": true,
|
||||
"human-standard-multi-collectible-abi": true,
|
||||
"human-standard-token-abi": true,
|
||||
"immer": true,
|
||||
"isomorphic-fetch": true,
|
||||
@ -618,6 +660,18 @@
|
||||
"events": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
"packages": {
|
||||
"@apocentre/alias-sampling": true,
|
||||
"assert": true,
|
||||
"bignumber.js": true,
|
||||
"buffer": true,
|
||||
"cbor-sync": true,
|
||||
"crc": true,
|
||||
"jsbi": true,
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"globals": {
|
||||
"Element": true,
|
||||
@ -737,6 +791,23 @@
|
||||
"util": 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,
|
||||
@ -974,6 +1045,9 @@
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
},
|
||||
"packages": {
|
||||
"browser-resolve": true
|
||||
}
|
||||
@ -1098,6 +1172,20 @@
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"get-intrinsic": true
|
||||
}
|
||||
},
|
||||
"cbor-sync": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"cids": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
@ -1159,7 +1247,7 @@
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"globals": {
|
||||
"clipboardData.setData": true,
|
||||
"clipboardData": true,
|
||||
"console.error": true,
|
||||
"console.warn": true,
|
||||
"document.body.appendChild": true,
|
||||
@ -1190,6 +1278,11 @@
|
||||
"is-buffer": true
|
||||
}
|
||||
},
|
||||
"crc": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"crc-32": {
|
||||
"globals": {
|
||||
"DO_NOT_EXPORT_CRC": true,
|
||||
@ -1489,12 +1582,6 @@
|
||||
"prr": true
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"eth-block-tracker": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1933,6 +2020,18 @@
|
||||
"webkitRTCSessionDescription": true
|
||||
}
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"globals": {
|
||||
"AggregateError": true,
|
||||
"FinalizationRegistry": true,
|
||||
"WeakRef": true
|
||||
},
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"get-params": {
|
||||
"globals": {
|
||||
"GetParams": "write"
|
||||
@ -1975,6 +2074,11 @@
|
||||
"sparse-array": true
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"packages": {
|
||||
"function-bind": true
|
||||
}
|
||||
},
|
||||
"has-binary2": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -2006,6 +2110,7 @@
|
||||
"hdkey": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"bs58check": true,
|
||||
"coinstring": true,
|
||||
"crypto-browserify": true,
|
||||
"safe-buffer": true,
|
||||
@ -2497,6 +2602,7 @@
|
||||
},
|
||||
"is-regex": {
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
@ -2558,6 +2664,11 @@
|
||||
"console.warn": true
|
||||
}
|
||||
},
|
||||
"jsbi": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"json-rpc-engine": {
|
||||
"packages": {
|
||||
"@metamask/safe-event-emitter": true,
|
||||
@ -3768,6 +3879,17 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"qrcode.react": {
|
||||
"globals": {
|
||||
"Path2D": true,
|
||||
"devicePixelRatio": true
|
||||
},
|
||||
"packages": {
|
||||
"prop-types": true,
|
||||
"qr.js": true,
|
||||
"react": true
|
||||
}
|
||||
},
|
||||
"rabin-wasm": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -4074,8 +4196,7 @@
|
||||
"console": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/runtime": true,
|
||||
"symbol-observable": true
|
||||
"@babel/runtime": true
|
||||
}
|
||||
},
|
||||
"redux-devtools-core": {
|
||||
@ -4114,8 +4235,8 @@
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"packages": {
|
||||
"define-properties": true,
|
||||
"es-abstract": true
|
||||
"call-bind": true,
|
||||
"define-properties": true
|
||||
}
|
||||
},
|
||||
"relative-url": {
|
||||
|
@ -412,6 +412,47 @@
|
||||
"Intl.getCanonicalLocales": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/base-eth-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"@ngraveio/bc-ur": true,
|
||||
"bs58check": true,
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/bc-ur-registry-eth": {
|
||||
"packages": {
|
||||
"@keystonehq/bc-ur-registry": true,
|
||||
"buffer": true,
|
||||
"ethereumjs-util": true,
|
||||
"hdkey": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@keystonehq/metamask-airgapped-keyring": {
|
||||
"packages": {
|
||||
"@ethereumjs/tx": true,
|
||||
"@keystonehq/base-eth-keyring": true,
|
||||
"@keystonehq/bc-ur-registry-eth": true,
|
||||
"@metamask/obs-store": true,
|
||||
"buffer": true,
|
||||
"events": true,
|
||||
"rlp": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@material-ui/core": {
|
||||
"globals": {
|
||||
"Image": true,
|
||||
@ -526,6 +567,7 @@
|
||||
"ethjs-util": true,
|
||||
"events": true,
|
||||
"human-standard-collectible-abi": true,
|
||||
"human-standard-multi-collectible-abi": true,
|
||||
"human-standard-token-abi": true,
|
||||
"immer": true,
|
||||
"isomorphic-fetch": true,
|
||||
@ -618,6 +660,18 @@
|
||||
"events": true
|
||||
}
|
||||
},
|
||||
"@ngraveio/bc-ur": {
|
||||
"packages": {
|
||||
"@apocentre/alias-sampling": true,
|
||||
"assert": true,
|
||||
"bignumber.js": true,
|
||||
"buffer": true,
|
||||
"cbor-sync": true,
|
||||
"crc": true,
|
||||
"jsbi": true,
|
||||
"sha.js": true
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"globals": {
|
||||
"Element": true,
|
||||
@ -737,6 +791,23 @@
|
||||
"util": 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,
|
||||
@ -974,6 +1045,9 @@
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
},
|
||||
"packages": {
|
||||
"browser-resolve": true
|
||||
}
|
||||
@ -1098,6 +1172,20 @@
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"get-intrinsic": true
|
||||
}
|
||||
},
|
||||
"cbor-sync": {
|
||||
"globals": {
|
||||
"define": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"cids": {
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
@ -1159,7 +1247,7 @@
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"globals": {
|
||||
"clipboardData.setData": true,
|
||||
"clipboardData": true,
|
||||
"console.error": true,
|
||||
"console.warn": true,
|
||||
"document.body.appendChild": true,
|
||||
@ -1190,6 +1278,11 @@
|
||||
"is-buffer": true
|
||||
}
|
||||
},
|
||||
"crc": {
|
||||
"packages": {
|
||||
"buffer": true
|
||||
}
|
||||
},
|
||||
"crc-32": {
|
||||
"globals": {
|
||||
"DO_NOT_EXPORT_CRC": true,
|
||||
@ -1489,12 +1582,6 @@
|
||||
"prr": true
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"eth-block-tracker": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
@ -1933,6 +2020,18 @@
|
||||
"webkitRTCSessionDescription": true
|
||||
}
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"globals": {
|
||||
"AggregateError": true,
|
||||
"FinalizationRegistry": true,
|
||||
"WeakRef": true
|
||||
},
|
||||
"packages": {
|
||||
"function-bind": true,
|
||||
"has": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
"get-params": {
|
||||
"globals": {
|
||||
"GetParams": "write"
|
||||
@ -1975,6 +2074,11 @@
|
||||
"sparse-array": true
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"packages": {
|
||||
"function-bind": true
|
||||
}
|
||||
},
|
||||
"has-binary2": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -2006,6 +2110,7 @@
|
||||
"hdkey": {
|
||||
"packages": {
|
||||
"assert": true,
|
||||
"bs58check": true,
|
||||
"coinstring": true,
|
||||
"crypto-browserify": true,
|
||||
"safe-buffer": true,
|
||||
@ -2497,6 +2602,7 @@
|
||||
},
|
||||
"is-regex": {
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"has-symbols": true
|
||||
}
|
||||
},
|
||||
@ -2558,6 +2664,11 @@
|
||||
"console.warn": true
|
||||
}
|
||||
},
|
||||
"jsbi": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"json-rpc-engine": {
|
||||
"packages": {
|
||||
"@metamask/safe-event-emitter": true,
|
||||
@ -3768,6 +3879,17 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"qrcode.react": {
|
||||
"globals": {
|
||||
"Path2D": true,
|
||||
"devicePixelRatio": true
|
||||
},
|
||||
"packages": {
|
||||
"prop-types": true,
|
||||
"qr.js": true,
|
||||
"react": true
|
||||
}
|
||||
},
|
||||
"rabin-wasm": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
@ -4074,8 +4196,7 @@
|
||||
"console": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/runtime": true,
|
||||
"symbol-observable": true
|
||||
"@babel/runtime": true
|
||||
}
|
||||
},
|
||||
"redux-devtools-core": {
|
||||
@ -4114,8 +4235,8 @@
|
||||
},
|
||||
"regexp.prototype.flags": {
|
||||
"packages": {
|
||||
"define-properties": true,
|
||||
"es-abstract": true
|
||||
"call-bind": true,
|
||||
"define-properties": true
|
||||
}
|
||||
},
|
||||
"relative-url": {
|
||||
|
@ -12,19 +12,23 @@
|
||||
"@babel/core": {
|
||||
"builtin": {
|
||||
"fs": true,
|
||||
"module": true,
|
||||
"path": true,
|
||||
"url": true
|
||||
"url": true,
|
||||
"v8": true
|
||||
},
|
||||
"globals": {
|
||||
"console.log": true,
|
||||
"process.cwd": true,
|
||||
"process.env.BABEL_ENV": true,
|
||||
"process.env.BABEL_SHOW_CONFIG_FOR": true,
|
||||
"process.env.NODE_ENV": true
|
||||
"process.env.NODE_ENV": true,
|
||||
"process.versions.node": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": true,
|
||||
"@babel/generator": true,
|
||||
"@babel/helper-compilation-targets": true,
|
||||
"@babel/helper-module-transforms": true,
|
||||
"@babel/helpers": true,
|
||||
"@babel/parser": true,
|
||||
@ -35,8 +39,6 @@
|
||||
"debug": true,
|
||||
"gensync": true,
|
||||
"json5": true,
|
||||
"lodash": true,
|
||||
"resolve": true,
|
||||
"semver": true,
|
||||
"source-map": true
|
||||
}
|
||||
@ -76,22 +78,9 @@
|
||||
"@babel/types": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-builder-react-jsx": {
|
||||
"packages": {
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/types": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-builder-react-jsx-experimental": {
|
||||
"packages": {
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/helper-module-imports": true,
|
||||
"@babel/types": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"globals": {
|
||||
"console.log": true,
|
||||
"console.warn": true,
|
||||
"process.versions.node": true
|
||||
},
|
||||
"packages": {
|
||||
@ -107,6 +96,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/helper-function-name": true,
|
||||
"@babel/helper-member-expression-to-functions": true,
|
||||
"@babel/helper-optimise-call-expression": true,
|
||||
@ -121,11 +111,22 @@
|
||||
"regexpu-core": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-define-map": {
|
||||
"@babel/helper-define-polyfill-provider": {
|
||||
"builtin": {
|
||||
"path": true
|
||||
},
|
||||
"globals": {
|
||||
"console.log": true,
|
||||
"console.warn": true,
|
||||
"process.exitCode": "write",
|
||||
"process.versions.node": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/helper-function-name": true,
|
||||
"@babel/types": true,
|
||||
"lodash": true
|
||||
"@babel/core": true,
|
||||
"@babel/helper-compilation-targets": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"lodash.debounce": true,
|
||||
"resolve": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-explode-assignable-expression": {
|
||||
@ -177,8 +178,7 @@
|
||||
"@babel/helper-validator-identifier": true,
|
||||
"@babel/template": true,
|
||||
"@babel/traverse": true,
|
||||
"@babel/types": true,
|
||||
"lodash": true
|
||||
"@babel/types": true
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@ -237,6 +237,14 @@
|
||||
"js-tokens": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/helper-skip-transparent-expression-wrappers": true,
|
||||
"@babel/plugin-proposal-optional-chaining": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-async-generator-functions": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
@ -251,6 +259,13 @@
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-class-static-block": {
|
||||
"packages": {
|
||||
"@babel/helper-create-class-features-plugin": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/plugin-syntax-class-static-block": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-dynamic-import": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true,
|
||||
@ -292,7 +307,9 @@
|
||||
},
|
||||
"@babel/plugin-proposal-object-rest-spread": {
|
||||
"packages": {
|
||||
"@babel/compat-data": true,
|
||||
"@babel/core": true,
|
||||
"@babel/helper-compilation-targets": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/plugin-syntax-object-rest-spread": true,
|
||||
"@babel/plugin-transform-parameters": true
|
||||
@ -318,6 +335,14 @@
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-private-property-in-object": {
|
||||
"packages": {
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/helper-create-class-features-plugin": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/plugin-syntax-private-property-in-object": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-unicode-property-regex": {
|
||||
"packages": {
|
||||
"@babel/helper-create-regexp-features-plugin": true,
|
||||
@ -334,6 +359,11 @@
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-class-static-block": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-dynamic-import": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true
|
||||
@ -384,6 +414,11 @@
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-private-property-in-object": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-top-level-await": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true
|
||||
@ -418,7 +453,6 @@
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/helper-define-map": true,
|
||||
"@babel/helper-function-name": true,
|
||||
"@babel/helper-optimise-call-expression": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
@ -554,7 +588,9 @@
|
||||
},
|
||||
"@babel/plugin-transform-react-display-name": {
|
||||
"builtin": {
|
||||
"path": true
|
||||
"path.basename": true,
|
||||
"path.dirname": true,
|
||||
"path.extname": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
@ -564,30 +600,15 @@
|
||||
"@babel/plugin-transform-react-jsx": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-builder-react-jsx": true,
|
||||
"@babel/helper-builder-react-jsx-experimental": true,
|
||||
"@babel/helper-annotate-as-pure": true,
|
||||
"@babel/helper-module-imports": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/plugin-syntax-jsx": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-react-jsx-development": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-builder-react-jsx-experimental": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/plugin-syntax-jsx": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-react-jsx-self": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-plugin-utils": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-react-jsx-source": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-plugin-utils": true
|
||||
"@babel/plugin-transform-react-jsx": true
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-react-pure-annotations": {
|
||||
@ -673,11 +694,12 @@
|
||||
"packages": {
|
||||
"@babel/compat-data": true,
|
||||
"@babel/helper-compilation-targets": true,
|
||||
"@babel/helper-module-imports": true,
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/helper-validator-option": true,
|
||||
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": true,
|
||||
"@babel/plugin-proposal-async-generator-functions": true,
|
||||
"@babel/plugin-proposal-class-properties": true,
|
||||
"@babel/plugin-proposal-class-static-block": true,
|
||||
"@babel/plugin-proposal-dynamic-import": true,
|
||||
"@babel/plugin-proposal-export-namespace-from": true,
|
||||
"@babel/plugin-proposal-json-strings": true,
|
||||
@ -688,9 +710,11 @@
|
||||
"@babel/plugin-proposal-optional-catch-binding": true,
|
||||
"@babel/plugin-proposal-optional-chaining": true,
|
||||
"@babel/plugin-proposal-private-methods": true,
|
||||
"@babel/plugin-proposal-private-property-in-object": true,
|
||||
"@babel/plugin-proposal-unicode-property-regex": true,
|
||||
"@babel/plugin-syntax-async-generators": true,
|
||||
"@babel/plugin-syntax-class-properties": true,
|
||||
"@babel/plugin-syntax-class-static-block": true,
|
||||
"@babel/plugin-syntax-dynamic-import": true,
|
||||
"@babel/plugin-syntax-export-namespace-from": true,
|
||||
"@babel/plugin-syntax-json-strings": true,
|
||||
@ -700,6 +724,7 @@
|
||||
"@babel/plugin-syntax-object-rest-spread": true,
|
||||
"@babel/plugin-syntax-optional-catch-binding": true,
|
||||
"@babel/plugin-syntax-optional-chaining": true,
|
||||
"@babel/plugin-syntax-private-property-in-object": true,
|
||||
"@babel/plugin-syntax-top-level-await": true,
|
||||
"@babel/plugin-transform-arrow-functions": true,
|
||||
"@babel/plugin-transform-async-to-generator": true,
|
||||
@ -735,6 +760,9 @@
|
||||
"@babel/plugin-transform-unicode-regex": true,
|
||||
"@babel/preset-modules": true,
|
||||
"@babel/types": true,
|
||||
"babel-plugin-polyfill-corejs2": true,
|
||||
"babel-plugin-polyfill-corejs3": true,
|
||||
"babel-plugin-polyfill-regenerator": true,
|
||||
"core-js-compat": true,
|
||||
"semver": true
|
||||
}
|
||||
@ -742,11 +770,10 @@
|
||||
"@babel/preset-react": {
|
||||
"packages": {
|
||||
"@babel/helper-plugin-utils": true,
|
||||
"@babel/helper-validator-option": true,
|
||||
"@babel/plugin-transform-react-display-name": true,
|
||||
"@babel/plugin-transform-react-jsx": true,
|
||||
"@babel/plugin-transform-react-jsx-development": true,
|
||||
"@babel/plugin-transform-react-jsx-self": true,
|
||||
"@babel/plugin-transform-react-jsx-source": true,
|
||||
"@babel/plugin-transform-react-pure-annotations": true
|
||||
}
|
||||
},
|
||||
@ -760,13 +787,13 @@
|
||||
"@babel/traverse": {
|
||||
"globals": {
|
||||
"console.log": true,
|
||||
"console.trace": true,
|
||||
"process.env.NODE_ENV": true
|
||||
"console.trace": true
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": true,
|
||||
"@babel/generator": true,
|
||||
"@babel/helper-function-name": true,
|
||||
"@babel/helper-hoist-variables": true,
|
||||
"@babel/helper-split-export-declaration": true,
|
||||
"@babel/parser": true,
|
||||
"@babel/types": true,
|
||||
@ -781,7 +808,6 @@
|
||||
},
|
||||
"packages": {
|
||||
"@babel/helper-validator-identifier": true,
|
||||
"lodash": true,
|
||||
"to-fast-properties": true
|
||||
}
|
||||
},
|
||||
@ -945,11 +971,6 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"acorn-dynamic-import": {
|
||||
"packages": {
|
||||
"acorn": true
|
||||
}
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"packages": {
|
||||
"acorn": true
|
||||
@ -958,7 +979,6 @@
|
||||
"acorn-node": {
|
||||
"packages": {
|
||||
"acorn": true,
|
||||
"acorn-dynamic-import": true,
|
||||
"acorn-walk": true,
|
||||
"xtend": true
|
||||
}
|
||||
@ -1117,13 +1137,33 @@
|
||||
"packages": {
|
||||
"browserslist": true,
|
||||
"caniuse-lite": true,
|
||||
"colorette": true,
|
||||
"normalize-range": true,
|
||||
"num2fraction": true,
|
||||
"picocolors": true,
|
||||
"postcss": true,
|
||||
"postcss-value-parser": true
|
||||
}
|
||||
},
|
||||
"babel-plugin-polyfill-corejs2": {
|
||||
"packages": {
|
||||
"@babel/compat-data": true,
|
||||
"@babel/core": true,
|
||||
"@babel/helper-define-polyfill-provider": true,
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"babel-plugin-polyfill-corejs3": {
|
||||
"packages": {
|
||||
"@babel/core": true,
|
||||
"@babel/helper-define-polyfill-provider": true,
|
||||
"core-js-compat": true
|
||||
}
|
||||
},
|
||||
"babel-plugin-polyfill-regenerator": {
|
||||
"packages": {
|
||||
"@babel/helper-define-polyfill-provider": true
|
||||
}
|
||||
},
|
||||
"babelify": {
|
||||
"builtin": {
|
||||
"path.extname": true,
|
||||
@ -1675,7 +1715,7 @@
|
||||
},
|
||||
"deps-sort": {
|
||||
"packages": {
|
||||
"shasum": true,
|
||||
"shasum-object": true,
|
||||
"through2": true
|
||||
}
|
||||
},
|
||||
@ -1773,15 +1813,9 @@
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"globals": {
|
||||
"AggregateError": true,
|
||||
"FinalizationRegistry": true,
|
||||
"WeakRef": true
|
||||
},
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"es-to-primitive": true,
|
||||
"function-bind": true,
|
||||
"get-intrinsic": true,
|
||||
"has": true,
|
||||
"has-symbols": true,
|
||||
@ -3069,7 +3103,6 @@
|
||||
"labeled-stream-splicer": {
|
||||
"packages": {
|
||||
"inherits": true,
|
||||
"isarray": true,
|
||||
"stream-splicer": true
|
||||
}
|
||||
},
|
||||
@ -3196,6 +3229,12 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"globals": {
|
||||
"clearTimeout": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"log-symbols": {
|
||||
"globals": {
|
||||
"process.env.CI": true,
|
||||
@ -3489,7 +3528,6 @@
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"define-properties": true,
|
||||
"es-abstract": true,
|
||||
"has-symbols": true,
|
||||
"object-keys": true
|
||||
}
|
||||
@ -3534,14 +3572,6 @@
|
||||
"make-iterator": true
|
||||
}
|
||||
},
|
||||
"object.values": {
|
||||
"packages": {
|
||||
"call-bind": true,
|
||||
"define-properties": true,
|
||||
"es-abstract": true,
|
||||
"has": true
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"packages": {
|
||||
"wrappy": true
|
||||
@ -3670,6 +3700,16 @@
|
||||
"through": true
|
||||
}
|
||||
},
|
||||
"picocolors": {
|
||||
"builtin": {
|
||||
"tty.isatty": true
|
||||
},
|
||||
"globals": {
|
||||
"process.argv.includes": true,
|
||||
"process.env": true,
|
||||
"process.platform": true
|
||||
}
|
||||
},
|
||||
"picomatch": {
|
||||
"builtin": {
|
||||
"path.basename": true,
|
||||
@ -3727,6 +3767,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"chalk": true,
|
||||
"picocolors": true,
|
||||
"source-map": true,
|
||||
"supports-color": true
|
||||
}
|
||||
@ -3767,13 +3808,9 @@
|
||||
}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
"builtin": {
|
||||
"util.deprecate": true
|
||||
},
|
||||
"packages": {
|
||||
"cssesc": true,
|
||||
"indexes-of": true,
|
||||
"uniq": true
|
||||
"util-deprecate": true
|
||||
}
|
||||
},
|
||||
"postcss-syntax": {
|
||||
@ -3861,6 +3898,11 @@
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"packages": {
|
||||
"side-channel": true
|
||||
}
|
||||
},
|
||||
"quote-stream": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
@ -4272,6 +4314,17 @@
|
||||
"json-stable-stringify": true
|
||||
}
|
||||
},
|
||||
"shasum-object": {
|
||||
"builtin": {
|
||||
"crypto.createHash": true
|
||||
},
|
||||
"globals": {
|
||||
"Buffer.isBuffer": true
|
||||
},
|
||||
"packages": {
|
||||
"fast-safe-stringify": true
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"packages": {
|
||||
"shebang-regex": true
|
||||
@ -5200,8 +5253,8 @@
|
||||
"yaml": {
|
||||
"globals": {
|
||||
"Buffer": true,
|
||||
"_YAML_SILENCE_DEPRECATION_WARNINGS": true,
|
||||
"_YAML_SILENCE_WARNINGS": true,
|
||||
"YAML_SILENCE_DEPRECATION_WARNINGS": true,
|
||||
"YAML_SILENCE_WARNINGS": true,
|
||||
"atob": true,
|
||||
"btoa": true,
|
||||
"console.warn": true,
|
||||
|
15
package.json
15
package.json
@ -60,6 +60,7 @@
|
||||
"start:dev": "concurrently -k -n build,react,redux yarn:start yarn:devtools:react yarn:devtools:redux",
|
||||
"announce": "node development/announcer.js",
|
||||
"storybook": "start-storybook -p 6006 -c .storybook -s ./app,./.storybook/images",
|
||||
"storybook:test": "jest --config=./jest.stories.config.js",
|
||||
"storybook:build": "build-storybook -c .storybook -o storybook-build -s ./app,./.storybook/images",
|
||||
"storybook:deploy": "storybook-to-ghpages --existing-output-dir storybook-build --remote storybook --branch master",
|
||||
"update-changelog": "auto-changelog update",
|
||||
@ -106,9 +107,11 @@
|
||||
"@ethereumjs/tx": "^3.2.1",
|
||||
"@formatjs/intl-relativetimeformat": "^5.2.6",
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@keystonehq/bc-ur-registry-eth": "^0.6.8",
|
||||
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@metamask/contract-metadata": "^1.28.0",
|
||||
"@metamask/controllers": "^17.0.0",
|
||||
"@metamask/controllers": "^20.1.0",
|
||||
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
|
||||
"@metamask/eth-token-tracker": "^3.0.1",
|
||||
"@metamask/etherscan-link": "^2.1.0",
|
||||
@ -117,11 +120,13 @@
|
||||
"@metamask/obs-store": "^5.0.0",
|
||||
"@metamask/post-message-stream": "^4.0.0",
|
||||
"@metamask/providers": "^8.1.1",
|
||||
"@ngraveio/bc-ur": "^1.1.6",
|
||||
"@popperjs/core": "^2.4.0",
|
||||
"@reduxjs/toolkit": "^1.6.2",
|
||||
"@sentry/browser": "^6.0.0",
|
||||
"@sentry/integrations": "^6.0.0",
|
||||
"@zxing/library": "^0.8.0",
|
||||
"@zxing/browser": "^0.0.10",
|
||||
"@zxing/library": "0.8.0",
|
||||
"analytics-node": "^3.4.0-beta.3",
|
||||
"await-semaphore": "^0.1.1",
|
||||
"base32-encode": "^1.2.0",
|
||||
@ -145,7 +150,7 @@
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-rpc-errors": "^4.0.2",
|
||||
"eth-sig-util": "^3.0.0",
|
||||
"eth-trezor-keyring": "^0.7.0",
|
||||
"eth-trezor-keyring": "^0.8.0",
|
||||
"ethereum-ens-network-map": "^1.0.2",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-util": "^7.0.10",
|
||||
@ -181,6 +186,7 @@
|
||||
"pump": "^3.0.0",
|
||||
"punycode": "^2.1.1",
|
||||
"qrcode-generator": "1.4.1",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^16.12.0",
|
||||
"react-dnd": "^3.0.2",
|
||||
"react-dnd-html5-backend": "^7.4.4",
|
||||
@ -206,6 +212,7 @@
|
||||
"swappable-obj-proxy": "^1.1.0",
|
||||
"textarea-caret": "^3.0.1",
|
||||
"unicode-confusables": "^0.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"valid-url": "^1.0.9",
|
||||
"web3": "^0.20.7",
|
||||
"web3-stream-provider": "^4.0.0"
|
||||
@ -299,7 +306,7 @@
|
||||
"jest": "^26.6.3",
|
||||
"jsdom": "^11.2.0",
|
||||
"koa": "^2.7.0",
|
||||
"lavamoat": "^5.3.4",
|
||||
"lavamoat": "^5.3.5",
|
||||
"lavamoat-browserify": "^14.0.3",
|
||||
"lavamoat-viz": "^6.0.9",
|
||||
"lockfile-lint": "^4.0.0",
|
||||
|
62
patches/@babel+runtime+7.15.4.patch
Normal file
62
patches/@babel+runtime+7.15.4.patch
Normal file
@ -0,0 +1,62 @@
|
||||
diff --git a/node_modules/@babel/runtime/helpers/extends.js b/node_modules/@babel/runtime/helpers/extends.js
|
||||
index eaf9547..d0474f5 100644
|
||||
--- a/node_modules/@babel/runtime/helpers/extends.js
|
||||
+++ b/node_modules/@babel/runtime/helpers/extends.js
|
||||
@@ -1,20 +1,5 @@
|
||||
function _extends() {
|
||||
- module.exports = _extends = Object.assign || function (target) {
|
||||
- for (var i = 1; i < arguments.length; i++) {
|
||||
- var source = arguments[i];
|
||||
-
|
||||
- for (var key in source) {
|
||||
- if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
- target[key] = source[key];
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- return target;
|
||||
- };
|
||||
-
|
||||
- module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
- return _extends.apply(this, arguments);
|
||||
+ return Object.assign(...arguments)
|
||||
}
|
||||
|
||||
module.exports = _extends;
|
||||
diff --git a/node_modules/@babel/runtime/helpers/getPrototypeOf.js b/node_modules/@babel/runtime/helpers/getPrototypeOf.js
|
||||
index a6916eb..e01b2d6 100644
|
||||
--- a/node_modules/@babel/runtime/helpers/getPrototypeOf.js
|
||||
+++ b/node_modules/@babel/runtime/helpers/getPrototypeOf.js
|
||||
@@ -1,9 +1,5 @@
|
||||
function _getPrototypeOf(o) {
|
||||
- module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
||||
- return o.__proto__ || Object.getPrototypeOf(o);
|
||||
- };
|
||||
- module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
- return _getPrototypeOf(o);
|
||||
+ return Object.getPrototypeOf(o);
|
||||
}
|
||||
|
||||
module.exports = _getPrototypeOf;
|
||||
diff --git a/node_modules/@babel/runtime/helpers/setPrototypeOf.js b/node_modules/@babel/runtime/helpers/setPrototypeOf.js
|
||||
index 415797b..63312f2 100644
|
||||
--- a/node_modules/@babel/runtime/helpers/setPrototypeOf.js
|
||||
+++ b/node_modules/@babel/runtime/helpers/setPrototypeOf.js
|
||||
@@ -1,12 +1,7 @@
|
||||
function _setPrototypeOf(o, p) {
|
||||
- module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||
- o.__proto__ = p;
|
||||
- return o;
|
||||
- };
|
||||
-
|
||||
- module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
- return _setPrototypeOf(o, p);
|
||||
-}
|
||||
+ o.__proto__ = p;
|
||||
+ return o;
|
||||
+};
|
||||
|
||||
module.exports = _setPrototypeOf;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
\ No newline at end of file
|
@ -1,17 +1,18 @@
|
||||
diff --git a/node_modules/object.values/index.js b/node_modules/object.values/index.js
|
||||
index b8ba091..2dc8083 100644
|
||||
index abf0449..2dc8083 100644
|
||||
--- a/node_modules/object.values/index.js
|
||||
+++ b/node_modules/object.values/index.js
|
||||
@@ -1,17 +1,3 @@
|
||||
@@ -1,18 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
-var define = require('define-properties');
|
||||
-var callBind = require('call-bind');
|
||||
-
|
||||
-var implementation = require('./implementation');
|
||||
-var getPolyfill = require('./polyfill');
|
||||
-var shim = require('./shim');
|
||||
-
|
||||
-var polyfill = getPolyfill();
|
||||
-var polyfill = callBind(getPolyfill(), Object);
|
||||
-
|
||||
-define(polyfill, {
|
||||
- getPolyfill: getPolyfill,
|
@ -1,25 +1,29 @@
|
||||
diff --git a/node_modules/sass/sass.dart.js b/node_modules/sass/sass.dart.js
|
||||
index fedd867..fef6a8f 100644
|
||||
index 512d612..1374f5e 100644
|
||||
--- a/node_modules/sass/sass.dart.js
|
||||
+++ b/node_modules/sass/sass.dart.js
|
||||
@@ -16,6 +16,9 @@ self.scheduleImmediate = self.setImmediate
|
||||
@@ -16,6 +16,10 @@ self.scheduleImmediate = typeof setImmediate !== "undefined"
|
||||
// CommonJS globals.
|
||||
self.exports = exports;
|
||||
|
||||
+// realm bridge utility functions
|
||||
+exports.bridgeJson = (target) => JSON.parse(JSON.stringify(target))
|
||||
+exports.bridgeFn = (target) => ((...args) => target(...args))
|
||||
+
|
||||
// Node.js specific exports, check to see if they exist & or polyfilled
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
@@ -3616,10 +3619,6 @@ self.fs = require("fs");
|
||||
@@ -3700,13 +3704,6 @@ self.fs = require("fs");
|
||||
return C.PlainJavaScriptObject_methods;
|
||||
if (proto === Object.prototype)
|
||||
return C.PlainJavaScriptObject_methods;
|
||||
- if (typeof $constructor == "function") {
|
||||
- Object.defineProperty($constructor, J.JS_INTEROP_INTERCEPTOR_TAG(), {value: C.UnknownJavaScriptObject_methods, enumerable: false, writable: true, configurable: true});
|
||||
- t1 = $._JS_INTEROP_INTERCEPTOR_TAG;
|
||||
- if (t1 == null)
|
||||
- t1 = $._JS_INTEROP_INTERCEPTOR_TAG = init.getIsolateTag("_$dart_js");
|
||||
- Object.defineProperty($constructor, t1, {value: C.UnknownJavaScriptObject_methods, enumerable: false, writable: true, configurable: true});
|
||||
- return C.UnknownJavaScriptObject_methods;
|
||||
- }
|
||||
return C.UnknownJavaScriptObject_methods;
|
||||
},
|
||||
JS_INTEROP_INTERCEPTOR_TAG: function() {
|
||||
JSArray_JSArray$fixed: function($length, $E) {
|
@ -30,6 +30,17 @@ export const GAS_RECOMMENDATIONS = {
|
||||
HIGH: 'high',
|
||||
};
|
||||
|
||||
/**
|
||||
* These represent types of gas estimation
|
||||
*/
|
||||
export const PRIORITY_LEVELS = {
|
||||
LOW: 'low',
|
||||
MEDIUM: 'medium',
|
||||
HIGH: 'high',
|
||||
CUSTOM: 'custom',
|
||||
DAPP_SUGGESTED: 'dappSuggested',
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the user customizing their gas preference
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Accounts can be instantiated from simple, HD or the two hardware wallet
|
||||
* Accounts can be instantiated from simple, HD or the multiple hardware wallet
|
||||
* keyring types. Both simple and HD are treated as default but we do special
|
||||
* case accounts managed by a hardware wallet.
|
||||
*/
|
||||
@ -7,6 +7,14 @@ export const KEYRING_TYPES = {
|
||||
LEDGER: 'Ledger Hardware',
|
||||
TREZOR: 'Trezor Hardware',
|
||||
LATTICE: 'Lattice Hardware',
|
||||
QR: 'QR Hardware Wallet Device',
|
||||
};
|
||||
|
||||
export const DEVICE_NAMES = {
|
||||
LEDGER: 'ledger',
|
||||
TREZOR: 'trezor',
|
||||
QR: 'QR Hardware',
|
||||
LATTICE: 'lattice',
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -84,12 +84,7 @@ export const WBNB_CONTRACT_ADDRESS =
|
||||
export const WMATIC_CONTRACT_ADDRESS =
|
||||
'0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270';
|
||||
|
||||
const METASWAP_ETH_API_HOST = 'https://api.metaswap.codefi.network';
|
||||
|
||||
const METASWAP_BSC_API_HOST = 'https://bsc-api.metaswap.codefi.network';
|
||||
|
||||
const SWAPS_TESTNET_CHAIN_ID = '0x539';
|
||||
const SWAPS_TESTNET_HOST = 'https://metaswap-api.airswap-dev.codefi.network';
|
||||
|
||||
export const SWAPS_API_V2_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
export const SWAPS_DEV_API_V2_BASE_URL =
|
||||
@ -111,14 +106,6 @@ export const ALLOWED_SWAPS_CHAIN_IDS = {
|
||||
[RINKEBY_CHAIN_ID]: true,
|
||||
};
|
||||
|
||||
// This is mapping for v1 URLs and will be removed once we migrate to v2.
|
||||
export const METASWAP_CHAINID_API_HOST_MAP = {
|
||||
[MAINNET_CHAIN_ID]: METASWAP_ETH_API_HOST,
|
||||
[SWAPS_TESTNET_CHAIN_ID]: `${SWAPS_API_V2_BASE_URL}/networks/1`,
|
||||
[BSC_CHAIN_ID]: METASWAP_BSC_API_HOST,
|
||||
[RINKEBY_CHAIN_ID]: SWAPS_TESTNET_HOST,
|
||||
};
|
||||
|
||||
export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
|
||||
[MAINNET_CHAIN_ID]: MAINNET_CONTRACT_ADDRESS,
|
||||
[SWAPS_TESTNET_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS,
|
||||
|
@ -229,6 +229,21 @@ const multiplyCurrencies = (a, b, options = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const divideCurrencies = (a, b, options = {}) => {
|
||||
const { dividendBase, divisorBase, ...conversionOptions } = options;
|
||||
|
||||
if (!isValidBase(dividendBase) || !isValidBase(divisorBase)) {
|
||||
throw new Error('Must specify valid dividendBase and divisorBase');
|
||||
}
|
||||
|
||||
const value = getBigNumber(a, dividendBase).div(getBigNumber(b, divisorBase));
|
||||
|
||||
return converter({
|
||||
value,
|
||||
...conversionOptions,
|
||||
});
|
||||
};
|
||||
|
||||
const conversionGreaterThan = ({ ...firstProps }, { ...secondProps }) => {
|
||||
const firstValue = converter({ ...firstProps });
|
||||
const secondValue = converter({ ...secondProps });
|
||||
@ -291,4 +306,5 @@ export {
|
||||
decGWEIToHexWEI,
|
||||
toBigNumber,
|
||||
toNormalizedDenomination,
|
||||
divideCurrencies,
|
||||
};
|
||||
|
@ -1,5 +1,9 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { addCurrencies, conversionUtil } from './conversion.utils';
|
||||
import {
|
||||
addCurrencies,
|
||||
conversionUtil,
|
||||
divideCurrencies,
|
||||
} from './conversion.utils';
|
||||
|
||||
describe('conversion utils', () => {
|
||||
describe('addCurrencies()', () => {
|
||||
@ -163,4 +167,39 @@ describe('conversion utils', () => {
|
||||
).toStrictEqual('1.5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('divideCurrencies()', () => {
|
||||
it('should correctly divide decimal values', () => {
|
||||
const result = divideCurrencies(9, 3, {
|
||||
dividendBase: 10,
|
||||
divisorBase: 10,
|
||||
});
|
||||
expect(result.toNumber()).toStrictEqual(3);
|
||||
});
|
||||
|
||||
it('should correctly divide hexadecimal values', () => {
|
||||
const result = divideCurrencies(1000, 0xa, {
|
||||
dividendBase: 16,
|
||||
divisorBase: 16,
|
||||
});
|
||||
expect(result.toNumber()).toStrictEqual(0x100);
|
||||
});
|
||||
|
||||
it('should correctly divide hexadecimal value from decimal value', () => {
|
||||
const result = divideCurrencies(0x3e8, 0xa, {
|
||||
dividendBase: 16,
|
||||
divisorBase: 16,
|
||||
});
|
||||
expect(result.toNumber()).toStrictEqual(0x100);
|
||||
});
|
||||
|
||||
it('should throw error for wrong base value', () => {
|
||||
expect(() => {
|
||||
divideCurrencies(0x3e8, 0xa, {
|
||||
dividendBase: 10.5,
|
||||
divisorBase: 7,
|
||||
});
|
||||
}).toThrow('Must specify valid dividendBase and divisorBase');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,8 +2,8 @@ import nock from 'nock';
|
||||
import { MILLISECOND, SECOND } from '../constants/time';
|
||||
import getFetchWithTimeout from './fetch-with-timeout';
|
||||
|
||||
describe('getFetchWithTimeout', function () {
|
||||
it('fetches a url', async function () {
|
||||
describe('getFetchWithTimeout', () => {
|
||||
it('fetches a url', async () => {
|
||||
nock('https://api.infura.io').get('/money').reply(200, '{"hodl": false}');
|
||||
|
||||
const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
|
||||
@ -15,7 +15,7 @@ describe('getFetchWithTimeout', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when the request hits a custom timeout', async function () {
|
||||
it('throws when the request hits a custom timeout', async () => {
|
||||
nock('https://api.infura.io')
|
||||
.get('/moon')
|
||||
.delay(SECOND * 2)
|
||||
@ -23,19 +23,14 @@ describe('getFetchWithTimeout', function () {
|
||||
|
||||
const fetchWithTimeout = getFetchWithTimeout(MILLISECOND * 123);
|
||||
|
||||
const fetchWithTimeoutThrowsError = async () => {
|
||||
await expect(async () => {
|
||||
await fetchWithTimeout('https://api.infura.io/moon').then((r) =>
|
||||
r.json(),
|
||||
);
|
||||
throw new Error('Request should throw');
|
||||
};
|
||||
|
||||
await expect(fetchWithTimeoutThrowsError()).rejects.toThrow(
|
||||
'The user aborted a request.',
|
||||
);
|
||||
}).rejects.toThrow('The user aborted a request.');
|
||||
});
|
||||
|
||||
it('should abort the request when the custom timeout is hit', async function () {
|
||||
it('should abort the request when the custom timeout is hit', async () => {
|
||||
nock('https://api.infura.io')
|
||||
.get('/moon')
|
||||
.delay(SECOND * 2)
|
||||
@ -43,29 +38,24 @@ describe('getFetchWithTimeout', function () {
|
||||
|
||||
const fetchWithTimeout = getFetchWithTimeout(MILLISECOND * 123);
|
||||
|
||||
const fetchWithTimeoutThrowsError = async () => {
|
||||
await expect(async () => {
|
||||
await fetchWithTimeout('https://api.infura.io/moon').then((r) =>
|
||||
r.json(),
|
||||
);
|
||||
throw new Error('Request should be aborted');
|
||||
};
|
||||
|
||||
await expect(fetchWithTimeoutThrowsError()).rejects.toThrow(
|
||||
'The user aborted a request.',
|
||||
);
|
||||
}).rejects.toThrow('The user aborted a request.');
|
||||
});
|
||||
|
||||
it('throws on invalid timeout', async function () {
|
||||
expect(() => getFetchWithTimeout()).toThrow(
|
||||
it('throws on invalid timeout', async () => {
|
||||
await expect(() => getFetchWithTimeout()).toThrow(
|
||||
'Must specify positive integer timeout.',
|
||||
);
|
||||
expect(() => getFetchWithTimeout(-1)).toThrow(
|
||||
await expect(() => getFetchWithTimeout(-1)).toThrow(
|
||||
'Must specify positive integer timeout.',
|
||||
);
|
||||
expect(() => getFetchWithTimeout({})).toThrow(
|
||||
await expect(() => getFetchWithTimeout({})).toThrow(
|
||||
'Must specify positive integer timeout.',
|
||||
);
|
||||
expect(() => getFetchWithTimeout(true)).toThrow(
|
||||
await expect(() => getFetchWithTimeout(true)).toThrow(
|
||||
'Must specify positive integer timeout.',
|
||||
);
|
||||
});
|
||||
|
@ -6,51 +6,51 @@ describe('hexstring utils', function () {
|
||||
it('should allow 40-char non-prefixed hex', function () {
|
||||
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825';
|
||||
const result = isValidHexAddress(address);
|
||||
expect(result).toBe(true);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should allow 42-char prefixed hex', function () {
|
||||
const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825';
|
||||
const result = isValidHexAddress(address);
|
||||
expect(result).toBe(true);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should NOT allow 40-char non-prefixed hex when allowNonPrefixed is false', function () {
|
||||
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825';
|
||||
const result = isValidHexAddress(address, { allowNonPrefixed: false });
|
||||
expect(result).toBe(false);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('should NOT allow any length of non hex-prefixed string', function () {
|
||||
const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85';
|
||||
const result = isValidHexAddress(address);
|
||||
expect(result).toBe(false);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('should NOT allow less than 42 character hex-prefixed string', function () {
|
||||
const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85';
|
||||
const result = isValidHexAddress(address);
|
||||
expect(result).toBe(false);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('should recognize correct capitalized checksum', function () {
|
||||
const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825';
|
||||
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
|
||||
expect(result).toBe(true);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should recognize incorrect capitalized checksum', function () {
|
||||
const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825';
|
||||
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
|
||||
expect(result).toBe(false);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('should recognize this sample hashed address', function () {
|
||||
const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0';
|
||||
const result = isValidHexAddress(address, { mixedCaseUseChecksum: true });
|
||||
const hashed = toChecksumAddress(address.toLowerCase());
|
||||
expect(hashed).toBe(address);
|
||||
expect(result).toBe(true);
|
||||
expect(hashed).toStrictEqual(address);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
27
test/data/mock-estimates.json
Normal file
27
test/data/mock-estimates.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"fee-market": {
|
||||
"gasEstimateType": "fee-market",
|
||||
"gasFeeEstimates": {
|
||||
"low": {
|
||||
"minWaitTimeEstimate": 180000,
|
||||
"maxWaitTimeEstimate": 300000,
|
||||
"suggestedMaxPriorityFeePerGas": "3",
|
||||
"suggestedMaxFeePerGas": "53"
|
||||
},
|
||||
"medium": {
|
||||
"minWaitTimeEstimate": 15000,
|
||||
"maxWaitTimeEstimate": 60000,
|
||||
"suggestedMaxPriorityFeePerGas": "7",
|
||||
"suggestedMaxFeePerGas": "70"
|
||||
},
|
||||
"high": {
|
||||
"minWaitTimeEstimate": 0,
|
||||
"maxWaitTimeEstimate": 15000,
|
||||
"suggestedMaxPriorityFeePerGas": "10",
|
||||
"suggestedMaxFeePerGas": "100"
|
||||
},
|
||||
"estimatedBaseFee": "50"
|
||||
},
|
||||
"estimatedGasFeeTimeBounds": {}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
const { strict: assert } = require('assert');
|
||||
const { withFixtures, regularDelayMs } = require('../helpers');
|
||||
const { withFixtures, regularDelayMs, largeDelayMs } = require('../helpers');
|
||||
const enLocaleMessages = require('../../../app/_locales/en/messages.json');
|
||||
|
||||
describe('Metamask Import UI', function () {
|
||||
@ -318,6 +318,7 @@ describe('Metamask Import UI', function () {
|
||||
|
||||
// should open the TREZOR Connect popup
|
||||
await driver.clickElement('.hw-connect__btn:nth-of-type(2)');
|
||||
await driver.delay(largeDelayMs * 2);
|
||||
await driver.clickElement({ text: 'Continue', tag: 'button' });
|
||||
await driver.waitUntilXWindowHandles(2);
|
||||
const allWindows = await driver.getAllWindowHandles();
|
||||
|
@ -62,7 +62,7 @@ describe('Editing Confirm Transaction', function () {
|
||||
|
||||
// has correct updated value on the confirm screen the transaction
|
||||
const editedTransactionAmounts = await driver.findElements(
|
||||
'.transaction-detail-item__row .transaction-detail-item__detail-text .currency-display-component__text',
|
||||
'.transaction-detail-item__row .transaction-detail-item__detail-values .currency-display-component__text:last-of-type',
|
||||
);
|
||||
const editedTransactionAmount = editedTransactionAmounts[0];
|
||||
assert.equal(await editedTransactionAmount.getText(), '0.0008');
|
||||
|
@ -1,2 +1,2 @@
|
||||
export const METASWAP_BASE_URL = 'https://api.metaswap.codefi.network';
|
||||
export const METASWAP_API_V2_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
export const METASWAP_BASE_URL = 'https://api2.metaswap.codefi.network';
|
||||
export const GAS_API_URL = 'https://gas-api.metaswap.codefi.network';
|
||||
|
@ -221,7 +221,6 @@ export const createSwapsMockStore = () => {
|
||||
topAggId: 'TEST_AGG_BEST',
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
},
|
||||
useTokenDetection: true,
|
||||
tokenList: {
|
||||
|
@ -239,6 +239,7 @@ export default class AccountMenu extends Component {
|
||||
case KEYRING_TYPES.TREZOR:
|
||||
case KEYRING_TYPES.LEDGER:
|
||||
case KEYRING_TYPES.LATTICE:
|
||||
case KEYRING_TYPES.QR:
|
||||
label = t('hardware');
|
||||
break;
|
||||
case 'Simple Key Pair':
|
||||
|
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Box from '../../../ui/box';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
|
||||
const AdvancedGasFeeInputSubtext = ({ latest, historical }) => {
|
||||
return (
|
||||
<Box className="advanced-gas-fee-input-subtext">
|
||||
<Box display="flex" alignItems="center">
|
||||
<span className="advanced-gas-fee-input-subtext__label">
|
||||
<I18nValue messageKey="currentTitle" />
|
||||
</span>
|
||||
<span>{latest}</span>
|
||||
<img src="./images/high-arrow.svg" alt="" />
|
||||
</Box>
|
||||
<Box>
|
||||
<span className="advanced-gas-fee-input-subtext__label">
|
||||
<I18nValue messageKey="twelveHrTitle" />
|
||||
</span>
|
||||
<span>{historical}</span>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
AdvancedGasFeeInputSubtext.propTypes = {
|
||||
latest: PropTypes.string,
|
||||
historical: PropTypes.string,
|
||||
};
|
||||
|
||||
export default AdvancedGasFeeInputSubtext;
|
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import AdvancedGasFeeInputSubtext from './advanced-gas-fee-input-subtext';
|
||||
|
||||
describe('AdvancedGasFeeInputSubtext', () => {
|
||||
it('should renders latest and historical values passed', () => {
|
||||
render(
|
||||
<AdvancedGasFeeInputSubtext
|
||||
latest="Latest Value"
|
||||
historical="Historical value"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText('Latest Value')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Historical value')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-fee-input-subtext';
|
@ -0,0 +1,17 @@
|
||||
.advanced-gas-fee-input-subtext {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 2px;
|
||||
color: $ui-4;
|
||||
font-size: $font-size-h8;
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../../../ui/box';
|
||||
import BaseFeeInput from './base-fee-input';
|
||||
import PriorityFeeInput from './priority-fee-input';
|
||||
|
||||
const AdvancedGasFeeInputs = () => {
|
||||
return (
|
||||
<Box className="advanced-gas-fee-inputs" margin={4}>
|
||||
<BaseFeeInput />
|
||||
<div className="advanced-gas-fee-inputs__separator" />
|
||||
<PriorityFeeInput />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedGasFeeInputs;
|
@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||
import {
|
||||
divideCurrencies,
|
||||
multiplyCurrencies,
|
||||
} from '../../../../../../shared/modules/conversion.utils';
|
||||
import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
||||
import { useCurrencyDisplay } from '../../../../../hooks/useCurrencyDisplay';
|
||||
import Button from '../../../../ui/button';
|
||||
import Box from '../../../../ui/box';
|
||||
import FormField from '../../../../ui/form-field';
|
||||
import I18nValue from '../../../../ui/i18n-value';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||
|
||||
const divideCurrencyValues = (value, baseFee) => {
|
||||
if (baseFee === 0) {
|
||||
return 0;
|
||||
}
|
||||
return divideCurrencies(value, baseFee, {
|
||||
numberOfDecimals: 2,
|
||||
dividendBase: 10,
|
||||
divisorBase: 10,
|
||||
}).toNumber();
|
||||
};
|
||||
|
||||
const multiplyCurrencyValues = (baseFee, value, numberOfDecimals) =>
|
||||
multiplyCurrencies(baseFee, value, {
|
||||
numberOfDecimals,
|
||||
multiplicandBase: 10,
|
||||
multiplierBase: 10,
|
||||
}).toNumber();
|
||||
|
||||
const BaseFeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext();
|
||||
const { setDirty, setMaxFeePerGas } = useAdvanceGasFeePopoverContext();
|
||||
const { estimatedBaseFee } = gasFeeEstimates;
|
||||
const {
|
||||
numberOfDecimals: numberOfDecimalsPrimary,
|
||||
} = useUserPreferencedCurrency(PRIMARY);
|
||||
const {
|
||||
currency,
|
||||
numberOfDecimals: numberOfDecimalsFiat,
|
||||
} = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
|
||||
const [editingInGwei, setEditingInGwei] = useState(false);
|
||||
|
||||
const [maxBaseFeeGWEI, setMaxBaseFeeGWEI] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
advancedGasFeeValues.maxBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
);
|
||||
}
|
||||
return maxFeePerGas;
|
||||
});
|
||||
|
||||
const [maxBaseFeeMultiplier, setMaxBaseFeeMultiplier] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.maxBaseFee
|
||||
) {
|
||||
return advancedGasFeeValues.maxBaseFee;
|
||||
}
|
||||
return divideCurrencyValues(maxFeePerGas, estimatedBaseFee);
|
||||
});
|
||||
|
||||
const [, { value: baseFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(maxBaseFeeGWEI),
|
||||
{ currency, numberOfDecimalsFiat },
|
||||
);
|
||||
|
||||
const updateBaseFee = useCallback(
|
||||
(value) => {
|
||||
let baseFeeInGWEI;
|
||||
let baseFeeMultiplierValue;
|
||||
if (editingInGwei) {
|
||||
baseFeeInGWEI = value;
|
||||
baseFeeMultiplierValue = divideCurrencyValues(value, estimatedBaseFee);
|
||||
} else {
|
||||
baseFeeInGWEI = multiplyCurrencyValues(
|
||||
estimatedBaseFee,
|
||||
value,
|
||||
numberOfDecimalsPrimary,
|
||||
);
|
||||
baseFeeMultiplierValue = value;
|
||||
}
|
||||
setMaxBaseFeeGWEI(baseFeeInGWEI);
|
||||
setMaxBaseFeeMultiplier(baseFeeMultiplierValue);
|
||||
setDirty(true);
|
||||
},
|
||||
[
|
||||
editingInGwei,
|
||||
estimatedBaseFee,
|
||||
numberOfDecimalsPrimary,
|
||||
setMaxBaseFeeGWEI,
|
||||
setMaxBaseFeeMultiplier,
|
||||
setDirty,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxFeePerGas(maxBaseFeeGWEI);
|
||||
}, [maxBaseFeeGWEI, setMaxFeePerGas]);
|
||||
|
||||
return (
|
||||
<Box className="base-fee-input">
|
||||
<FormField
|
||||
onChange={updateBaseFee}
|
||||
titleText={t('maxBaseFee')}
|
||||
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
|
||||
tooltipText={t('advancedBaseGasFeeToolTip')}
|
||||
titleDetail={
|
||||
<Button
|
||||
className="base-fee-input__edit-link"
|
||||
type="link"
|
||||
onClick={() => setEditingInGwei(!editingInGwei)}
|
||||
>
|
||||
<I18nValue
|
||||
messageKey={editingInGwei ? 'editInMultiplier' : 'editInGwei'}
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
value={editingInGwei ? maxBaseFeeGWEI : maxBaseFeeMultiplier}
|
||||
detailText={
|
||||
editingInGwei
|
||||
? `${maxBaseFeeMultiplier}x ${`≈ ${baseFeeInFiat}`}`
|
||||
: `${maxBaseFeeGWEI} GWEI ${`≈ ${baseFeeInFiat}`}`
|
||||
}
|
||||
numeric
|
||||
/>
|
||||
<AdvancedGasFeeInputSubtext
|
||||
latest={estimatedBaseFee}
|
||||
historical="23-359 GWEI"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseFeeInput;
|
@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../../store/store';
|
||||
|
||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
||||
import BaseFeeInput from './base-fee-input';
|
||||
|
||||
jest.mock('../../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const render = (txProps) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
advancedGasFee: { maxBaseFee: 2 },
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates:
|
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
userFeeLevel: 'custom',
|
||||
...txProps,
|
||||
}}
|
||||
>
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<BaseFeeInput />
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('BaseFeeInput', () => {
|
||||
it('should renders advancedGasFee.baseFee value if current estimate used is not custom', () => {
|
||||
render({
|
||||
userFeeLevel: 'high',
|
||||
});
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(2);
|
||||
});
|
||||
|
||||
it('should renders baseFee values from transaction if current estimate used is custom', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxFeePerGas: '0x174876E800',
|
||||
},
|
||||
});
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(2);
|
||||
});
|
||||
|
||||
it('should show GWEI value in input when Edit in GWEI link is clicked', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxFeePerGas: '0x174876E800',
|
||||
},
|
||||
});
|
||||
fireEvent.click(screen.queryByText('Edit in GWEI'));
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(100);
|
||||
});
|
||||
|
||||
it('should correctly update GWEI value if multiplier is changed', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxFeePerGas: '0x174876E800',
|
||||
},
|
||||
});
|
||||
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||
target: { value: 4 },
|
||||
});
|
||||
fireEvent.click(screen.queryByText('Edit in GWEI'));
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(200);
|
||||
});
|
||||
|
||||
it('should correctly update multiplier value if GWEI is changed', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxFeePerGas: '0x174876E800',
|
||||
},
|
||||
});
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(2);
|
||||
fireEvent.click(screen.queryByText('Edit in GWEI'));
|
||||
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||
target: { value: 200 },
|
||||
});
|
||||
fireEvent.click(screen.queryByText('Edit in multiplier'));
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(4);
|
||||
});
|
||||
|
||||
it('should show current value of estimatedBaseFee in subtext', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxFeePerGas: '0x174876E800',
|
||||
},
|
||||
});
|
||||
expect(screen.queryByText('50')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './base-fee-input';
|
@ -0,0 +1,8 @@
|
||||
.base-fee-input {
|
||||
a.base-fee-input__edit-link {
|
||||
display: inline;
|
||||
font-size: $font-size-h7;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-fee-inputs';
|
@ -0,0 +1,14 @@
|
||||
.advanced-gas-fee-inputs {
|
||||
.form-field {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.form-field__heading-title > h6 {
|
||||
font-size: $font-size-h7;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
border-top: 1px solid $ui-grey;
|
||||
margin: 24px 0 16px 0;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './priority-fee-input';
|
@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../../shared/constants/gas';
|
||||
import { SECONDARY } from '../../../../../helpers/constants/common';
|
||||
import { decGWEIToHexWEI } from '../../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../../selectors';
|
||||
import { useCurrencyDisplay } from '../../../../../hooks/useCurrencyDisplay';
|
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||
import { useUserPreferencedCurrency } from '../../../../../hooks/useUserPreferencedCurrency';
|
||||
import FormField from '../../../../ui/form-field';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../../context';
|
||||
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
|
||||
|
||||
const PriorityFeeInput = () => {
|
||||
const t = useI18nContext();
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
const {
|
||||
setDirty,
|
||||
setMaxPriorityFeePerGas,
|
||||
} = useAdvanceGasFeePopoverContext();
|
||||
const { estimateUsed, maxPriorityFeePerGas } = useGasFeeContext();
|
||||
|
||||
const [priorityFee, setPriorityFee] = useState(() => {
|
||||
if (
|
||||
estimateUsed !== PRIORITY_LEVELS.CUSTOM &&
|
||||
advancedGasFeeValues?.priorityFee
|
||||
)
|
||||
return advancedGasFeeValues.priorityFee;
|
||||
return maxPriorityFeePerGas;
|
||||
});
|
||||
|
||||
const { currency, numberOfDecimals } = useUserPreferencedCurrency(SECONDARY);
|
||||
|
||||
const [, { value: priorityFeeInFiat }] = useCurrencyDisplay(
|
||||
decGWEIToHexWEI(priorityFee),
|
||||
{ currency, numberOfDecimals },
|
||||
);
|
||||
|
||||
const updatePriorityFee = (value) => {
|
||||
setPriorityFee(value);
|
||||
setDirty(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPriorityFeePerGas(priorityFee);
|
||||
}, [priorityFee, setMaxPriorityFeePerGas]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
onChange={updatePriorityFee}
|
||||
titleText={t('priorityFee')}
|
||||
titleUnit="(GWEI)"
|
||||
tooltipText={t('advancedPriorityFeeToolTip')}
|
||||
value={priorityFee}
|
||||
detailText={`≈ ${priorityFeeInFiat}`}
|
||||
numeric
|
||||
/>
|
||||
<AdvancedGasFeeInputSubtext latest="1-18 GWEI" historical="23-359 GWEI" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PriorityFeeInput;
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../../store/store';
|
||||
|
||||
import { AdvanceGasFeePopoverContextProvider } from '../../context';
|
||||
import PriorityfeeInput from './priority-fee-input';
|
||||
|
||||
jest.mock('../../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const render = (txProps) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
advancedGasFee: { priorityFee: 100 },
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates:
|
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
userFeeLevel: 'custom',
|
||||
...txProps,
|
||||
}}
|
||||
>
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<PriorityfeeInput />
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('PriorityfeeInput', () => {
|
||||
it('should renders advancedGasFee.priorityfee value if current estimate used is not custom', () => {
|
||||
render({
|
||||
userFeeLevel: 'high',
|
||||
});
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(100);
|
||||
});
|
||||
|
||||
it('should renders priorityfee value from transaction if current estimate used is custom', () => {
|
||||
render({
|
||||
txParams: {
|
||||
maxPriorityFeePerGas: '0x77359400',
|
||||
},
|
||||
});
|
||||
expect(document.getElementsByTagName('input')[0]).toHaveValue(2);
|
||||
});
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
|
||||
import Box from '../../ui/box';
|
||||
import Popover from '../../ui/popover';
|
||||
|
||||
import { AdvanceGasFeePopoverContextProvider } from './context';
|
||||
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
|
||||
import AdvancedGasFeeSaveButton from './advanced-gas-fee-save';
|
||||
|
||||
const AdvancedGasFeePopover = () => {
|
||||
const t = useI18nContext();
|
||||
const {
|
||||
closeModal,
|
||||
closeAllModals,
|
||||
currentModal,
|
||||
} = useTransactionModalContext();
|
||||
|
||||
if (currentModal !== 'advancedGasFee') return null;
|
||||
|
||||
return (
|
||||
<AdvanceGasFeePopoverContextProvider>
|
||||
<Popover
|
||||
className="advanced-gas-fee-popover"
|
||||
title={t('advancedGasFeeModalTitle')}
|
||||
onBack={() => closeModal('advancedGasFee')}
|
||||
onClose={closeAllModals}
|
||||
footer={<AdvancedGasFeeSaveButton />}
|
||||
>
|
||||
<Box className="advanced-gas-fee-popover__wrapper">
|
||||
<AdvancedGasFeeInputs />
|
||||
</Box>
|
||||
</Popover>
|
||||
</AdvanceGasFeePopoverContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedGasFeePopover;
|
@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import mockEstimates from '../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import configureStore from '../../../store/store';
|
||||
|
||||
import AdvancedGasFeePopover from './advanced-gas-fee-popover';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../contexts/transaction-modal', () => ({
|
||||
useTransactionModalContext: () => ({
|
||||
closeModal: () => undefined,
|
||||
currentModal: 'advancedGasFee',
|
||||
}),
|
||||
}));
|
||||
|
||||
const render = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
advancedGasFee: { priorityFee: 100 },
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates:
|
||||
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{
|
||||
userFeeLevel: 'custom',
|
||||
}}
|
||||
>
|
||||
<AdvancedGasFeePopover />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('AdvancedGasFeePopover', () => {
|
||||
it('should renders save button disabled by default', () => {
|
||||
render();
|
||||
expect(screen.queryByRole('button', { name: 'Save' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable save button as input value is changed', () => {
|
||||
render();
|
||||
fireEvent.change(document.getElementsByTagName('input')[0], {
|
||||
target: { value: 4 },
|
||||
});
|
||||
expect(screen.queryByRole('button', { name: 'Save' })).not.toBeDisabled();
|
||||
});
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import Button from '../../../ui/button';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
|
||||
import { useAdvanceGasFeePopoverContext } from '../context';
|
||||
import { decGWEIToHexWEI } from '../../../../../shared/modules/conversion.utils';
|
||||
|
||||
const AdvancedGasFeeSaveButton = () => {
|
||||
const { closeModal } = useTransactionModalContext();
|
||||
const { updateTransaction } = useGasFeeContext();
|
||||
const {
|
||||
isDirty,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
} = useAdvanceGasFeePopoverContext();
|
||||
|
||||
const onSave = () => {
|
||||
updateTransaction(
|
||||
PRIORITY_LEVELS.CUSTOM,
|
||||
decGWEIToHexWEI(maxFeePerGas),
|
||||
decGWEIToHexWEI(maxPriorityFeePerGas),
|
||||
);
|
||||
closeModal('advancedGasFee');
|
||||
};
|
||||
|
||||
return (
|
||||
<Button type="primary" disabled={!isDirty} onClick={onSave}>
|
||||
<I18nValue messageKey="save" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedGasFeeSaveButton;
|
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-fee-save';
|
@ -0,0 +1,33 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const AdvanceGasFeePopoverContext = createContext({});
|
||||
|
||||
export const AdvanceGasFeePopoverContextProvider = ({ children }) => {
|
||||
const [maxFeePerGas, setMaxFeePerGas] = useState();
|
||||
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState();
|
||||
const [isDirty, setDirty] = useState();
|
||||
|
||||
return (
|
||||
<AdvanceGasFeePopoverContext.Provider
|
||||
value={{
|
||||
isDirty,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
setDirty,
|
||||
setMaxPriorityFeePerGas,
|
||||
setMaxFeePerGas,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AdvanceGasFeePopoverContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useAdvanceGasFeePopoverContext() {
|
||||
return useContext(AdvanceGasFeePopoverContext);
|
||||
}
|
||||
|
||||
AdvanceGasFeePopoverContextProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './advanceGasFeePopover';
|
1
ui/components/app/advanced-gas-fee-popover/index.js
Normal file
1
ui/components/app/advanced-gas-fee-popover/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './advanced-gas-fee-popover';
|
10
ui/components/app/advanced-gas-fee-popover/index.scss
Normal file
10
ui/components/app/advanced-gas-fee-popover/index.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.advanced-gas-fee-popover {
|
||||
&__wrapper {
|
||||
border-top: 1px solid $ui-grey;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
border-top: 1px solid $ui-grey;
|
||||
margin: 24px 0 16px 0;
|
||||
}
|
||||
}
|
@ -13,6 +13,10 @@
|
||||
@import 'connected-status-indicator/index';
|
||||
@import 'edit-gas-display/index';
|
||||
@import 'edit-gas-display-education/index';
|
||||
@import 'edit-gas-fee-popover/index';
|
||||
@import 'edit-gas-fee-popover/edit-gas-item/index';
|
||||
@import 'edit-gas-fee-popover/network-status/index';
|
||||
@import 'edit-gas-fee-popover/network-status/status-slider/index';
|
||||
@import 'gas-customization/gas-modal-page-container/index';
|
||||
@import 'gas-customization/gas-price-button-group/index';
|
||||
@import 'gas-customization/index';
|
||||
@ -46,4 +50,8 @@
|
||||
@import 'transaction-total-banner/index';
|
||||
@import 'wallet-overview/index';
|
||||
@import 'whats-new-popup/index';
|
||||
@import 'loading-network-screen/index'
|
||||
@import 'loading-network-screen/index';
|
||||
@import 'advanced-gas-fee-popover/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/base-fee-input/index';
|
||||
@import 'advanced-gas-fee-popover/advanced-gas-fee-input-subtext/index';
|
||||
|
@ -0,0 +1,148 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Box from '../../ui/box';
|
||||
import Button from '../../ui/button';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
TEXT_ALIGN,
|
||||
JUSTIFY_CONTENT,
|
||||
FLEX_DIRECTION,
|
||||
ALIGN_ITEMS,
|
||||
DISPLAY,
|
||||
BLOCK_SIZES,
|
||||
SIZES,
|
||||
FLEX_WRAP,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
|
||||
export default function CollectiblesItems({ onAddNFT, onRefreshList }) {
|
||||
const t = useI18nContext();
|
||||
const collections = {};
|
||||
const defaultDropdownState = {};
|
||||
|
||||
Object.keys(collections).forEach((key) => {
|
||||
defaultDropdownState[key] = true;
|
||||
});
|
||||
|
||||
const [dropdownState, setDropdownState] = useState(defaultDropdownState);
|
||||
const width =
|
||||
getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
|
||||
? BLOCK_SIZES.ONE_THIRD
|
||||
: BLOCK_SIZES.ONE_SIXTH;
|
||||
return (
|
||||
<div className="collectibles-items">
|
||||
<Box padding={[4, 6, 4, 6]} flexDirection={FLEX_DIRECTION.COLUMN}>
|
||||
<>
|
||||
{Object.keys(collections).map((key, index) => {
|
||||
const { icon, collectibles } = collections[key];
|
||||
const isExpanded = dropdownState[key];
|
||||
|
||||
return (
|
||||
<div key={`collection-${index}`}>
|
||||
<Box
|
||||
marginTop={4}
|
||||
marginBottom={4}
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN}
|
||||
>
|
||||
<Box alignItems={ALIGN_ITEMS.CENTER}>
|
||||
<img width="28" src={icon} />
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
variant={TYPOGRAPHY.H4}
|
||||
margin={[0, 0, 0, 2]}
|
||||
>
|
||||
{`${key} (${collectibles.length})`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box alignItems={ALIGN_ITEMS.FLEX_END}>
|
||||
<i
|
||||
className={`fa fa-lg fa-chevron-${
|
||||
isExpanded ? 'down' : 'right'
|
||||
}`}
|
||||
onClick={() => {
|
||||
setDropdownState((_dropdownState) => ({
|
||||
..._dropdownState,
|
||||
[key]: !isExpanded,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{isExpanded ? (
|
||||
<Box display={DISPLAY.FLEX} flexWrap={FLEX_WRAP.WRAP}>
|
||||
{collectibles.map((collectible, i) => {
|
||||
return (
|
||||
<Box width={width} padding={2} key={`collectible-${i}`}>
|
||||
<Box
|
||||
borderRadius={SIZES.MD}
|
||||
backgroundColor={collectible.backgroundColor}
|
||||
>
|
||||
<img src={collectible.icon} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Box
|
||||
marginTop={6}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
>
|
||||
<Typography
|
||||
color={COLORS.UI3}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
align={TEXT_ALIGN.CENTER}
|
||||
>
|
||||
{t('missingNFT')}
|
||||
</Typography>
|
||||
<Box
|
||||
alignItems={ALIGN_ITEMS.CENTER}
|
||||
justifyContent={JUSTIFY_CONTENT.CENTER}
|
||||
>
|
||||
<Box justifyContent={JUSTIFY_CONTENT.FLEX_END}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={onRefreshList}
|
||||
style={{ padding: '4px' }}
|
||||
>
|
||||
{t('refreshList')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography
|
||||
color={COLORS.UI3}
|
||||
variant={TYPOGRAPHY.H4}
|
||||
align={TEXT_ALIGN.CENTER}
|
||||
>
|
||||
{t('or')}
|
||||
</Typography>
|
||||
<Box justifyContent={JUSTIFY_CONTENT.FLEX_START}>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={onAddNFT}
|
||||
style={{ padding: '4px' }}
|
||||
>
|
||||
{t('addNFTLowerCase')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CollectiblesItems.propTypes = {
|
||||
onAddNFT: PropTypes.func.isRequired,
|
||||
onRefreshList: PropTypes.func.isRequired,
|
||||
};
|
1
ui/components/app/collectibles-items/index.js
Normal file
1
ui/components/app/collectibles-items/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './collectibles-items.component';
|
@ -1 +0,0 @@
|
||||
export { default } from './collectibles-list.component';
|
@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
|
||||
import Box from '../../ui/box';
|
||||
import Button from '../../ui/button';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import NewCollectiblesNotice from '../new-collectibles-notice';
|
||||
import CollectiblesItems from '../collectibles-items';
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
@ -13,16 +15,23 @@ import {
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
export default function CollectiblesList({ onAddNFT }) {
|
||||
export default function CollectiblesTab({ onAddNFT }) {
|
||||
const collectibles = [];
|
||||
const newNFTsDetected = false;
|
||||
const t = useI18nContext();
|
||||
|
||||
return (
|
||||
<div className="collectibles-list">
|
||||
<div className="collectibles-tab">
|
||||
{collectibles.length > 0 ? (
|
||||
<span>{JSON.stringify(collectibles)}</span>
|
||||
<CollectiblesItems
|
||||
onAddNFT={onAddNFT}
|
||||
onRefreshList={() => {
|
||||
console.log('refreshing collectibles');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box padding={[4, 0, 4, 0]}>
|
||||
<Box padding={[6, 12, 6, 12]}>
|
||||
{newNFTsDetected ? <NewCollectiblesNotice /> : null}
|
||||
<Box justifyContent={JUSTIFY_CONTENT.CENTER}>
|
||||
<img src="./images/no-nfts.svg" />
|
||||
</Box>
|
||||
@ -76,6 +85,6 @@ export default function CollectiblesList({ onAddNFT }) {
|
||||
);
|
||||
}
|
||||
|
||||
CollectiblesList.propTypes = {
|
||||
CollectiblesTab.propTypes = {
|
||||
onAddNFT: PropTypes.func.isRequired,
|
||||
};
|
1
ui/components/app/collectibles-tab/index.js
Normal file
1
ui/components/app/collectibles-tab/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './collectibles-tab.component';
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Tabs, Tab } from '../../../ui/tabs';
|
||||
import ErrorMessage from '../../../ui/error-message';
|
||||
import ActionableMessage from '../../../ui/actionable-message/actionable-message';
|
||||
import { PageContainerFooter } from '../../../ui/page-container';
|
||||
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';
|
||||
|
||||
@ -17,6 +18,7 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
detailsComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
hasSimulationError: PropTypes.bool,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
identiconAddress: PropTypes.string,
|
||||
nonce: PropTypes.string,
|
||||
@ -31,11 +33,14 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
onCancel: PropTypes.func,
|
||||
cancelText: PropTypes.string,
|
||||
onSubmit: PropTypes.func,
|
||||
setUserAcknowledgedGasMissing: PropTypes.func,
|
||||
submitText: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
hideUserAcknowledgedGasMissing: PropTypes.bool,
|
||||
unapprovedTxCount: PropTypes.number,
|
||||
rejectNText: PropTypes.string,
|
||||
hideTitle: PropTypes.boolean,
|
||||
hideTitle: PropTypes.bool,
|
||||
supportsEIP1559V2: PropTypes.bool,
|
||||
};
|
||||
|
||||
renderContent() {
|
||||
@ -71,6 +76,7 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
action,
|
||||
errorKey,
|
||||
errorMessage,
|
||||
hasSimulationError,
|
||||
title,
|
||||
titleComponent,
|
||||
subtitleComponent,
|
||||
@ -91,14 +97,33 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
origin,
|
||||
ethGasPriceWarning,
|
||||
hideTitle,
|
||||
setUserAcknowledgedGasMissing,
|
||||
hideUserAcknowledgedGasMissing,
|
||||
supportsEIP1559V2,
|
||||
} = this.props;
|
||||
|
||||
const primaryAction = hideUserAcknowledgedGasMissing
|
||||
? null
|
||||
: {
|
||||
label: this.context.t('tryAnywayOption'),
|
||||
onClick: setUserAcknowledgedGasMissing,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="confirm-page-container-content">
|
||||
{warning ? <ConfirmPageContainerWarning warning={warning} /> : null}
|
||||
{ethGasPriceWarning && (
|
||||
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
|
||||
)}
|
||||
{hasSimulationError && (
|
||||
<div className="confirm-page-container-content__error-container">
|
||||
<ActionableMessage
|
||||
type="danger"
|
||||
primaryAction={primaryAction}
|
||||
message={this.context.t('simulationErrorMessage')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<ConfirmPageContainerSummary
|
||||
className={classnames({
|
||||
'confirm-page-container-summary--border':
|
||||
@ -115,11 +140,13 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
hideTitle={hideTitle}
|
||||
/>
|
||||
{this.renderContent()}
|
||||
{(errorKey || errorMessage) && (
|
||||
<div className="confirm-page-container-content__error-container">
|
||||
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
|
||||
</div>
|
||||
)}
|
||||
{!supportsEIP1559V2 &&
|
||||
!hasSimulationError &&
|
||||
(errorKey || errorMessage) && (
|
||||
<div className="confirm-page-container-content__error-container">
|
||||
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
|
||||
</div>
|
||||
)}
|
||||
<PageContainerFooter
|
||||
onCancel={onCancel}
|
||||
cancelText={cancelText}
|
||||
|
@ -0,0 +1,126 @@
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys';
|
||||
import ConfirmPageContainerContent from './confirm-page-container-content.component';
|
||||
|
||||
describe('Confirm Page Container Content', () => {
|
||||
const mockStore = {
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureMockStore()(mockStore);
|
||||
|
||||
let props = {};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockOnCancel = jest.fn();
|
||||
const mockOnCancelAll = jest.fn();
|
||||
const mockOnSubmit = jest.fn();
|
||||
const mockSetUserAcknowledgedGasMissing = jest.fn();
|
||||
props = {
|
||||
action: ' Withdraw Stake',
|
||||
errorMessage: null,
|
||||
errorKey: null,
|
||||
hasSimulationError: true,
|
||||
onCancelAll: mockOnCancelAll,
|
||||
onCancel: mockOnCancel,
|
||||
cancelText: 'Reject',
|
||||
onSubmit: mockOnSubmit,
|
||||
setUserAcknowledgedGasMissing: mockSetUserAcknowledgedGasMissing,
|
||||
submitText: 'Confirm',
|
||||
disabled: true,
|
||||
origin: 'http://localhost:4200',
|
||||
hideTitle: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('render ConfirmPageContainer component with simulation error', async () => {
|
||||
process.env.EIP_1559_V2 = false;
|
||||
|
||||
const { queryByText, getByText } = renderWithProvider(
|
||||
<ConfirmPageContainerContent {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(
|
||||
queryByText('Transaction Error. Exception thrown in contract code.'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryByText('I will try anyway')).toBeInTheDocument();
|
||||
|
||||
const confirmButton = getByText('Confirm');
|
||||
expect(getByText('Confirm').closest('button')).toBeDisabled();
|
||||
fireEvent.click(confirmButton);
|
||||
expect(props.onSubmit).toHaveBeenCalledTimes(0);
|
||||
|
||||
const iWillTryButton = getByText('I will try anyway');
|
||||
fireEvent.click(iWillTryButton);
|
||||
expect(props.setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
|
||||
|
||||
const cancelButton = getByText('Reject');
|
||||
fireEvent.click(cancelButton);
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('render ConfirmPageContainer component with another error', async () => {
|
||||
props.hasSimulationError = false;
|
||||
props.disabled = true;
|
||||
props.errorKey = TRANSACTION_ERROR_KEY;
|
||||
const { queryByText, getByText } = renderWithProvider(
|
||||
<ConfirmPageContainerContent {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
|
||||
),
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByText('I will try anyway')).not.toBeInTheDocument();
|
||||
expect(getByText('Confirm').closest('button')).toBeDisabled();
|
||||
expect(
|
||||
getByText('Transaction Error. Exception thrown in contract code.'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const cancelButton = getByText('Reject');
|
||||
fireEvent.click(cancelButton);
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('render ConfirmPageContainer component with no errors', async () => {
|
||||
props.hasSimulationError = false;
|
||||
props.disabled = false;
|
||||
const { queryByText, getByText } = renderWithProvider(
|
||||
<ConfirmPageContainerContent {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
|
||||
),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByText('Transaction Error. Exception thrown in contract code.'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(queryByText('I will try anyway')).not.toBeInTheDocument();
|
||||
|
||||
const confirmButton = getByText('Confirm');
|
||||
fireEvent.click(confirmButton);
|
||||
expect(props.onSubmit).toHaveBeenCalledTimes(1);
|
||||
|
||||
const cancelButton = getByText('Reject');
|
||||
fireEvent.click(cancelButton);
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import Identicon from '../../../../ui/identicon';
|
||||
import { useGasFeeContext } from '../../../../../contexts/gasFee';
|
||||
|
||||
const ConfirmPageContainerSummary = (props) => {
|
||||
const {
|
||||
@ -18,6 +19,8 @@ const ConfirmPageContainerSummary = (props) => {
|
||||
hideTitle,
|
||||
} = props;
|
||||
|
||||
const { supportsEIP1559V2 } = useGasFeeContext();
|
||||
|
||||
return (
|
||||
<div className={classnames('confirm-page-container-summary', className)}>
|
||||
{origin === 'metamask' ? null : (
|
||||
@ -45,7 +48,7 @@ const ConfirmPageContainerSummary = (props) => {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{hideSubtitle || (
|
||||
{!hideSubtitle && !supportsEIP1559V2 && (
|
||||
<div className="confirm-page-container-summary__subtitle">
|
||||
{subtitleComponent}
|
||||
</div>
|
||||
|
@ -1,13 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SenderToRecipient from '../../ui/sender-to-recipient';
|
||||
import { PageContainerFooter } from '../../ui/page-container';
|
||||
import EditGasPopover from '../edit-gas-popover';
|
||||
|
||||
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import ErrorMessage from '../../ui/error-message';
|
||||
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
|
||||
|
||||
import { PageContainerFooter } from '../../ui/page-container';
|
||||
import Dialog from '../../ui/dialog';
|
||||
import ErrorMessage from '../../ui/error-message';
|
||||
import SenderToRecipient from '../../ui/sender-to-recipient';
|
||||
|
||||
import AdvancedGasFeePopover from '../advanced-gas-fee-popover';
|
||||
import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
|
||||
import EditGasPopover from '../edit-gas-popover';
|
||||
|
||||
import {
|
||||
ConfirmPageContainerHeader,
|
||||
ConfirmPageContainerContent,
|
||||
@ -72,6 +78,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
showAddToAddressBookModal: PropTypes.func,
|
||||
contact: PropTypes.object,
|
||||
isOwnedAccount: PropTypes.bool,
|
||||
supportsEIP1559V2: PropTypes.bool,
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -122,6 +129,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
showAddToAddressBookModal,
|
||||
contact = {},
|
||||
isOwnedAccount,
|
||||
supportsEIP1559V2,
|
||||
} = this.props;
|
||||
|
||||
const showAddToAddressDialog =
|
||||
@ -203,6 +211,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
origin={origin}
|
||||
ethGasPriceWarning={ethGasPriceWarning}
|
||||
hideTitle={hideTitle}
|
||||
supportsEIP1559V2={supportsEIP1559V2}
|
||||
/>
|
||||
)}
|
||||
{shouldDisplayWarning && (
|
||||
@ -225,13 +234,15 @@ export default class ConfirmPageContainer extends Component {
|
||||
)}
|
||||
</PageContainerFooter>
|
||||
)}
|
||||
{editingGas && (
|
||||
{editingGas && !supportsEIP1559V2 && (
|
||||
<EditGasPopover
|
||||
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
|
||||
onClose={handleCloseEditGas}
|
||||
transaction={currentTransaction}
|
||||
/>
|
||||
)}
|
||||
<EditGasFeePopover />
|
||||
<AdvancedGasFeePopover />
|
||||
</div>
|
||||
</GasFeeContextProvider>
|
||||
);
|
||||
|
@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
|
||||
import ErrorMessage from '../../ui/error-message';
|
||||
import I18nValue from '../../ui/i18n-value';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
import Popover from '../../ui/popover';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
|
||||
import { COLORS } from '../../../helpers/constants/design-system';
|
||||
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-keys';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import EditGasItem from './edit-gas-item';
|
||||
import NetworkStatus from './network-status';
|
||||
|
||||
const EditGasFeePopover = () => {
|
||||
const { balanceError } = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
const { closeModal, currentModal } = useTransactionModalContext();
|
||||
|
||||
if (currentModal !== 'editGasFee') return null;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={t('editGasFeeModalTitle')}
|
||||
onClose={() => closeModal('editGasFee')}
|
||||
className="edit-gas-fee-popover"
|
||||
>
|
||||
<>
|
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />}
|
||||
<div className="edit-gas-fee-popover__wrapper">
|
||||
<div className="edit-gas-fee-popover__content">
|
||||
{balanceError && (
|
||||
<ErrorMessage errorKey={INSUFFICIENT_FUNDS_ERROR_KEY} />
|
||||
)}
|
||||
<div className="edit-gas-fee-popover__content__header">
|
||||
<span className="edit-gas-fee-popover__content__header-option">
|
||||
<I18nValue messageKey="gasOption" />
|
||||
</span>
|
||||
<span className="edit-gas-fee-popover__content__header-time">
|
||||
<I18nValue messageKey="time" />
|
||||
</span>
|
||||
<span className="edit-gas-fee-popover__content__header-max-fee">
|
||||
<I18nValue messageKey="maxFee" />
|
||||
</span>
|
||||
</div>
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.LOW} />
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
|
||||
<div className="edit-gas-fee-popover__content__separator" />
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} />
|
||||
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
|
||||
<NetworkStatus />
|
||||
<Typography
|
||||
className="edit-gas-fee-popover__know-more"
|
||||
align="center"
|
||||
color={COLORS.UI4}
|
||||
fontSize="12px"
|
||||
>
|
||||
<I18nValue
|
||||
messageKey="learmMoreAboutGas"
|
||||
options={[
|
||||
<a
|
||||
key="learnMoreLink"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://metamask.zendesk.com/hc/en-us/articles/4404600179227-User-Guide-Gas"
|
||||
>
|
||||
<I18nValue messageKey="learnMore" />
|
||||
</a>,
|
||||
]}
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditGasFeePopover;
|
@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import { ETH } from '../../../helpers/constants/common';
|
||||
import configureStore from '../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
|
||||
import EditGasFeePopover from './edit-gas-fee-popover';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../contexts/transaction-modal', () => ({
|
||||
useTransactionModalContext: () => ({
|
||||
closeModal: () => undefined,
|
||||
currentModal: 'editGasFee',
|
||||
}),
|
||||
}));
|
||||
|
||||
const MOCK_FEE_ESTIMATE = {
|
||||
low: {
|
||||
minWaitTimeEstimate: 360000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 30000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
const render = (txProps) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{ txParams: { gas: '0x5208' }, ...txProps }}
|
||||
>
|
||||
<EditGasFeePopover />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('EditGasFeePopover', () => {
|
||||
it('should renders low / medium / high options', () => {
|
||||
render();
|
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
||||
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Site')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show time estimates', () => {
|
||||
render();
|
||||
expect(screen.queryAllByText('5 min')).toHaveLength(2);
|
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show gas fee estimates', () => {
|
||||
render();
|
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show insufficient balance message if transaction value is less than balance', () => {
|
||||
render({ userFeeLevel: 'high', txParams: { value: '0x64' } });
|
||||
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show insufficient balance message if transaction value is more than balance', () => {
|
||||
render({ userFeeLevel: 'high', txParams: { value: '0x5208' } });
|
||||
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,158 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
||||
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
||||
import { PRIMARY } from '../../../../helpers/constants/common';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../helpers/utils/conversions.util';
|
||||
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||
import { toHumanReadableTime } from '../../../../helpers/utils/util';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
import InfoTooltip from '../../../ui/info-tooltip';
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||
|
||||
import { useCustomTimeEstimate } from './useCustomTimeEstimate';
|
||||
|
||||
const EditGasItem = ({ priorityLevel }) => {
|
||||
const {
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
maxFeePerGas: maxFeePerGasValue,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||
updateTransactionUsingGasFeeEstimates,
|
||||
transaction: { dappSuggestedGasFees },
|
||||
} = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||
const { closeModal, openModal } = useTransactionModalContext();
|
||||
|
||||
let maxFeePerGas;
|
||||
let maxPriorityFeePerGas;
|
||||
let minWaitTime;
|
||||
|
||||
if (gasFeeEstimates?.[priorityLevel]) {
|
||||
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
|
||||
} else if (
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
dappSuggestedGasFees
|
||||
) {
|
||||
maxFeePerGas = hexWEIToDecGWEI(dappSuggestedGasFees.maxFeePerGas);
|
||||
maxPriorityFeePerGas = hexWEIToDecGWEI(
|
||||
dappSuggestedGasFees.maxPriorityFeePerGas,
|
||||
);
|
||||
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) {
|
||||
maxFeePerGas = maxFeePerGasValue;
|
||||
maxPriorityFeePerGas = maxPriorityFeePerGasValue;
|
||||
} else if (advancedGasFeeValues) {
|
||||
maxFeePerGas =
|
||||
gasFeeEstimates.estimatedBaseFee *
|
||||
parseFloat(advancedGasFeeValues.maxBaseFee);
|
||||
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee;
|
||||
}
|
||||
}
|
||||
|
||||
const { waitTimeEstimate } = useCustomTimeEstimate({
|
||||
gasFeeEstimates,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
});
|
||||
|
||||
if (gasFeeEstimates[priorityLevel]) {
|
||||
minWaitTime =
|
||||
priorityLevel === PRIORITY_LEVELS.HIGH
|
||||
? gasFeeEstimates?.high.minWaitTimeEstimate
|
||||
: gasFeeEstimates?.low.maxWaitTimeEstimate;
|
||||
} else {
|
||||
minWaitTime = waitTimeEstimate;
|
||||
}
|
||||
|
||||
const hexMaximumTransactionFee = maxFeePerGas
|
||||
? getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||
})
|
||||
: null;
|
||||
|
||||
const onOptionSelect = () => {
|
||||
if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||
openModal('advancedGasFee');
|
||||
} else {
|
||||
updateTransactionUsingGasFeeEstimates(priorityLevel);
|
||||
closeModal('editGasFee');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames('edit-gas-item', {
|
||||
'edit-gas-item-selected': priorityLevel === estimateUsed,
|
||||
'edit-gas-item-disabled':
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
!dappSuggestedGasFees,
|
||||
})}
|
||||
disabled={
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||
!dappSuggestedGasFees
|
||||
}
|
||||
onClick={onOptionSelect}
|
||||
aria-label={priorityLevel}
|
||||
autoFocus={priorityLevel === estimateUsed}
|
||||
>
|
||||
<span className="edit-gas-item__name">
|
||||
<span
|
||||
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
|
||||
>
|
||||
{PRIORITY_LEVEL_ICON_MAP[priorityLevel]}
|
||||
</span>
|
||||
<I18nValue
|
||||
messageKey={
|
||||
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED
|
||||
? 'dappSuggestedShortLabel'
|
||||
: priorityLevel
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
|
||||
>
|
||||
{minWaitTime
|
||||
? minWaitTime && toHumanReadableTime(t, minWaitTime)
|
||||
: '--'}
|
||||
</span>
|
||||
<span
|
||||
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${priorityLevel}`}
|
||||
>
|
||||
{hexMaximumTransactionFee ? (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
/>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
</span>
|
||||
<span className="edit-gas-item__tooltip">
|
||||
<InfoTooltip position="top" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
EditGasItem.propTypes = {
|
||||
priorityLevel: PropTypes.string,
|
||||
};
|
||||
|
||||
export default EditGasItem;
|
@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import { ETH } from '../../../../helpers/constants/common';
|
||||
import configureStore from '../../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||
|
||||
import EditGasItem from './edit-gas-item';
|
||||
|
||||
jest.mock('../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve('unknown')),
|
||||
}));
|
||||
|
||||
const MOCK_FEE_ESTIMATE = {
|
||||
low: {
|
||||
minWaitTimeEstimate: 360000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 30000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
const DAPP_SUGGESTED_ESTIMATE = {
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
};
|
||||
|
||||
const renderComponent = (componentProps, transactionProps) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
advancedGasFee: {
|
||||
maxBaseFee: '1.5',
|
||||
priorityFee: '2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider
|
||||
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }}
|
||||
>
|
||||
<EditGasItem priorityLevel="low" {...componentProps} />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('EditGasItem', () => {
|
||||
it('should renders low gas estimate option for priorityLevel low', () => {
|
||||
renderComponent({ priorityLevel: 'low' });
|
||||
expect(screen.queryByRole('button', { name: 'low' })).toBeInTheDocument();
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||
expect(screen.queryByText('5 min')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders market gas estimate option for priorityLevel medium', () => {
|
||||
renderComponent({ priorityLevel: 'medium' });
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'medium' }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||
expect(screen.queryByText('5 min')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders aggressive gas estimate option for priorityLevel high', () => {
|
||||
renderComponent({ priorityLevel: 'high' });
|
||||
expect(screen.queryByRole('button', { name: 'high' })).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should highlight option is priorityLevel is currently selected', () => {
|
||||
renderComponent({ priorityLevel: 'high' }, { userFeeLevel: 'high' });
|
||||
expect(
|
||||
document.getElementsByClassName('edit-gas-item-selected'),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
|
||||
renderComponent(
|
||||
{ priorityLevel: 'dappSuggested' },
|
||||
{ dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
|
||||
);
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dappSuggested' }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Site')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should disable site gas estimate option for is transaction does not have dappSuggestedGasFees', async () => {
|
||||
renderComponent({ priorityLevel: 'dappSuggested' });
|
||||
expect(
|
||||
document.getElementsByClassName('edit-gas-item-disabled'),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should renders advance gas estimate option for priorityLevel custom', () => {
|
||||
renderComponent({ priorityLevel: 'custom' }, { userFeeLevel: 'high' });
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'custom' }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
||||
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee
|
||||
expect(screen.queryByTitle('0.001575 ETH')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './edit-gas-item';
|
@ -0,0 +1,70 @@
|
||||
.edit-gas-item {
|
||||
border-radius: 24px;
|
||||
background: white;
|
||||
color: $ui-4;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 12px 0;
|
||||
padding: 4px 12px;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
|
||||
&-selected {
|
||||
background-color: $ui-1;
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: $ui-black;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 4px;
|
||||
|
||||
&-custom {
|
||||
font-size: 20px;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__time-estimate {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
&__fee-estimate {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__tooltip {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
width: 10%;
|
||||
|
||||
.info-tooltip {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__time-estimate-low,
|
||||
&__fee-estimate-high {
|
||||
color: $secondary-1;
|
||||
}
|
||||
|
||||
&__time-estimate-medium,
|
||||
&__time-estimate-high {
|
||||
color: $success-3;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
|
||||
import {
|
||||
getGasEstimateType,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../../../ducks/metamask/metamask';
|
||||
import { getGasFeeTimeEstimate } from '../../../../store/actions';
|
||||
|
||||
export const useCustomTimeEstimate = ({
|
||||
gasFeeEstimates,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
}) => {
|
||||
const gasEstimateType = useSelector(getGasEstimateType);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||
|
||||
const returnNoEstimates =
|
||||
isGasEstimatesLoading ||
|
||||
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||
!maxPriorityFeePerGas;
|
||||
|
||||
// If the user has chosen a value lower than the low gas fee estimate,
|
||||
// We'll need to use the useEffect hook below to make a call to calculate
|
||||
// the time to show
|
||||
const isUnknownLow =
|
||||
gasFeeEstimates?.low &&
|
||||
Number(maxPriorityFeePerGas) <
|
||||
Number(gasFeeEstimates.low.suggestedMaxPriorityFeePerGas);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isGasEstimatesLoading ||
|
||||
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||
!maxPriorityFeePerGas
|
||||
)
|
||||
return;
|
||||
if (isUnknownLow) {
|
||||
// getGasFeeTimeEstimate requires parameters in string format
|
||||
getGasFeeTimeEstimate(
|
||||
new BigNumber(maxPriorityFeePerGas, 10).toString(10),
|
||||
new BigNumber(maxFeePerGas, 10).toString(10),
|
||||
).then((result) => {
|
||||
setCustomEstimatedTime(result);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
gasEstimateType,
|
||||
isUnknownLow,
|
||||
isGasEstimatesLoading,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
returnNoEstimates,
|
||||
]);
|
||||
|
||||
if (returnNoEstimates) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { low = {}, medium = {}, high = {} } = gasFeeEstimates;
|
||||
let waitTimeEstimate = '';
|
||||
|
||||
if (
|
||||
isUnknownLow &&
|
||||
customEstimatedTime &&
|
||||
customEstimatedTime !== 'unknown' &&
|
||||
customEstimatedTime?.upperTimeBound !== 'unknown'
|
||||
) {
|
||||
waitTimeEstimate = Number(customEstimatedTime?.upperTimeBound);
|
||||
} else if (
|
||||
Number(maxPriorityFeePerGas) >= Number(medium.suggestedMaxPriorityFeePerGas)
|
||||
) {
|
||||
waitTimeEstimate = high.minWaitTimeEstimate;
|
||||
} else {
|
||||
waitTimeEstimate = low.maxWaitTimeEstimate;
|
||||
}
|
||||
|
||||
return { waitTimeEstimate };
|
||||
};
|
1
ui/components/app/edit-gas-fee-popover/index.js
Normal file
1
ui/components/app/edit-gas-fee-popover/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './edit-gas-fee-popover';
|
51
ui/components/app/edit-gas-fee-popover/index.scss
Normal file
51
ui/components/app/edit-gas-fee-popover/index.scss
Normal file
@ -0,0 +1,51 @@
|
||||
.edit-gas-fee-popover {
|
||||
height: 500px;
|
||||
|
||||
&__wrapper {
|
||||
border-top: 1px solid $ui-grey;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 16px 12px;
|
||||
|
||||
& .error-message {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
color: $ui-4;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
margin: 0 12px;
|
||||
|
||||
&-option {
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&-time {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
&-max-fee {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
border-top: 1px solid $ui-grey;
|
||||
margin: 8px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__network-status {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
&__know-more a {
|
||||
color: $primary-1;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './network-status';
|
@ -0,0 +1,41 @@
|
||||
.network-status {
|
||||
margin: 24px 0 12px;
|
||||
|
||||
&__info {
|
||||
border-top: 1px solid $ui-2;
|
||||
border-bottom: 1px solid $ui-2;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__separator {
|
||||
border-left: 1px solid $ui-2;
|
||||
height: 65%;
|
||||
}
|
||||
|
||||
&__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30%;
|
||||
|
||||
&--priority-fee {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&-data {
|
||||
color: $ui-4;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-label {
|
||||
color: $Black-100;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import { COLORS } from '../../../../helpers/constants/design-system';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
import Typography from '../../../ui/typography/typography';
|
||||
|
||||
import StatusSlider from './status-slider';
|
||||
|
||||
const NetworkStatus = () => {
|
||||
const { gasFeeEstimates } = useGasFeeContext();
|
||||
|
||||
return (
|
||||
<div className="network-status">
|
||||
<Typography
|
||||
color={COLORS.UI4}
|
||||
fontSize="10px"
|
||||
fontWeight="bold"
|
||||
margin={[3, 0]}
|
||||
variant="h6"
|
||||
>
|
||||
<I18nValue messageKey="networkStatus" />
|
||||
</Typography>
|
||||
<div className="network-status__info">
|
||||
<div className="network-status__info__field">
|
||||
<span className="network-status__info__field-data">
|
||||
{gasFeeEstimates?.estimatedBaseFee &&
|
||||
`${gasFeeEstimates?.estimatedBaseFee} GWEI`}
|
||||
</span>
|
||||
<span className="network-status__info__field-label">Base fee</span>
|
||||
</div>
|
||||
<div className="network-status__info__separator" />
|
||||
<div className="network-status__info__field network-status__info__field--priority-fee">
|
||||
<span className="network-status__info__field-data">
|
||||
0.5 - 22 GWEI
|
||||
</span>
|
||||
<span className="network-status__info__field-label">
|
||||
Priority fee
|
||||
</span>
|
||||
</div>
|
||||
<div className="network-status__info__separator" />
|
||||
<div className="network-status__info__field">
|
||||
<StatusSlider />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkStatus.propTypes = {};
|
||||
|
||||
export default NetworkStatus;
|
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../../test/jest';
|
||||
import { ETH } from '../../../../helpers/constants/common';
|
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../store/store';
|
||||
|
||||
import NetworkStatus from './network-status';
|
||||
|
||||
jest.mock('../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve('unknown')),
|
||||
}));
|
||||
|
||||
const MOCK_FEE_ESTIMATE = {
|
||||
estimatedBaseFee: '50.0112',
|
||||
};
|
||||
|
||||
const renderComponent = (props) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider>
|
||||
<NetworkStatus />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('NetworkStatus', () => {
|
||||
it('should renders labels', () => {
|
||||
renderComponent();
|
||||
expect(screen.queryByText('Base fee')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Priority fee')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders current base fee value', () => {
|
||||
renderComponent();
|
||||
expect(
|
||||
screen.queryByText(`${MOCK_FEE_ESTIMATE.estimatedBaseFee} GWEI`),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './status-slider';
|
@ -0,0 +1,42 @@
|
||||
.status-slider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 55%;
|
||||
|
||||
&__line {
|
||||
background-image: linear-gradient(to right, #037dd6, #d73a49);
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
border-radius: 100px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&__arrow-border {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid white;
|
||||
position: relative;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid black;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: -5px;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
|
||||
import I18nValue from '../../../../ui/i18n-value';
|
||||
|
||||
const GRADIENT_COLORS = [
|
||||
'#037DD6',
|
||||
'#1876C8',
|
||||
'#2D70BA',
|
||||
'#4369AB',
|
||||
'#57629E',
|
||||
'#6A5D92',
|
||||
'#805683',
|
||||
'#9A4D71',
|
||||
'#B44561',
|
||||
'#C54055',
|
||||
];
|
||||
|
||||
const StatusSlider = () => {
|
||||
// todo: value below to be replaced with dynamic values from api once it is available
|
||||
// corresponding test cases also to be added
|
||||
const statusValue = 0.5;
|
||||
const sliderValueNumeric = Math.round(statusValue * 10);
|
||||
|
||||
let statusLabel = 'stable';
|
||||
if (statusValue <= 0.33) {
|
||||
statusLabel = 'notBusy';
|
||||
} else if (statusValue > 0.66) {
|
||||
statusLabel = 'busy';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="status-slider">
|
||||
<div className="status-slider__arrow-border">
|
||||
<div
|
||||
className="status-slider__arrow"
|
||||
style={{
|
||||
borderTopColor: GRADIENT_COLORS[sliderValueNumeric],
|
||||
marginLeft: `${sliderValueNumeric * 10}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="status-slider__line" />
|
||||
<div
|
||||
className="status-slider__label"
|
||||
style={{ color: GRADIENT_COLORS[sliderValueNumeric] }}
|
||||
>
|
||||
<I18nValue messageKey={statusLabel} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusSlider;
|
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../../../test/jest';
|
||||
import { ETH } from '../../../../../helpers/constants/common';
|
||||
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
|
||||
import configureStore from '../../../../../store/store';
|
||||
|
||||
import StatusSlider from './status-slider';
|
||||
|
||||
jest.mock('../../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve('unknown')),
|
||||
}));
|
||||
|
||||
const renderComponent = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider>
|
||||
<StatusSlider />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('NetworkStatus', () => {
|
||||
it('should renders stable for statusValue > 0.33 and <= 0.66', () => {
|
||||
renderComponent();
|
||||
expect(screen.queryByText('Stable')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -28,8 +28,6 @@ import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
|
||||
// Once we reach this second threshold, we switch to minutes as a unit
|
||||
const SECOND_CUTOFF = 90;
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
||||
|
||||
// Shows "seconds" as unit of time if under SECOND_CUTOFF, otherwise "minutes"
|
||||
const toHumanReadableTime = (milliseconds = 1, t) => {
|
||||
@ -50,7 +48,7 @@ export default function GasTiming({
|
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||
const t = useContext(I18nContext);
|
||||
const { estimateToUse } = useGasFeeContext();
|
||||
const { estimateUsed, supportsEIP1559V2 } = useGasFeeContext();
|
||||
|
||||
// If the user has chosen a value lower than the low gas fee estimate,
|
||||
// We'll need to use the useEffect hook below to make a call to calculate
|
||||
@ -97,7 +95,7 @@ export default function GasTiming({
|
||||
]);
|
||||
|
||||
let unknownProcessingTimeText;
|
||||
if (EIP_1559_V2) {
|
||||
if (supportsEIP1559V2) {
|
||||
unknownProcessingTimeText = t('editGasTooLow');
|
||||
} else {
|
||||
unknownProcessingTimeText = (
|
||||
@ -155,7 +153,7 @@ export default function GasTiming({
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!EIP_1559_V2 || estimateToUse === 'low') {
|
||||
if (!supportsEIP1559V2 || estimateUsed === 'low') {
|
||||
attitude = 'negative';
|
||||
}
|
||||
// If the user has chosen a value less than our low estimate,
|
||||
@ -176,7 +174,7 @@ export default function GasTiming({
|
||||
}
|
||||
}
|
||||
// code below needs to cleaned-up once EIP_1559_V2 flag is removed
|
||||
else if (EIP_1559_V2) {
|
||||
else if (supportsEIP1559V2) {
|
||||
text = t('gasTimingNegative', [
|
||||
toHumanReadableTime(low.maxWaitTimeEstimate, t),
|
||||
]);
|
||||
@ -199,8 +197,8 @@ export default function GasTiming({
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className={classNames('gas-timing', {
|
||||
[`gas-timing--${attitude}`]: attitude && !EIP_1559_V2,
|
||||
[`gas-timing--${attitude}-V2`]: attitude && EIP_1559_V2,
|
||||
[`gas-timing--${attitude}`]: attitude && !supportsEIP1559V2,
|
||||
[`gas-timing--${attitude}-V2`]: attitude && supportsEIP1559V2,
|
||||
})}
|
||||
>
|
||||
{text}
|
||||
|
1
ui/components/app/new-collectibles-notice/index.js
Normal file
1
ui/components/app/new-collectibles-notice/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './new-collectibles-notice.component';
|
@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import Box from '../../ui/box';
|
||||
import Dialog from '../../ui/dialog';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
TEXT_ALIGN,
|
||||
FONT_WEIGHT,
|
||||
DISPLAY,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
export default function NewCollectiblesNotice() {
|
||||
const t = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box marginBottom={8}>
|
||||
<Dialog type="message">
|
||||
<Box display={DISPLAY.FLEX}>
|
||||
<Box paddingTop={2}>
|
||||
<i style={{ fontSize: '1rem' }} className="fa fa-info-circle" />
|
||||
</Box>
|
||||
<Box paddingLeft={4}>
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
align={TEXT_ALIGN.LEFT}
|
||||
variant={TYPOGRAPHY.Paragraph}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
>
|
||||
{t('newNFTsDetected')}
|
||||
</Typography>
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
align={TEXT_ALIGN.LEFT}
|
||||
variant={TYPOGRAPHY.Paragraph}
|
||||
boxProps={{ marginBottom: 4 }}
|
||||
>
|
||||
{t('newNFTsDetectedInfo')}
|
||||
</Typography>
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
console.log('show preference popover');
|
||||
}}
|
||||
style={{ fontSize: '.9rem' }}
|
||||
>
|
||||
{t('selectNFTPrivacyPreference')}
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
217
ui/components/app/qr-hardware-popover/base-reader.js
Normal file
217
ui/components/app/qr-hardware-popover/base-reader.js
Normal file
@ -0,0 +1,217 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import log from 'loglevel';
|
||||
import { URDecoder } from '@ngraveio/bc-ur';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
|
||||
import WebcamUtils from '../../../helpers/utils/webcam-utils';
|
||||
import PageContainerFooter from '../../ui/page-container/page-container-footer/page-container-footer.component';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { SECOND } from '../../../../shared/constants/time';
|
||||
import EnhancedReader from './enhanced-reader';
|
||||
|
||||
const READY_STATE = {
|
||||
ACCESSING_CAMERA: 'ACCESSING_CAMERA',
|
||||
NEED_TO_ALLOW_ACCESS: 'NEED_TO_ALLOW_ACCESS',
|
||||
READY: 'READY',
|
||||
};
|
||||
|
||||
const BaseReader = ({
|
||||
isReadingWallet,
|
||||
handleCancel,
|
||||
handleSuccess,
|
||||
setErrorTitle,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const [ready, setReady] = useState(READY_STATE.ACCESSING_CAMERA);
|
||||
const [error, setError] = useState(null);
|
||||
const [urDecoder, setURDecoder] = useState(new URDecoder());
|
||||
|
||||
let permissionChecker = null;
|
||||
const mounted = useRef(false);
|
||||
|
||||
const reset = () => {
|
||||
setReady(READY_STATE.ACCESSING_CAMERA);
|
||||
setError(null);
|
||||
setURDecoder(new URDecoder());
|
||||
};
|
||||
|
||||
const checkEnvironment = async () => {
|
||||
try {
|
||||
const { environmentReady } = await WebcamUtils.checkStatus();
|
||||
if (
|
||||
!environmentReady &&
|
||||
getEnvironmentType() !== ENVIRONMENT_TYPE_FULLSCREEN
|
||||
) {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
const currentHash = currentUrl.hash;
|
||||
const currentRoute = currentHash ? currentHash.substring(1) : null;
|
||||
global.platform.openExtensionInBrowser(currentRoute);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted.current) {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
// initial attempt is required to trigger permission prompt
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
return initCamera();
|
||||
};
|
||||
|
||||
const checkPermissions = async () => {
|
||||
try {
|
||||
const { permissions } = await WebcamUtils.checkStatus();
|
||||
if (permissions) {
|
||||
// Let the video stream load first...
|
||||
await new Promise((resolve) => setTimeout(resolve, SECOND * 2));
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
setReady(READY_STATE.READY);
|
||||
} else if (mounted.current) {
|
||||
// Keep checking for permissions
|
||||
permissionChecker = setTimeout(checkPermissions, SECOND);
|
||||
setReady(READY_STATE.NEED_TO_ALLOW_ACCESS);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted.current) {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleScan = (data) => {
|
||||
try {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
urDecoder.receivePart(data);
|
||||
if (urDecoder.isComplete()) {
|
||||
const result = urDecoder.resultUR();
|
||||
handleSuccess(result).catch(setError);
|
||||
}
|
||||
} catch (e) {
|
||||
if (isReadingWallet) {
|
||||
setErrorTitle(t('QRHardwareUnknownQRCodeTitle'));
|
||||
} else {
|
||||
setErrorTitle(t('QRHardwareInvalidTransactionTitle'));
|
||||
}
|
||||
setError(new Error(t('unknownQrCode')));
|
||||
}
|
||||
};
|
||||
|
||||
const initCamera = () => {
|
||||
try {
|
||||
checkPermissions();
|
||||
} catch (e) {
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
if (e.name === 'NotAllowedError') {
|
||||
log.info(`Permission denied: '${e}'`);
|
||||
setReady(READY_STATE.NEED_TO_ALLOW_ACCESS);
|
||||
} else {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
checkEnvironment();
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
clearTimeout(permissionChecker);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready === READY_STATE.READY) {
|
||||
initCamera();
|
||||
} else if (ready === READY_STATE.NEED_TO_ALLOW_ACCESS) {
|
||||
checkPermissions();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ready]);
|
||||
|
||||
const tryAgain = () => {
|
||||
clearTimeout(permissionChecker);
|
||||
reset();
|
||||
checkEnvironment();
|
||||
};
|
||||
|
||||
const renderError = () => {
|
||||
let title, msg;
|
||||
if (error.type === 'NO_WEBCAM_FOUND') {
|
||||
title = t('noWebcamFoundTitle');
|
||||
msg = t('noWebcamFound');
|
||||
} else if (error.message === t('unknownQrCode')) {
|
||||
if (isReadingWallet) {
|
||||
msg = t('QRHardwareUnknownWalletQRCode');
|
||||
} else {
|
||||
msg = t('unknownQrCode');
|
||||
}
|
||||
} else if (error.message === t('QRHardwareMismatchedSignId')) {
|
||||
msg = t('QRHardwareMismatchedSignId');
|
||||
} else {
|
||||
title = t('unknownCameraErrorTitle');
|
||||
msg = t('unknownCameraError');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="qr-scanner__image">
|
||||
<img src="images/webcam.svg" width="70" height="70" alt="" />
|
||||
</div>
|
||||
{title ? <div className="qr-scanner__title">{title}</div> : null}
|
||||
<div className="qr-scanner__error">{msg}</div>
|
||||
<PageContainerFooter
|
||||
onCancel={() => {
|
||||
setErrorTitle('');
|
||||
handleCancel();
|
||||
}}
|
||||
onSubmit={() => {
|
||||
setErrorTitle('');
|
||||
tryAgain();
|
||||
}}
|
||||
cancelText={t('cancel')}
|
||||
submitText={t('tryAgain')}
|
||||
submitButtonType="confirm"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderVideo = () => {
|
||||
let message;
|
||||
if (ready === READY_STATE.ACCESSING_CAMERA) {
|
||||
message = t('accessingYourCamera');
|
||||
} else if (ready === READY_STATE.READY) {
|
||||
message = t('QRHardwareScanInstructions');
|
||||
} else if (ready === READY_STATE.NEED_TO_ALLOW_ACCESS) {
|
||||
message = t('youNeedToAllowCameraAccess');
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="qr-scanner__content">
|
||||
<EnhancedReader handleScan={handleScan} />
|
||||
</div>
|
||||
{message && <div className="qr-scanner__status">{message}</div>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="qr-scanner">{error ? renderError() : renderVideo()}</div>
|
||||
);
|
||||
};
|
||||
|
||||
BaseReader.propTypes = {
|
||||
isReadingWallet: PropTypes.bool.isRequired,
|
||||
handleCancel: PropTypes.func.isRequired,
|
||||
handleSuccess: PropTypes.func.isRequired,
|
||||
setErrorTitle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default BaseReader;
|
67
ui/components/app/qr-hardware-popover/enhanced-reader.js
Normal file
67
ui/components/app/qr-hardware-popover/enhanced-reader.js
Normal file
@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { BarcodeFormat, DecodeHintType } from '@zxing/library';
|
||||
import { BrowserQRCodeReader } from '@zxing/browser';
|
||||
import log from 'loglevel';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MILLISECOND } from '../../../../shared/constants/time';
|
||||
import Spinner from '../../ui/spinner';
|
||||
|
||||
const EnhancedReader = ({ handleScan }) => {
|
||||
const [canplay, setCanplay] = useState(false);
|
||||
const codeReader = useMemo(() => {
|
||||
const hint = new Map();
|
||||
hint.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
|
||||
return new BrowserQRCodeReader(hint, {
|
||||
delayBetweenScanAttempts: MILLISECOND * 100,
|
||||
delayBetweenScanSuccess: MILLISECOND * 100,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const videoElem = document.getElementById('video');
|
||||
const canplayListener = () => {
|
||||
setCanplay(true);
|
||||
};
|
||||
videoElem.addEventListener('canplay', canplayListener);
|
||||
const promise = codeReader.decodeFromVideoDevice(
|
||||
undefined,
|
||||
'video',
|
||||
(result) => {
|
||||
if (result) {
|
||||
handleScan(result.getText());
|
||||
}
|
||||
},
|
||||
);
|
||||
return () => {
|
||||
videoElem.removeEventListener('canplay', canplayListener);
|
||||
promise
|
||||
.then((controls) => {
|
||||
if (controls) {
|
||||
controls.stop();
|
||||
}
|
||||
})
|
||||
.catch(log.info);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="qr-scanner__content__video-wrapper">
|
||||
<video
|
||||
id="video"
|
||||
style={{
|
||||
display: canplay ? 'block' : 'none',
|
||||
width: '100%',
|
||||
filter: 'blur(4px)',
|
||||
}}
|
||||
/>
|
||||
{canplay ? null : <Spinner color="#F7C06C" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
EnhancedReader.propTypes = {
|
||||
handleScan: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EnhancedReader;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user