mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Connect Ledger via WebHID (#12411)
* Connect ledger via webhid if that option is available * Explicitly setting preference for webhid * Use ledgerTransportType enum instead of booleans for ledger live and webhid preferences * Use single setLEdgerTransport preference methods and property * Temp * Lint fix * Unit test fix * Remove async keyword from setLedgerTransportPreference function definition in preferences controller * Fix ledgelive setting toggle logic * Migrate useLedgerLive preference property to ledgerTransportType * Use shared constants for ledger transport type enums * Use constant for ledger usb vendor id * Use correct property to check if ledgerLive preference is set when deciding whether to ask for webhid connection * Update eth-ledger-bridge-keyring to v0.9.0 * Only show ledger live transaction helper messages if using ledger live * Only show ledger live part of tutorial if ledger live setting is on * Fix ledger related prop type errors * Explicitly use u2f enum instead of empty string as a transport type; default transport type to webhid if available; use constants for u2f and webhid * Cleanup * Wrap ledger webhid device request in try/catch * Clean up * Lint fix * Ensure user can easily connect their ledger wallet when they need to. * Fix locales * Fix/improve locales changes * Remove unused isFirefox property from confirm-transaction-base.container.js * Disable transaction and message signing confirmation if ledger webhid requires connection * Ensure translation keys for ledger connection options in settings dropdown can be properly detected by verify-locales * Drop .component from ledger-instruction-field file name * Move renderLedgerLiveStep to module scope * Remove ledgerLive from function and message names in ledger-instruction-field * Wrap ledger connection logic in ledger-instruction-field in try catch * Clean up signature-request.component.js * Check whether the signing address, and not the selected address, is a ledger account in singature-request.container * Ensure ledger instructions and webhid connection button are shown on signature-request-original signatures * Improve webhid selection handling in select-ledger-transport-type onChange handler * Move metamask redux focused ledger selectors to metamask duck * Lint fix * Use async await in checkWebHidStatusRef.current * Remove unnecessary use of ref in ledger-instruction-field.js * Lint fix * Remove unnecessary try/catch in ledger-instruction-field.js * Check if from address, not selected address, is from a ledger account in confirm-approve * Move findKeyringForAddress to metamask duck * Fix typo in function name * Ensure isEqualCaseInsensitive handles possible differences in address casing * Fix Learn More link size in advanced settings tab * Update app/scripts/migrations/066.js Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Update ui/pages/settings/advanced-tab/advanced-tab.component.test.js Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Add jsdoc comments for new selectors * Use jest.spyOn for mocking navigator in ledger webhid migration tests * Use LEDGER_TRANSPORT_TYPES values to set proptype of ledgerTransportType * Use LEDGER_TRANSPORT_TYPES values to set proptype of ledgerTransportType * Fix font size of link in ledger connection description in advanced settings * Fix return type in setLedgerTransportPreference comment * Clean up connectHardware code for webhid connection in actions.js * Update app/scripts/migrations/066.test.js Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Update ui/ducks/metamask/metamask.js Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Add migration test for when useLedgerLive is true in a browser that supports webhid * Lint fix * Fix inline-link size Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
parent
bd83f43ae1
commit
904dad256f
@ -345,6 +345,10 @@
|
|||||||
"chromeRequiredForHardwareWallets": {
|
"chromeRequiredForHardwareWallets": {
|
||||||
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
|
"message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet."
|
||||||
},
|
},
|
||||||
|
"clickToConnectLedgerViaWebHID": {
|
||||||
|
"message": "Click here to connect your Ledger via WebHID",
|
||||||
|
"description": "Text that can be clicked to open a browser popup for connecting the ledger device via webhid"
|
||||||
|
},
|
||||||
"clickToRevealSeed": {
|
"clickToRevealSeed": {
|
||||||
"message": "Click here to reveal secret words"
|
"message": "Click here to reveal secret words"
|
||||||
},
|
},
|
||||||
@ -1251,36 +1255,42 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "You need to make use your last account before you can add a new one."
|
"message": "You need to make use your last account before you can add a new one."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
"ledgerConnectionInstructionHeader": {
|
||||||
"message": "Use Ledger Live"
|
"message": "Prior to clicking confirm:"
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
"ledgerConnectionInstructionStepFour": {
|
||||||
"message": "The new Ledger Live bridge allows you to more easily use your Ledger. Only available in Chrome."
|
"message": "Enable \"smart contract data\" or \"blind signing\" on your Ledger device"
|
||||||
|
},
|
||||||
|
"ledgerConnectionInstructionStepOne": {
|
||||||
|
"message": "Enable Use Ledger Live under Settings > Advanced"
|
||||||
|
},
|
||||||
|
"ledgerConnectionInstructionStepThree": {
|
||||||
|
"message": "Plug in your Ledger device and select the Ethereum app"
|
||||||
|
},
|
||||||
|
"ledgerConnectionInstructionStepTwo": {
|
||||||
|
"message": "Open and unlock Ledger Live App"
|
||||||
|
},
|
||||||
|
"ledgerConnectionPreferenceDescription": {
|
||||||
|
"message": "Customize how you connect your Ledger to MetaMask. $1 is recommended, but other options are available. Read more here: $2",
|
||||||
|
"description": "A description that appears above a dropdown where users can select between up to three options - Ledger Live, U2F or WebHID - depending on what is supported in their browser. $1 is the recommended browser option, it will be either WebHID or U2f. $2 is a link to an article where users can learn more, but will be the translation of the learnMore message."
|
||||||
|
},
|
||||||
|
"ledgerLive": {
|
||||||
|
"message": "Ledger Live",
|
||||||
|
"description": "The name of a desktop app that can be used with your ledger device. We can also use it to connect a users Ledger device to MetaMask."
|
||||||
},
|
},
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Ledger Live App"
|
"message": "Ledger Live App"
|
||||||
},
|
},
|
||||||
"ledgerLiveDialogHeader": {
|
|
||||||
"message": "Prior to clicking confirm:"
|
|
||||||
},
|
|
||||||
"ledgerLiveDialogStepFour": {
|
|
||||||
"message": "Enable \"smart contract data\" or \"blind signing\" on your Ledger device"
|
|
||||||
},
|
|
||||||
"ledgerLiveDialogStepOne": {
|
|
||||||
"message": "Enable Use Ledger Live under Settings > Advanced"
|
|
||||||
},
|
|
||||||
"ledgerLiveDialogStepThree": {
|
|
||||||
"message": "Plug in your Ledger device and select the Ethereum app"
|
|
||||||
},
|
|
||||||
"ledgerLiveDialogStepTwo": {
|
|
||||||
"message": "Open and unlock Ledger Live App"
|
|
||||||
},
|
|
||||||
"ledgerLocked": {
|
"ledgerLocked": {
|
||||||
"message": "Cannot connect to Ledger device. Please make sure your device is unlocked and Ethereum app is opened."
|
"message": "Cannot connect to Ledger device. Please make sure your device is unlocked and Ethereum app is opened."
|
||||||
},
|
},
|
||||||
"ledgerTimeout": {
|
"ledgerTimeout": {
|
||||||
"message": "Ledger Live is taking too long to respond or connection timeout. Make sure Ledger Live app is opened and your device is unlocked."
|
"message": "Ledger Live is taking too long to respond or connection timeout. Make sure Ledger Live app is opened and your device is unlocked."
|
||||||
},
|
},
|
||||||
|
"ledgerWebHIDNotConnectedErrorMessage": {
|
||||||
|
"message": "The ledger device was not connected. If you wish to connect your Ledger, please click 'Continue' again and approve HID connection",
|
||||||
|
"description": "An error message shown to the user during the hardware connect flow."
|
||||||
|
},
|
||||||
"letsGoSetUp": {
|
"letsGoSetUp": {
|
||||||
"message": "Yes, let’s get set up!"
|
"message": "Yes, let’s get set up!"
|
||||||
},
|
},
|
||||||
@ -1710,6 +1720,10 @@
|
|||||||
"onlyConnectTrust": {
|
"onlyConnectTrust": {
|
||||||
"message": "Only connect with sites you trust."
|
"message": "Only connect with sites you trust."
|
||||||
},
|
},
|
||||||
|
"openFullScreenForLedgerWebHid": {
|
||||||
|
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
|
||||||
|
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
|
||||||
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"message": "Optional"
|
"message": "Optional"
|
||||||
},
|
},
|
||||||
@ -1772,6 +1786,10 @@
|
|||||||
"message": "+ $1 more",
|
"message": "+ $1 more",
|
||||||
"description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items"
|
"description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items"
|
||||||
},
|
},
|
||||||
|
"preferredLedgerConnectionType": {
|
||||||
|
"message": "Preferred Ledger Connection Type",
|
||||||
|
"description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
|
||||||
|
},
|
||||||
"prev": {
|
"prev": {
|
||||||
"message": "Prev"
|
"message": "Prev"
|
||||||
},
|
},
|
||||||
@ -2817,6 +2835,10 @@
|
|||||||
"typePassword": {
|
"typePassword": {
|
||||||
"message": "Type your MetaMask password"
|
"message": "Type your MetaMask password"
|
||||||
},
|
},
|
||||||
|
"u2f": {
|
||||||
|
"message": "U2F",
|
||||||
|
"description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices."
|
||||||
|
},
|
||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "Unapproved"
|
"message": "Unapproved"
|
||||||
},
|
},
|
||||||
@ -2958,6 +2980,10 @@
|
|||||||
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
|
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
|
||||||
"description": "$1 is a clickable link."
|
"description": "$1 is a clickable link."
|
||||||
},
|
},
|
||||||
|
"webhid": {
|
||||||
|
"message": "WebHID",
|
||||||
|
"description": "Refers to a interface for connecting external devices to the browser. Used for connecting ledger to the browser. Read more here https://developer.mozilla.org/en-US/docs/Web/API/WebHID_API"
|
||||||
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"message": "Welcome to MetaMask"
|
"message": "Welcome to MetaMask"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Debe usar su última cuenta antes de poder agregar una nueva."
|
"message": "Debe usar su última cuenta antes de poder agregar una nueva."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Utilizar Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "El nuevo puente Ledger Live le permite utilizar su Ledger de forma más sencilla. Disponible solo en Google Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Aplicación de Ledger Live"
|
"message": "Aplicación de Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Debe usar su última cuenta antes de poder agregar una nueva."
|
"message": "Debe usar su última cuenta antes de poder agregar una nueva."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Utilizar Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "El nuevo puente Ledger Live le permite utilizar su Ledger de forma más sencilla. Disponible solo en Google Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Aplicación de Ledger Live"
|
"message": "Aplicación de Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "नया खाता जोड़ने से पहले आपको अपने अंतिम खाते का उपयोग करना होगा।"
|
"message": "नया खाता जोड़ने से पहले आपको अपने अंतिम खाते का उपयोग करना होगा।"
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Ledger Live का उपयोग करें"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "नया Ledger Live ब्रिज आपको अपने लेजर का अधिक आसानी से उपयोग करने की अनुमति देता है। केवल Chrome में उपलब्ध है।"
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Ledger Live ऐप"
|
"message": "Ledger Live ऐप"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Anda perlu memanfaatkan akun terakhir Anda sebelum menambahkan yang baru."
|
"message": "Anda perlu memanfaatkan akun terakhir Anda sebelum menambahkan yang baru."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Gunakan Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "Jembatan Ledger Live baru memungkinkan Anda untuk menggunakan Ledger Anda dengan lebih mudah. Hanya tersedia di Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Aplikasi Ledger Live"
|
"message": "Aplikasi Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "新しいアカウントを追加するには、その前に最後のアカウントを使用する必要があります。"
|
"message": "新しいアカウントを追加するには、その前に最後のアカウントを使用する必要があります。"
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "レジャー ライブを使用"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "新しいレジャー ライブのブリッジを使用すると、レジャーをより簡単に使用できます。Chrome でのみ利用可能。"
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "レジャー ライブのアプリ"
|
"message": "レジャー ライブのアプリ"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "새 계정을 추가하려면 먼저 마지막 계정을 사용해야 합니다."
|
"message": "새 계정을 추가하려면 먼저 마지막 계정을 사용해야 합니다."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Ledger Live 사용하기"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "새로운 Ledger Live 브리지를 통해 Ledger를 더 쉽게 사용할 수 있습니다. Chrome에서만 사용 가능합니다."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Ledger Live 앱"
|
"message": "Ledger Live 앱"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Kailangan mong gamitin ang huli mong account bago ka magdagdag ng panibago."
|
"message": "Kailangan mong gamitin ang huli mong account bago ka magdagdag ng panibago."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Gamitin ang Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "Binibigyang-daan ka ng bagong Ledger Live bridge na mas madaling magamit ang iyong Ledger. Available lang sa Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Ledger Live App"
|
"message": "Ledger Live App"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Você precisa usar sua última conta antes de adicionar uma nova."
|
"message": "Você precisa usar sua última conta antes de adicionar uma nova."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Usar Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "A nova ponte do Ledger Live permite utilizar seu Ledger mais facilmente. Disponível somente no Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Aplicativo Ledger Live"
|
"message": "Aplicativo Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Вам необходимо использовать свой последний счет, прежде чем вы сможете добавить новый."
|
"message": "Вам необходимо использовать свой последний счет, прежде чем вы сможете добавить новый."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Использовать Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "Новое решение Ledger Live Bridge упрощает использование Ledger. Доступно только в Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Приложение Ledger Live"
|
"message": "Приложение Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -1004,12 +1004,6 @@
|
|||||||
"ledgerAccountRestriction": {
|
"ledgerAccountRestriction": {
|
||||||
"message": "Bạn cần sử dụng tài khoản gần đây nhất thì mới có thể thêm một tài khoản mới."
|
"message": "Bạn cần sử dụng tài khoản gần đây nhất thì mới có thể thêm một tài khoản mới."
|
||||||
},
|
},
|
||||||
"ledgerLiveAdvancedSetting": {
|
|
||||||
"message": "Dùng Ledger Live"
|
|
||||||
},
|
|
||||||
"ledgerLiveAdvancedSettingDescription": {
|
|
||||||
"message": "Cầu Ledger Live mới cho phép bạn dùng Ledger dễ dàng hơn. Chỉ có trong Chrome."
|
|
||||||
},
|
|
||||||
"ledgerLiveApp": {
|
"ledgerLiveApp": {
|
||||||
"message": "Ứng dụng Ledger Live"
|
"message": "Ứng dụng Ledger Live"
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,7 @@ import { ethers } from 'ethers';
|
|||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
|
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
|
||||||
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
|
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets';
|
||||||
import { NETWORK_EVENTS } from './network';
|
import { NETWORK_EVENTS } from './network';
|
||||||
|
|
||||||
export default class PreferencesController {
|
export default class PreferencesController {
|
||||||
@ -58,7 +59,9 @@ export default class PreferencesController {
|
|||||||
// ENS decentralized website resolution
|
// ENS decentralized website resolution
|
||||||
ipfsGateway: 'dweb.link',
|
ipfsGateway: 'dweb.link',
|
||||||
infuraBlocked: null,
|
infuraBlocked: null,
|
||||||
useLedgerLive: false,
|
ledgerTransportType: window.navigator.hid
|
||||||
|
? LEDGER_TRANSPORT_TYPES.WEBHID
|
||||||
|
: LEDGER_TRANSPORT_TYPES.U2F,
|
||||||
...opts.initState,
|
...opts.initState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -516,21 +519,21 @@ export default class PreferencesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A setter for the `useLedgerLive` property
|
* A setter for the `useWebHid` property
|
||||||
* @param {bool} useLedgerLive - Value for ledger live support
|
* @param {string} ledgerTransportType - Either 'ledgerLive', 'webhid' or 'u2f'
|
||||||
* @returns {Promise<string>} A promise of the update to useLedgerLive
|
* @returns {string} The transport type that was set.
|
||||||
*/
|
*/
|
||||||
async setLedgerLivePreference(useLedgerLive) {
|
setLedgerTransportPreference(ledgerTransportType) {
|
||||||
this.store.updateState({ useLedgerLive });
|
this.store.updateState({ ledgerTransportType });
|
||||||
return useLedgerLive;
|
return ledgerTransportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A getter for the `useLedgerLive` property
|
* A getter for the `ledgerTransportType` property
|
||||||
* @returns {boolean} User preference of using Ledger Live
|
* @returns {boolean} User preference of using WebHid to connect Ledger
|
||||||
*/
|
*/
|
||||||
getLedgerLivePreference() {
|
getLedgerTransportPreference() {
|
||||||
return this.store.getState().useLedgerLive;
|
return this.store.getState().ledgerTransportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -841,7 +841,10 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
this.unlockHardwareWalletAccount,
|
this.unlockHardwareWalletAccount,
|
||||||
this,
|
this,
|
||||||
),
|
),
|
||||||
setLedgerLivePreference: nodeify(this.setLedgerLivePreference, this),
|
setLedgerTransportPreference: nodeify(
|
||||||
|
this.setLedgerTransportPreference,
|
||||||
|
this,
|
||||||
|
),
|
||||||
|
|
||||||
// mobile
|
// mobile
|
||||||
fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
|
fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
|
||||||
@ -1480,9 +1483,9 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
// keyring's iframe and have the setting initialized properly
|
// keyring's iframe and have the setting initialized properly
|
||||||
// Optimistically called to not block Metamask login due to
|
// Optimistically called to not block Metamask login due to
|
||||||
// Ledger Keyring GitHub downtime
|
// Ledger Keyring GitHub downtime
|
||||||
this.setLedgerLivePreference(
|
const transportPreference = this.preferencesController.getLedgerTransportPreference();
|
||||||
this.preferencesController.getLedgerLivePreference(),
|
|
||||||
);
|
this.setLedgerTransportPreference(transportPreference);
|
||||||
|
|
||||||
return this.keyringController.fullUpdate();
|
return this.keyringController.fullUpdate();
|
||||||
}
|
}
|
||||||
@ -2984,16 +2987,18 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
* Sets the Ledger Live preference to use for Ledger hardware wallet support
|
* Sets the Ledger Live preference to use for Ledger hardware wallet support
|
||||||
* @param {bool} bool - the value representing if the users wants to use Ledger Live
|
* @param {bool} bool - the value representing if the users wants to use Ledger Live
|
||||||
*/
|
*/
|
||||||
async setLedgerLivePreference(bool) {
|
async setLedgerTransportPreference(transportType) {
|
||||||
const currentValue = this.preferencesController.getLedgerLivePreference();
|
const currentValue = this.preferencesController.getLedgerTransportPreference();
|
||||||
this.preferencesController.setLedgerLivePreference(bool);
|
const newValue = this.preferencesController.setLedgerTransportPreference(
|
||||||
|
transportType,
|
||||||
|
);
|
||||||
|
|
||||||
const keyring = await this.getKeyringForDevice('ledger');
|
const keyring = await this.getKeyringForDevice('ledger');
|
||||||
if (keyring?.updateTransportMethod) {
|
if (keyring?.updateTransportMethod) {
|
||||||
return keyring.updateTransportMethod(bool).catch((e) => {
|
return keyring.updateTransportMethod(newValue).catch((e) => {
|
||||||
// If there was an error updating the transport, we should
|
// If there was an error updating the transport, we should
|
||||||
// fall back to the original value
|
// fall back to the original value
|
||||||
this.preferencesController.setLedgerLivePreference(currentValue);
|
this.preferencesController.setLedgerTransportPreference(currentValue);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
37
app/scripts/migrations/066.js
Normal file
37
app/scripts/migrations/066.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets';
|
||||||
|
|
||||||
|
const version = 66;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the useLedgerLive boolean property to the ledgerTransportType enum
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
version,
|
||||||
|
async migrate(originalVersionedData) {
|
||||||
|
const versionedData = cloneDeep(originalVersionedData);
|
||||||
|
versionedData.meta.version = version;
|
||||||
|
const state = versionedData.data;
|
||||||
|
const newState = transformState(state);
|
||||||
|
versionedData.data = newState;
|
||||||
|
return versionedData;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformState(state) {
|
||||||
|
const defaultTransportType = window.navigator.hid
|
||||||
|
? LEDGER_TRANSPORT_TYPES.WEBHID
|
||||||
|
: LEDGER_TRANSPORT_TYPES.U2F;
|
||||||
|
const useLedgerLive = Boolean(state.PreferencesController?.useLedgerLive);
|
||||||
|
const newState = {
|
||||||
|
...state,
|
||||||
|
PreferencesController: {
|
||||||
|
...state?.PreferencesController,
|
||||||
|
ledgerTransportType: useLedgerLive
|
||||||
|
? LEDGER_TRANSPORT_TYPES.LIVE
|
||||||
|
: defaultTransportType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
delete newState.PreferencesController.useLedgerLive;
|
||||||
|
return newState;
|
||||||
|
}
|
116
app/scripts/migrations/066.test.js
Normal file
116
app/scripts/migrations/066.test.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../shared/constants/hardware-wallets';
|
||||||
|
import migration66 from './066';
|
||||||
|
|
||||||
|
describe('migration #66', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the version metadata', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {
|
||||||
|
version: 65,
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(newStorage.meta).toStrictEqual({
|
||||||
|
version: 66,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if no preferences controller exists and webhid is not available', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if no useLedgerLive property exists and webhid is not available', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {
|
||||||
|
PreferencesController: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ledgerTransportType to `u2f` if useLedgerLive is false and webhid is not available', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {
|
||||||
|
PreferencesController: {
|
||||||
|
useLedgerLive: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.U2F);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ledgerTransportType to `webhid` if useLedgerLive is false and webhid is available', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {
|
||||||
|
PreferencesController: {
|
||||||
|
useLedgerLive: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(window, 'navigator', 'get')
|
||||||
|
.mockImplementation(() => ({ hid: true }));
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.WEBHID);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set ledgerTransportType to `ledgerLive` if useLedgerLive is true', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {
|
||||||
|
PreferencesController: {
|
||||||
|
useLedgerLive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual('ledgerLive');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change ledgerTransportType if useLedgerLive is true and webhid is available', async () => {
|
||||||
|
const oldStorage = {
|
||||||
|
meta: {},
|
||||||
|
data: {
|
||||||
|
PreferencesController: {
|
||||||
|
useLedgerLive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(window, 'navigator', 'get')
|
||||||
|
.mockImplementation(() => ({ hid: true }));
|
||||||
|
const newStorage = await migration66.migrate(oldStorage);
|
||||||
|
expect(
|
||||||
|
newStorage.data.PreferencesController.ledgerTransportType,
|
||||||
|
).toStrictEqual(LEDGER_TRANSPORT_TYPES.LIVE);
|
||||||
|
});
|
||||||
|
});
|
@ -69,6 +69,7 @@ import m062 from './062';
|
|||||||
import m063 from './063';
|
import m063 from './063';
|
||||||
import m064 from './064';
|
import m064 from './064';
|
||||||
import m065 from './065';
|
import m065 from './065';
|
||||||
|
import m066 from './066';
|
||||||
|
|
||||||
const migrations = [
|
const migrations = [
|
||||||
m002,
|
m002,
|
||||||
@ -135,6 +136,7 @@ const migrations = [
|
|||||||
m063,
|
m063,
|
||||||
m064,
|
m064,
|
||||||
m065,
|
m065,
|
||||||
|
m066,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default migrations;
|
export default migrations;
|
||||||
|
@ -111,7 +111,11 @@ export default class ExtensionPlatform {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
openExtensionInBrowser(route = null, queryString = null) {
|
openExtensionInBrowser(
|
||||||
|
route = null,
|
||||||
|
queryString = null,
|
||||||
|
keepWindowOpen = false,
|
||||||
|
) {
|
||||||
let extensionURL = extension.runtime.getURL('home.html');
|
let extensionURL = extension.runtime.getURL('home.html');
|
||||||
|
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
@ -122,7 +126,10 @@ export default class ExtensionPlatform {
|
|||||||
extensionURL += `#${route}`;
|
extensionURL += `#${route}`;
|
||||||
}
|
}
|
||||||
this.openTab({ url: extensionURL });
|
this.openTab({ url: extensionURL });
|
||||||
if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) {
|
if (
|
||||||
|
getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND &&
|
||||||
|
!keepWindowOpen
|
||||||
|
) {
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
"@material-ui/core": "^4.11.0",
|
"@material-ui/core": "^4.11.0",
|
||||||
"@metamask/contract-metadata": "^1.28.0",
|
"@metamask/contract-metadata": "^1.28.0",
|
||||||
"@metamask/controllers": "^17.0.0",
|
"@metamask/controllers": "^17.0.0",
|
||||||
"@metamask/eth-ledger-bridge-keyring": "^0.7.0",
|
"@metamask/eth-ledger-bridge-keyring": "^0.9.0",
|
||||||
"@metamask/eth-token-tracker": "^3.0.1",
|
"@metamask/eth-token-tracker": "^3.0.1",
|
||||||
"@metamask/etherscan-link": "^2.1.0",
|
"@metamask/etherscan-link": "^2.1.0",
|
||||||
"@metamask/jazzicon": "^2.0.0",
|
"@metamask/jazzicon": "^2.0.0",
|
||||||
|
@ -7,3 +7,20 @@ export const KEYRING_TYPES = {
|
|||||||
LEDGER: 'Ledger Hardware',
|
LEDGER: 'Ledger Hardware',
|
||||||
TREZOR: 'Trezor Hardware',
|
TREZOR: 'Trezor Hardware',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for setting the users preference for ledger transport type
|
||||||
|
*/
|
||||||
|
export const LEDGER_TRANSPORT_TYPES = {
|
||||||
|
LIVE: 'ledgerLive',
|
||||||
|
WEBHID: 'webhid',
|
||||||
|
U2F: 'u2f',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LEDGER_USB_VENDOR_ID = '0x2c97';
|
||||||
|
|
||||||
|
export const WEBHID_CONNECTED_STATUSES = {
|
||||||
|
CONNECTED: 'connected',
|
||||||
|
NOT_CONNECTED: 'notConnected',
|
||||||
|
UNKNOWN: 'unknown',
|
||||||
|
};
|
||||||
|
1
ui/components/app/ledger-instruction-field/index.js
Normal file
1
ui/components/app/ledger-instruction-field/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './ledger-instruction-field';
|
@ -0,0 +1,153 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
LEDGER_TRANSPORT_TYPES,
|
||||||
|
LEDGER_USB_VENDOR_ID,
|
||||||
|
WEBHID_CONNECTED_STATUSES,
|
||||||
|
} from '../../../../shared/constants/hardware-wallets';
|
||||||
|
import {
|
||||||
|
PLATFORM_FIREFOX,
|
||||||
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
|
} from '../../../../shared/constants/app';
|
||||||
|
|
||||||
|
import {
|
||||||
|
setLedgerWebHidConnectedStatus,
|
||||||
|
getLedgerWebHidConnectedStatus,
|
||||||
|
} from '../../../ducks/app/app';
|
||||||
|
|
||||||
|
import Typography from '../../ui/typography/typography';
|
||||||
|
import Button from '../../ui/button';
|
||||||
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
|
import {
|
||||||
|
COLORS,
|
||||||
|
FONT_WEIGHT,
|
||||||
|
TYPOGRAPHY,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import Dialog from '../../ui/dialog';
|
||||||
|
import {
|
||||||
|
getPlatform,
|
||||||
|
getEnvironmentType,
|
||||||
|
} from '../../../../app/scripts/lib/util';
|
||||||
|
import { getLedgerTransportType } from '../../../ducks/metamask/metamask';
|
||||||
|
|
||||||
|
const renderInstructionStep = (text, show = true, color = COLORS.PRIMARY3) => {
|
||||||
|
return (
|
||||||
|
show && (
|
||||||
|
<Typography
|
||||||
|
boxProps={{ margin: 0 }}
|
||||||
|
color={color}
|
||||||
|
fontWeight={FONT_WEIGHT.BOLD}
|
||||||
|
variant={TYPOGRAPHY.H7}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LedgerInstructionField({ showDataInstruction }) {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const webHidConnectedStatus = useSelector(getLedgerWebHidConnectedStatus);
|
||||||
|
const ledgerTransportType = useSelector(getLedgerTransportType);
|
||||||
|
const environmentType = getEnvironmentType();
|
||||||
|
const environmentTypeIsFullScreen =
|
||||||
|
environmentType === ENVIRONMENT_TYPE_FULLSCREEN;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialConnectedDeviceCheck = async () => {
|
||||||
|
if (
|
||||||
|
ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID &&
|
||||||
|
webHidConnectedStatus !== WEBHID_CONNECTED_STATUSES.CONNECTED
|
||||||
|
) {
|
||||||
|
const devices = await window.navigator.hid.getDevices();
|
||||||
|
const webHidIsConnected = devices.some(
|
||||||
|
(device) => device.vendorId === Number(LEDGER_USB_VENDOR_ID),
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
setLedgerWebHidConnectedStatus(
|
||||||
|
webHidIsConnected
|
||||||
|
? WEBHID_CONNECTED_STATUSES.CONNECTED
|
||||||
|
: WEBHID_CONNECTED_STATUSES.NOT_CONNECTED,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
initialConnectedDeviceCheck();
|
||||||
|
}, [dispatch, ledgerTransportType, webHidConnectedStatus]);
|
||||||
|
|
||||||
|
const usingLedgerLive = ledgerTransportType === LEDGER_TRANSPORT_TYPES.LIVE;
|
||||||
|
const usingWebHID = ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID;
|
||||||
|
|
||||||
|
const isFirefox = getPlatform() === PLATFORM_FIREFOX;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="confirm-detail-row">
|
||||||
|
<Dialog type="message">
|
||||||
|
<div className="ledger-live-dialog">
|
||||||
|
{renderInstructionStep(t('ledgerConnectionInstructionHeader'))}
|
||||||
|
{renderInstructionStep(
|
||||||
|
`- ${t('ledgerConnectionInstructionStepOne')}`,
|
||||||
|
!isFirefox && usingLedgerLive,
|
||||||
|
)}
|
||||||
|
{renderInstructionStep(
|
||||||
|
`- ${t('ledgerConnectionInstructionStepTwo')}`,
|
||||||
|
!isFirefox && usingLedgerLive,
|
||||||
|
)}
|
||||||
|
{renderInstructionStep(
|
||||||
|
`- ${t('ledgerConnectionInstructionStepThree')}`,
|
||||||
|
)}
|
||||||
|
{renderInstructionStep(
|
||||||
|
`- ${t('ledgerConnectionInstructionStepFour')}`,
|
||||||
|
showDataInstruction,
|
||||||
|
)}
|
||||||
|
{renderInstructionStep(
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
onClick={async () => {
|
||||||
|
if (environmentTypeIsFullScreen) {
|
||||||
|
const connectedDevices = await window.navigator.hid.requestDevice(
|
||||||
|
{
|
||||||
|
filters: [{ vendorId: LEDGER_USB_VENDOR_ID }],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const webHidIsConnected = connectedDevices.some(
|
||||||
|
(device) =>
|
||||||
|
device.vendorId === Number(LEDGER_USB_VENDOR_ID),
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
setLedgerWebHidConnectedStatus({
|
||||||
|
webHidConnectedStatus: webHidIsConnected
|
||||||
|
? WEBHID_CONNECTED_STATUSES.CONNECTED
|
||||||
|
: WEBHID_CONNECTED_STATUSES.NOT_CONNECTED,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
global.platform.openExtensionInBrowser(null, null, true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{environmentTypeIsFullScreen
|
||||||
|
? t('clickToConnectLedgerViaWebHID')
|
||||||
|
: t('openFullScreenForLedgerWebHid')}
|
||||||
|
</Button>
|
||||||
|
</span>,
|
||||||
|
usingWebHID &&
|
||||||
|
webHidConnectedStatus ===
|
||||||
|
WEBHID_CONNECTED_STATUSES.NOT_CONNECTED,
|
||||||
|
COLORS.SECONDARY1,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LedgerInstructionField.propTypes = {
|
||||||
|
showDataInstruction: PropTypes.bool,
|
||||||
|
};
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { stripHexPrefix } from 'ethereumjs-util';
|
import { stripHexPrefix } from 'ethereumjs-util';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { ObjectInspector } from 'react-inspector';
|
import { ObjectInspector } from 'react-inspector';
|
||||||
|
import LedgerInstructionField from '../ledger-instruction-field';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
@ -36,6 +37,8 @@ export default class SignatureRequestOriginal extends Component {
|
|||||||
sign: PropTypes.func.isRequired,
|
sign: PropTypes.func.isRequired,
|
||||||
txData: PropTypes.object.isRequired,
|
txData: PropTypes.object.isRequired,
|
||||||
domainMetadata: PropTypes.object,
|
domainMetadata: PropTypes.object,
|
||||||
|
hardwareWalletRequiresConnection: PropTypes.bool,
|
||||||
|
isLedgerWallet: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -286,6 +289,7 @@ export default class SignatureRequestOriginal extends Component {
|
|||||||
mostRecentOverviewPage,
|
mostRecentOverviewPage,
|
||||||
sign,
|
sign,
|
||||||
txData: { type },
|
txData: { type },
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { metricsEvent, t } = this.context;
|
const { metricsEvent, t } = this.context;
|
||||||
|
|
||||||
@ -319,6 +323,7 @@ export default class SignatureRequestOriginal extends Component {
|
|||||||
type="primary"
|
type="primary"
|
||||||
large
|
large
|
||||||
className="request-signature__footer__sign-button"
|
className="request-signature__footer__sign-button"
|
||||||
|
disabled={hardwareWalletRequiresConnection}
|
||||||
onClick={async (event) => {
|
onClick={async (event) => {
|
||||||
this._removeBeforeUnload();
|
this._removeBeforeUnload();
|
||||||
await sign(event);
|
await sign(event);
|
||||||
@ -347,6 +352,11 @@ export default class SignatureRequestOriginal extends Component {
|
|||||||
<div className="request-signature__container">
|
<div className="request-signature__container">
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
{this.renderBody()}
|
{this.renderBody()}
|
||||||
|
{this.props.isLedgerWallet ? (
|
||||||
|
<div className="confirm-approve-content__ledger-instruction-wrapper">
|
||||||
|
<LedgerInstructionField showDataInstruction />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{this.renderFooter()}
|
{this.renderFooter()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -8,18 +8,32 @@ import {
|
|||||||
accountsWithSendEtherInfoSelector,
|
accountsWithSendEtherInfoSelector,
|
||||||
conversionRateSelector,
|
conversionRateSelector,
|
||||||
getDomainMetadata,
|
getDomainMetadata,
|
||||||
|
doesAddressRequireLedgerHidConnection,
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
import { getAccountByAddress } from '../../../helpers/utils/util';
|
import { getAccountByAddress } from '../../../helpers/utils/util';
|
||||||
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
|
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||||
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
||||||
|
import { isAddressLedger } from '../../../ducks/metamask/metamask';
|
||||||
import SignatureRequestOriginal from './signature-request-original.component';
|
import SignatureRequestOriginal from './signature-request-original.component';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state, ownProps) {
|
||||||
|
const {
|
||||||
|
msgParams: { from },
|
||||||
|
} = ownProps.txData;
|
||||||
|
|
||||||
|
const hardwareWalletRequiresConnection = doesAddressRequireLedgerHidConnection(
|
||||||
|
state,
|
||||||
|
from,
|
||||||
|
);
|
||||||
|
const isLedgerWallet = isAddressLedger(state, from);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requester: null,
|
requester: null,
|
||||||
requesterAddress: null,
|
requesterAddress: null,
|
||||||
conversionRate: conversionRateSelector(state),
|
conversionRate: conversionRateSelector(state),
|
||||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
|
isLedgerWallet,
|
||||||
// not passed to component
|
// not passed to component
|
||||||
allAccounts: accountsWithSendEtherInfoSelector(state),
|
allAccounts: accountsWithSendEtherInfoSelector(state),
|
||||||
domainMetadata: getDomainMetadata(state),
|
domainMetadata: getDomainMetadata(state),
|
||||||
|
@ -6,6 +6,7 @@ export default class SignatureRequestFooter extends PureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
cancelAction: PropTypes.func.isRequired,
|
cancelAction: PropTypes.func.isRequired,
|
||||||
signAction: PropTypes.func.isRequired,
|
signAction: PropTypes.func.isRequired,
|
||||||
|
disabled: PropTypes.boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -13,13 +14,13 @@ export default class SignatureRequestFooter extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { cancelAction, signAction } = this.props;
|
const { cancelAction, signAction, disabled = false } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="signature-request-footer">
|
<div className="signature-request-footer">
|
||||||
<Button onClick={cancelAction} type="secondary" large>
|
<Button onClick={cancelAction} type="secondary" large>
|
||||||
{this.context.t('cancel')}
|
{this.context.t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={signAction} type="primary" large>
|
<Button onClick={signAction} type="primary" disabled={disabled} large>
|
||||||
{this.context.t('sign')}
|
{this.context.t('sign')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
|
||||||
import Identicon from '../../ui/identicon';
|
import Identicon from '../../ui/identicon';
|
||||||
|
import LedgerInstructionField from '../ledger-instruction-field';
|
||||||
import Header from './signature-request-header';
|
import Header from './signature-request-header';
|
||||||
import Footer from './signature-request-footer';
|
import Footer from './signature-request-footer';
|
||||||
import Message from './signature-request-message';
|
import Message from './signature-request-message';
|
||||||
@ -15,10 +16,11 @@ export default class SignatureRequest extends PureComponent {
|
|||||||
balance: PropTypes.string,
|
balance: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
isLedgerWallet: PropTypes.bool,
|
||||||
clearConfirmTransaction: PropTypes.func.isRequired,
|
clearConfirmTransaction: PropTypes.func.isRequired,
|
||||||
cancel: PropTypes.func.isRequired,
|
cancel: PropTypes.func.isRequired,
|
||||||
sign: PropTypes.func.isRequired,
|
sign: PropTypes.func.isRequired,
|
||||||
|
hardwareWalletRequiresConnection: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -69,6 +71,8 @@ export default class SignatureRequest extends PureComponent {
|
|||||||
},
|
},
|
||||||
cancel,
|
cancel,
|
||||||
sign,
|
sign,
|
||||||
|
isLedgerWallet,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { address: fromAddress } = fromAccount;
|
const { address: fromAddress } = fromAccount;
|
||||||
const { message, domain = {} } = JSON.parse(data);
|
const { message, domain = {} } = JSON.parse(data);
|
||||||
@ -128,8 +132,17 @@ export default class SignatureRequest extends PureComponent {
|
|||||||
{this.formatWallet(fromAddress)}
|
{this.formatWallet(fromAddress)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{isLedgerWallet ? (
|
||||||
|
<div className="confirm-approve-content__ledger-instruction-wrapper">
|
||||||
|
<LedgerInstructionField showDataInstruction />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<Message data={message} />
|
<Message data={message} />
|
||||||
<Footer cancelAction={onCancel} signAction={onSign} />
|
<Footer
|
||||||
|
cancelAction={onCancel}
|
||||||
|
signAction={onSign}
|
||||||
|
disabled={hardwareWalletRequiresConnection}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
|
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||||
import { accountsWithSendEtherInfoSelector } from '../../../selectors';
|
import {
|
||||||
|
accountsWithSendEtherInfoSelector,
|
||||||
|
doesAddressRequireLedgerHidConnection,
|
||||||
|
} from '../../../selectors';
|
||||||
|
import { isAddressLedger } from '../../../ducks/metamask/metamask';
|
||||||
import { getAccountByAddress } from '../../../helpers/utils/util';
|
import { getAccountByAddress } from '../../../helpers/utils/util';
|
||||||
import { MESSAGE_TYPE } from '../../../../shared/constants/app';
|
import { MESSAGE_TYPE } from '../../../../shared/constants/app';
|
||||||
import SignatureRequest from './signature-request.component';
|
import SignatureRequest from './signature-request.component';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state, ownProps) {
|
||||||
|
const { txData } = ownProps;
|
||||||
|
const {
|
||||||
|
msgParams: { from },
|
||||||
|
} = txData;
|
||||||
|
const hardwareWalletRequiresConnection = doesAddressRequireLedgerHidConnection(
|
||||||
|
state,
|
||||||
|
from,
|
||||||
|
);
|
||||||
|
const isLedgerWallet = isAddressLedger(state, from);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isLedgerWallet,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
// not forwarded to component
|
// not forwarded to component
|
||||||
allAccounts: accountsWithSendEtherInfoSelector(state),
|
allAccounts: accountsWithSendEtherInfoSelector(state),
|
||||||
};
|
};
|
||||||
@ -19,7 +35,11 @@ function mapDispatchToProps(dispatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mergeProps(stateProps, dispatchProps, ownProps) {
|
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||||
const { allAccounts } = stateProps;
|
const {
|
||||||
|
allAccounts,
|
||||||
|
isLedgerWallet,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
|
} = stateProps;
|
||||||
const {
|
const {
|
||||||
signPersonalMessage,
|
signPersonalMessage,
|
||||||
signTypedMessage,
|
signTypedMessage,
|
||||||
@ -58,6 +78,8 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
|
|||||||
txData,
|
txData,
|
||||||
cancel,
|
cancel,
|
||||||
sign,
|
sign,
|
||||||
|
isLedgerWallet,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { WEBHID_CONNECTED_STATUSES } from '../../../shared/constants/hardware-wallets';
|
||||||
import * as actionConstants from '../../store/actionConstants';
|
import * as actionConstants from '../../store/actionConstants';
|
||||||
|
|
||||||
// actionConstants
|
// actionConstants
|
||||||
@ -48,6 +49,7 @@ export default function reduceApp(state = {}, action) {
|
|||||||
testKey: null,
|
testKey: null,
|
||||||
},
|
},
|
||||||
gasLoadingAnimationIsShowing: false,
|
gasLoadingAnimationIsShowing: false,
|
||||||
|
ledgerWebHidConnectedStatus: WEBHID_CONNECTED_STATUSES.UNKNOWN,
|
||||||
...state,
|
...state,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -340,6 +342,12 @@ export default function reduceApp(state = {}, action) {
|
|||||||
gasLoadingAnimationIsShowing: action.value,
|
gasLoadingAnimationIsShowing: action.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case actionConstants.SET_WEBHID_CONNECTED_STATUS:
|
||||||
|
return {
|
||||||
|
...appState,
|
||||||
|
ledgerWebHidConnectedStatus: action.value,
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return appState;
|
return appState;
|
||||||
}
|
}
|
||||||
@ -363,6 +371,10 @@ export function toggleGasLoadingAnimation(value) {
|
|||||||
return { type: actionConstants.TOGGLE_GAS_LOADING_ANIMATION, value };
|
return { type: actionConstants.TOGGLE_GAS_LOADING_ANIMATION, value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setLedgerWebHidConnectedStatus(value) {
|
||||||
|
return { type: actionConstants.SET_WEBHID_CONNECTED_STATUS, value };
|
||||||
|
}
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
export function getQrCodeData(state) {
|
export function getQrCodeData(state) {
|
||||||
return state.appState.qrCodeData;
|
return state.appState.qrCodeData;
|
||||||
@ -371,3 +383,7 @@ export function getQrCodeData(state) {
|
|||||||
export function getGasLoadingAnimationIsShowing(state) {
|
export function getGasLoadingAnimationIsShowing(state) {
|
||||||
return state.appState.gasLoadingAnimationIsShowing;
|
return state.appState.gasLoadingAnimationIsShowing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLedgerWebHidConnectedStatus(state) {
|
||||||
|
return state.appState.ledgerWebHidConnectedStatus;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { addHexPrefix, isHexString } from 'ethereumjs-util';
|
import { addHexPrefix, isHexString, stripHexPrefix } from 'ethereumjs-util';
|
||||||
import * as actionConstants from '../../store/actionConstants';
|
import * as actionConstants from '../../store/actionConstants';
|
||||||
import { ALERT_TYPES } from '../../../shared/constants/alerts';
|
import { ALERT_TYPES } from '../../../shared/constants/alerts';
|
||||||
import { NETWORK_TYPE_RPC } from '../../../shared/constants/network';
|
import { NETWORK_TYPE_RPC } from '../../../shared/constants/network';
|
||||||
@ -10,7 +10,9 @@ import {
|
|||||||
import { updateTransaction } from '../../store/actions';
|
import { updateTransaction } from '../../store/actions';
|
||||||
import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck';
|
import { setCustomGasLimit, setCustomGasPrice } from '../gas/gas.duck';
|
||||||
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util';
|
import { decGWEIToHexWEI } from '../../helpers/utils/conversions.util';
|
||||||
|
import { isEqualCaseInsensitive } from '../../helpers/utils/util';
|
||||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||||
|
import { KEYRING_TYPES } from '../../../shared/constants/hardware-wallets';
|
||||||
|
|
||||||
export default function reduceMetamask(state = {}, action) {
|
export default function reduceMetamask(state = {}, action) {
|
||||||
const metamaskState = {
|
const metamaskState = {
|
||||||
@ -340,3 +342,59 @@ export function getIsUnlocked(state) {
|
|||||||
export function getSeedPhraseBackedUp(state) {
|
export function getSeedPhraseBackedUp(state) {
|
||||||
return state.metamask.seedPhraseBackedUp;
|
return state.metamask.seedPhraseBackedUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the redux state object and an address, finds a keyring that contains that address, if one exists
|
||||||
|
*
|
||||||
|
* @param {Object} state - the redux state object
|
||||||
|
* @param {string} address - the address to search for among the keyring addresses
|
||||||
|
* @returns {Object|undefined} The keyring which contains the passed address, or undefined
|
||||||
|
*/
|
||||||
|
export function findKeyringForAddress(state, address) {
|
||||||
|
const keyring = state.metamask.keyrings.find((kr) => {
|
||||||
|
return kr.accounts.some((account) => {
|
||||||
|
return (
|
||||||
|
isEqualCaseInsensitive(account, addHexPrefix(address)) ||
|
||||||
|
isEqualCaseInsensitive(account, stripHexPrefix(address))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return keyring;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the redux state object, returns the users preferred ledger transport type
|
||||||
|
*
|
||||||
|
* @param {Object} state - the redux state object
|
||||||
|
* @returns {string} The users preferred ledger transport type. One of'ledgerLive', 'webhid' or 'u2f'
|
||||||
|
*/
|
||||||
|
export function getLedgerTransportType(state) {
|
||||||
|
return state.metamask.ledgerTransportType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the redux state object and an address, returns a boolean indicating whether the passed address is part of a Ledger keyring
|
||||||
|
*
|
||||||
|
* @param {Object} state - the redux state object
|
||||||
|
* @param {string} address - the address to search for among all keyring addresses
|
||||||
|
* @returns {boolean} true if the passed address is part of a ledger keyring, and false otherwise
|
||||||
|
*/
|
||||||
|
export function isAddressLedger(state, address) {
|
||||||
|
const keyring = findKeyringForAddress(state, address);
|
||||||
|
|
||||||
|
return keyring?.type === KEYRING_TYPES.LEDGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the redux state object, returns a boolean indicating whether the user has any Ledger accounts added to MetaMask (i.e. Ledger keyrings
|
||||||
|
* in state)
|
||||||
|
*
|
||||||
|
* @param {Object} state - the redux state object
|
||||||
|
* @returns {boolean} true if the user has a Ledger account and false otherwise
|
||||||
|
*/
|
||||||
|
export function doesUserHaveALedgerAccount(state) {
|
||||||
|
return state.metamask.keyrings.some((kr) => {
|
||||||
|
return kr.type === KEYRING_TYPES.LEDGER;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
import Box from '../../../components/ui/box';
|
import Box from '../../../components/ui/box';
|
||||||
import Button from '../../../components/ui/button';
|
import Button from '../../../components/ui/button';
|
||||||
|
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
|
||||||
|
|
||||||
export default class ConfirmApproveContent extends Component {
|
export default class ConfirmApproveContent extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -44,6 +45,8 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
nextNonce: PropTypes.number,
|
nextNonce: PropTypes.number,
|
||||||
showCustomizeNonceModal: PropTypes.func,
|
showCustomizeNonceModal: PropTypes.func,
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
|
txData: PropTypes.object,
|
||||||
|
ledgerWalletRequiredHidConnection: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -238,6 +241,8 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
tokenBalance,
|
tokenBalance,
|
||||||
useNonceField,
|
useNonceField,
|
||||||
warning,
|
warning,
|
||||||
|
txData,
|
||||||
|
ledgerWalletRequiredHidConnection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showFullTxDetails } = this.state;
|
const { showFullTxDetails } = this.state;
|
||||||
|
|
||||||
@ -346,6 +351,14 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ledgerWalletRequiredHidConnection ? (
|
||||||
|
<div className="confirm-approve-content__ledger-instruction-wrapper">
|
||||||
|
<LedgerInstructionField
|
||||||
|
showDataInstruction={Boolean(txData.txParams?.data)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{showFullTxDetails ? (
|
{showFullTxDetails ? (
|
||||||
<div className="confirm-approve-content__full-tx-content">
|
<div className="confirm-approve-content__full-tx-content">
|
||||||
<div className="confirm-approve-content__permission">
|
<div className="confirm-approve-content__permission">
|
||||||
|
@ -161,6 +161,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__ledger-instruction-wrapper {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
&__transaction-details-content {
|
&__transaction-details-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
getUseNonceField,
|
getUseNonceField,
|
||||||
getCustomNonceValue,
|
getCustomNonceValue,
|
||||||
getNextSuggestedNonce,
|
getNextSuggestedNonce,
|
||||||
|
doesAddressRequireLedgerHidConnection,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
||||||
@ -35,12 +36,18 @@ import { isEqualCaseInsensitive } from '../../helpers/utils/util';
|
|||||||
import { getCustomTxParamsData } from './confirm-approve.util';
|
import { getCustomTxParamsData } from './confirm-approve.util';
|
||||||
import ConfirmApproveContent from './confirm-approve-content';
|
import ConfirmApproveContent from './confirm-approve-content';
|
||||||
|
|
||||||
|
const doesAddressRequireLedgerHidConnectionByFromAddress = (address) => (
|
||||||
|
state,
|
||||||
|
) => {
|
||||||
|
return doesAddressRequireLedgerHidConnection(state, address);
|
||||||
|
};
|
||||||
|
|
||||||
export default function ConfirmApprove() {
|
export default function ConfirmApprove() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { id: paramsTransactionId } = useParams();
|
const { id: paramsTransactionId } = useParams();
|
||||||
const {
|
const {
|
||||||
id: transactionId,
|
id: transactionId,
|
||||||
txParams: { to: tokenAddress, data } = {},
|
txParams: { to: tokenAddress, data, from } = {},
|
||||||
} = useSelector(txDataSelector);
|
} = useSelector(txDataSelector);
|
||||||
|
|
||||||
const currentCurrency = useSelector(getCurrentCurrency);
|
const currentCurrency = useSelector(getCurrentCurrency);
|
||||||
@ -52,6 +59,10 @@ export default function ConfirmApprove() {
|
|||||||
const nextNonce = useSelector(getNextSuggestedNonce);
|
const nextNonce = useSelector(getNextSuggestedNonce);
|
||||||
const customNonceValue = useSelector(getCustomNonceValue);
|
const customNonceValue = useSelector(getCustomNonceValue);
|
||||||
|
|
||||||
|
const ledgerWalletRequiredHidConnection = useSelector(
|
||||||
|
doesAddressRequireLedgerHidConnectionByFromAddress(from),
|
||||||
|
);
|
||||||
|
|
||||||
const transaction =
|
const transaction =
|
||||||
currentNetworkTxList.find(
|
currentNetworkTxList.find(
|
||||||
({ id }) => id === (Number(paramsTransactionId) || transactionId),
|
({ id }) => id === (Number(paramsTransactionId) || transactionId),
|
||||||
@ -207,6 +218,10 @@ export default function ConfirmApprove() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
warning={submitWarning}
|
warning={submitWarning}
|
||||||
|
txData={transaction}
|
||||||
|
ledgerWalletRequiredHidConnection={
|
||||||
|
ledgerWalletRequiredHidConnection
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{showCustomizeGasPopover && (
|
{showCustomizeGasPopover && (
|
||||||
<EditGasPopover
|
<EditGasPopover
|
||||||
|
@ -35,12 +35,11 @@ import TransactionDetailItem from '../../components/app/transaction-detail-item/
|
|||||||
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
||||||
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
|
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
|
||||||
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
||||||
import Dialog from '../../components/ui/dialog';
|
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COLORS,
|
COLORS,
|
||||||
FONT_STYLE,
|
FONT_STYLE,
|
||||||
FONT_WEIGHT,
|
|
||||||
TYPOGRAPHY,
|
TYPOGRAPHY,
|
||||||
} from '../../helpers/constants/design-system';
|
} from '../../helpers/constants/design-system';
|
||||||
import {
|
import {
|
||||||
@ -127,9 +126,9 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
isMainnet: PropTypes.bool,
|
isMainnet: PropTypes.bool,
|
||||||
gasFeeIsCustom: PropTypes.bool,
|
gasFeeIsCustom: PropTypes.bool,
|
||||||
showLedgerSteps: PropTypes.bool.isRequired,
|
showLedgerSteps: PropTypes.bool.isRequired,
|
||||||
isFirefox: PropTypes.bool.isRequired,
|
|
||||||
nativeCurrency: PropTypes.string,
|
nativeCurrency: PropTypes.string,
|
||||||
supportsEIP1559: PropTypes.bool,
|
supportsEIP1559: PropTypes.bool,
|
||||||
|
hardwareWalletRequiresConnection: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -310,7 +309,6 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
isMainnet,
|
isMainnet,
|
||||||
showLedgerSteps,
|
showLedgerSteps,
|
||||||
isFirefox,
|
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
@ -405,46 +403,6 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const renderLedgerLiveStep = (text, show = true) => {
|
|
||||||
return (
|
|
||||||
show && (
|
|
||||||
<Typography
|
|
||||||
boxProps={{ margin: 0 }}
|
|
||||||
color={COLORS.PRIMARY3}
|
|
||||||
fontWeight={FONT_WEIGHT.BOLD}
|
|
||||||
variant={TYPOGRAPHY.H7}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ledgerInstructionField = showLedgerSteps ? (
|
|
||||||
<div>
|
|
||||||
<div className="confirm-detail-row">
|
|
||||||
<Dialog type="message">
|
|
||||||
<div className="ledger-live-dialog">
|
|
||||||
{renderLedgerLiveStep(t('ledgerLiveDialogHeader'))}
|
|
||||||
{renderLedgerLiveStep(
|
|
||||||
`- ${t('ledgerLiveDialogStepOne')}`,
|
|
||||||
!isFirefox,
|
|
||||||
)}
|
|
||||||
{renderLedgerLiveStep(
|
|
||||||
`- ${t('ledgerLiveDialogStepTwo')}`,
|
|
||||||
!isFirefox,
|
|
||||||
)}
|
|
||||||
{renderLedgerLiveStep(`- ${t('ledgerLiveDialogStepThree')}`)}
|
|
||||||
{renderLedgerLiveStep(
|
|
||||||
`- ${t('ledgerLiveDialogStepFour')}`,
|
|
||||||
Boolean(txData.txParams?.data),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="confirm-page-container-content__details">
|
<div className="confirm-page-container-content__details">
|
||||||
<TransactionDetail
|
<TransactionDetail
|
||||||
@ -574,7 +532,11 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{nonceField}
|
{nonceField}
|
||||||
{ledgerInstructionField}
|
{showLedgerSteps ? (
|
||||||
|
<LedgerInstructionField
|
||||||
|
showDataInstruction={Boolean(txData.txParams?.data)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -916,6 +878,7 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
gasIsLoading,
|
gasIsLoading,
|
||||||
gasFeeIsCustom,
|
gasFeeIsCustom,
|
||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
submitting,
|
submitting,
|
||||||
@ -981,7 +944,12 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
lastTx={lastTx}
|
lastTx={lastTx}
|
||||||
ofText={ofText}
|
ofText={ofText}
|
||||||
requestsWaitingText={requestsWaitingText}
|
requestsWaitingText={requestsWaitingText}
|
||||||
disabled={!valid || submitting || (gasIsLoading && !gasFeeIsCustom)}
|
disabled={
|
||||||
|
!valid ||
|
||||||
|
submitting ||
|
||||||
|
hardwareWalletRequiresConnection ||
|
||||||
|
(gasIsLoading && !gasFeeIsCustom)
|
||||||
|
}
|
||||||
onEdit={() => this.handleEdit()}
|
onEdit={() => this.handleEdit()}
|
||||||
onCancelAll={() => this.handleCancelAll()}
|
onCancelAll={() => this.handleCancelAll()}
|
||||||
onCancel={() => this.handleCancel()}
|
onCancel={() => this.handleCancel()}
|
||||||
|
@ -28,24 +28,24 @@ import {
|
|||||||
getShouldShowFiat,
|
getShouldShowFiat,
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
getPreferences,
|
getPreferences,
|
||||||
getHardwareWalletType,
|
doesAddressRequireLedgerHidConnection,
|
||||||
getUseTokenDetection,
|
getUseTokenDetection,
|
||||||
getTokenList,
|
getTokenList,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import {
|
import {
|
||||||
transactionMatchesNetwork,
|
isAddressLedger,
|
||||||
txParamsAreDappSuggested,
|
|
||||||
} from '../../../shared/modules/transaction.utils';
|
|
||||||
import { KEYRING_TYPES } from '../../../shared/constants/hardware-wallets';
|
|
||||||
import { getPlatform } from '../../../app/scripts/lib/util';
|
|
||||||
import { PLATFORM_FIREFOX } from '../../../shared/constants/app';
|
|
||||||
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
|
||||||
import {
|
|
||||||
updateTransactionGasFees,
|
updateTransactionGasFees,
|
||||||
getIsGasEstimatesLoading,
|
getIsGasEstimatesLoading,
|
||||||
getNativeCurrency,
|
getNativeCurrency,
|
||||||
} from '../../ducks/metamask/metamask';
|
} from '../../ducks/metamask/metamask';
|
||||||
|
|
||||||
|
import {
|
||||||
|
transactionMatchesNetwork,
|
||||||
|
txParamsAreDappSuggested,
|
||||||
|
} from '../../../shared/modules/transaction.utils';
|
||||||
|
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||||
|
|
||||||
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
|
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
|
||||||
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
|
||||||
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
import ConfirmTransactionBase from './confirm-transaction-base.component';
|
||||||
@ -170,10 +170,14 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const gasFeeIsCustom =
|
const gasFeeIsCustom =
|
||||||
fullTxData.userFeeLevel === 'custom' ||
|
fullTxData.userFeeLevel === 'custom' ||
|
||||||
txParamsAreDappSuggested(fullTxData);
|
txParamsAreDappSuggested(fullTxData);
|
||||||
const showLedgerSteps = getHardwareWalletType(state) === KEYRING_TYPES.LEDGER;
|
const fromAddressIsLedger = isAddressLedger(state, fromAddress);
|
||||||
const isFirefox = getPlatform() === PLATFORM_FIREFOX;
|
|
||||||
const nativeCurrency = getNativeCurrency(state);
|
const nativeCurrency = getNativeCurrency(state);
|
||||||
|
|
||||||
|
const hardwareWalletRequiresConnection = doesAddressRequireLedgerHidConnection(
|
||||||
|
state,
|
||||||
|
fromAddress,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
balance,
|
balance,
|
||||||
fromAddress,
|
fromAddress,
|
||||||
@ -219,9 +223,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
maxPriorityFeePerGas: gasEstimationObject.maxPriorityFeePerGas,
|
maxPriorityFeePerGas: gasEstimationObject.maxPriorityFeePerGas,
|
||||||
baseFeePerGas: gasEstimationObject.baseFeePerGas,
|
baseFeePerGas: gasEstimationObject.baseFeePerGas,
|
||||||
gasFeeIsCustom,
|
gasFeeIsCustom,
|
||||||
showLedgerSteps,
|
showLedgerSteps: fromAddressIsLedger,
|
||||||
isFirefox,
|
|
||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
|
hardwareWalletRequiresConnection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { formatBalance } from '../../../helpers/utils/util';
|
import { formatBalance } from '../../../helpers/utils/util';
|
||||||
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
|
||||||
import { SECOND } from '../../../../shared/constants/time';
|
import { SECOND } from '../../../../shared/constants/time';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets';
|
||||||
import SelectHardware from './select-hardware';
|
import SelectHardware from './select-hardware';
|
||||||
import AccountList from './account-list';
|
import AccountList from './account-list';
|
||||||
|
|
||||||
@ -26,6 +27,10 @@ const HD_PATHS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class ConnectHardwareForm extends Component {
|
class ConnectHardwareForm extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
error: null,
|
error: null,
|
||||||
selectedAccounts: [],
|
selectedAccounts: [],
|
||||||
@ -106,7 +111,7 @@ class ConnectHardwareForm extends Component {
|
|||||||
|
|
||||||
getPage = (device, page, hdPath) => {
|
getPage = (device, page, hdPath) => {
|
||||||
this.props
|
this.props
|
||||||
.connectHardware(device, page, hdPath)
|
.connectHardware(device, page, hdPath, this.context.t)
|
||||||
.then((accounts) => {
|
.then((accounts) => {
|
||||||
if (accounts.length) {
|
if (accounts.length) {
|
||||||
// If we just loaded the accounts for the first time
|
// If we just loaded the accounts for the first time
|
||||||
@ -262,7 +267,7 @@ class ConnectHardwareForm extends Component {
|
|||||||
<SelectHardware
|
<SelectHardware
|
||||||
connectToHardwareWallet={this.connectToHardwareWallet}
|
connectToHardwareWallet={this.connectToHardwareWallet}
|
||||||
browserSupported={this.state.browserSupported}
|
browserSupported={this.state.browserSupported}
|
||||||
useLedgerLive={this.props.useLedgerLive}
|
ledgerTransportType={this.props.ledgerTransportType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -313,7 +318,7 @@ ConnectHardwareForm.propTypes = {
|
|||||||
connectedAccounts: PropTypes.array.isRequired,
|
connectedAccounts: PropTypes.array.isRequired,
|
||||||
defaultHdPaths: PropTypes.object,
|
defaultHdPaths: PropTypes.object,
|
||||||
mostRecentOverviewPage: PropTypes.string.isRequired,
|
mostRecentOverviewPage: PropTypes.string.isRequired,
|
||||||
useLedgerLive: PropTypes.bool.isRequired,
|
ledgerTransportType: PropTypes.oneOf(Object.values(LEDGER_TRANSPORT_TYPES)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
@ -323,7 +328,7 @@ const mapStateToProps = (state) => ({
|
|||||||
connectedAccounts: getMetaMaskAccountsConnected(state),
|
connectedAccounts: getMetaMaskAccountsConnected(state),
|
||||||
defaultHdPaths: state.appState.defaultHdPaths,
|
defaultHdPaths: state.appState.defaultHdPaths,
|
||||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||||
useLedgerLive: state.metamask.useLedgerLive,
|
ledgerTransportType: state.metamask.ledgerTransportType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
@ -331,8 +336,8 @@ const mapDispatchToProps = (dispatch) => {
|
|||||||
setHardwareWalletDefaultHdPath: ({ device, path }) => {
|
setHardwareWalletDefaultHdPath: ({ device, path }) => {
|
||||||
return dispatch(actions.setHardwareWalletDefaultHdPath({ device, path }));
|
return dispatch(actions.setHardwareWalletDefaultHdPath({ device, path }));
|
||||||
},
|
},
|
||||||
connectHardware: (deviceName, page, hdPath) => {
|
connectHardware: (deviceName, page, hdPath, t) => {
|
||||||
return dispatch(actions.connectHardware(deviceName, page, hdPath));
|
return dispatch(actions.connectHardware(deviceName, page, hdPath, t));
|
||||||
},
|
},
|
||||||
checkHardwareStatus: (deviceName, hdPath) => {
|
checkHardwareStatus: (deviceName, hdPath) => {
|
||||||
return dispatch(actions.checkHardwareStatus(deviceName, hdPath));
|
return dispatch(actions.checkHardwareStatus(deviceName, hdPath));
|
||||||
|
@ -2,6 +2,7 @@ import classnames from 'classnames';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Button from '../../../components/ui/button';
|
import Button from '../../../components/ui/button';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets';
|
||||||
|
|
||||||
export default class SelectHardware extends Component {
|
export default class SelectHardware extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -11,7 +12,7 @@ export default class SelectHardware extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||||
browserSupported: PropTypes.bool.isRequired,
|
browserSupported: PropTypes.bool.isRequired,
|
||||||
useLedgerLive: PropTypes.bool.isRequired,
|
ledgerTransportType: PropTypes.oneOf(Object.values(LEDGER_TRANSPORT_TYPES)),
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -136,7 +137,7 @@ export default class SelectHardware extends Component {
|
|||||||
|
|
||||||
renderLedgerTutorialSteps() {
|
renderLedgerTutorialSteps() {
|
||||||
const steps = [];
|
const steps = [];
|
||||||
if (this.props.useLedgerLive) {
|
if (this.props.ledgerTransportType === LEDGER_TRANSPORT_TYPES.LIVE) {
|
||||||
steps.push({
|
steps.push({
|
||||||
title: this.context.t('step1LedgerWallet'),
|
title: this.context.t('step1LedgerWallet'),
|
||||||
message: this.context.t('step1LedgerWalletMsg', [
|
message: this.context.t('step1LedgerWalletMsg', [
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets';
|
||||||
import SelectHardware from './select-hardware';
|
import SelectHardware from './select-hardware';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -14,7 +15,7 @@ export const SelectHardwareComponent = () => {
|
|||||||
connectToHardwareWallet={(selectedDevice) =>
|
connectToHardwareWallet={(selectedDevice) =>
|
||||||
action(`Continue connect to ${selectedDevice}`)()
|
action(`Continue connect to ${selectedDevice}`)()
|
||||||
}
|
}
|
||||||
useLedgerLive
|
ledgerTransportType={LEDGER_TRANSPORT_TYPES.LIVE}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -23,7 +24,7 @@ export const BrowserNotSupported = () => {
|
|||||||
<SelectHardware
|
<SelectHardware
|
||||||
browserSupported={false}
|
browserSupported={false}
|
||||||
connectToHardwareWallet={() => undefined}
|
connectToHardwareWallet={() => undefined}
|
||||||
useLedgerLive
|
ledgerTransportType={LEDGER_TRANSPORT_TYPES.LIVE}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,12 @@ import ToggleButton from '../../../components/ui/toggle-button';
|
|||||||
import TextField from '../../../components/ui/text-field';
|
import TextField from '../../../components/ui/text-field';
|
||||||
import Button from '../../../components/ui/button';
|
import Button from '../../../components/ui/button';
|
||||||
import { MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes';
|
import { MOBILE_SYNC_ROUTE } from '../../../helpers/constants/routes';
|
||||||
|
import Dropdown from '../../../components/ui/dropdown';
|
||||||
|
|
||||||
import { getPlatform } from '../../../../app/scripts/lib/util';
|
import {
|
||||||
import { PLATFORM_FIREFOX } from '../../../../shared/constants/app';
|
LEDGER_TRANSPORT_TYPES,
|
||||||
|
LEDGER_USB_VENDOR_ID,
|
||||||
|
} from '../../../../shared/constants/hardware-wallets';
|
||||||
|
|
||||||
export default class AdvancedTab extends PureComponent {
|
export default class AdvancedTab extends PureComponent {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -36,10 +39,11 @@ export default class AdvancedTab extends PureComponent {
|
|||||||
threeBoxDisabled: PropTypes.bool.isRequired,
|
threeBoxDisabled: PropTypes.bool.isRequired,
|
||||||
setIpfsGateway: PropTypes.func.isRequired,
|
setIpfsGateway: PropTypes.func.isRequired,
|
||||||
ipfsGateway: PropTypes.string.isRequired,
|
ipfsGateway: PropTypes.string.isRequired,
|
||||||
useLedgerLive: PropTypes.bool.isRequired,
|
ledgerTransportType: PropTypes.oneOf(Object.values(LEDGER_TRANSPORT_TYPES)),
|
||||||
setLedgerLivePreference: PropTypes.func.isRequired,
|
setLedgerLivePreference: PropTypes.func.isRequired,
|
||||||
setDismissSeedBackUpReminder: PropTypes.func.isRequired,
|
setDismissSeedBackUpReminder: PropTypes.func.isRequired,
|
||||||
dismissSeedBackUpReminder: PropTypes.bool.isRequired,
|
dismissSeedBackUpReminder: PropTypes.bool.isRequired,
|
||||||
|
userHasALedgerAccount: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -393,24 +397,77 @@ export default class AdvancedTab extends PureComponent {
|
|||||||
|
|
||||||
renderLedgerLiveControl() {
|
renderLedgerLiveControl() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const { useLedgerLive, setLedgerLivePreference } = this.props;
|
const {
|
||||||
|
ledgerTransportType,
|
||||||
|
setLedgerLivePreference,
|
||||||
|
userHasALedgerAccount,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const LEDGER_TRANSPORT_NAMES = {
|
||||||
|
LIVE: t('ledgerLive'),
|
||||||
|
WEBHID: t('webhid'),
|
||||||
|
U2F: t('u2f'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const transportTypeOptions = [
|
||||||
|
{
|
||||||
|
name: LEDGER_TRANSPORT_NAMES.LIVE,
|
||||||
|
value: LEDGER_TRANSPORT_TYPES.LIVE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: LEDGER_TRANSPORT_NAMES.U2F,
|
||||||
|
value: LEDGER_TRANSPORT_TYPES.U2F,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (window.navigator.hid) {
|
||||||
|
transportTypeOptions.push({
|
||||||
|
name: LEDGER_TRANSPORT_NAMES.WEBHID,
|
||||||
|
value: LEDGER_TRANSPORT_TYPES.WEBHID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendedLedgerOption = window.navigator.hid
|
||||||
|
? LEDGER_TRANSPORT_NAMES.WEBHID
|
||||||
|
: LEDGER_TRANSPORT_NAMES.U2F;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-page__content-row">
|
<div className="settings-page__content-row">
|
||||||
<div className="settings-page__content-item">
|
<div className="settings-page__content-item">
|
||||||
<span>{t('ledgerLiveAdvancedSetting')}</span>
|
<span>{t('preferredLedgerConnectionType')}</span>
|
||||||
<div className="settings-page__content-description">
|
<div className="settings-page__content-description">
|
||||||
{t('ledgerLiveAdvancedSettingDescription')}
|
{t('ledgerConnectionPreferenceDescription', [
|
||||||
|
recommendedLedgerOption,
|
||||||
|
<Button
|
||||||
|
key="ledger-connection-settings-learn-more"
|
||||||
|
type="link"
|
||||||
|
href="https://metamask.zendesk.com/hc/en-us/articles/360020394612-How-to-connect-a-Trezor-or-Ledger-Hardware-Wallet"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="settings-page__inline-link"
|
||||||
|
>
|
||||||
|
{t('learnMore')}
|
||||||
|
</Button>,
|
||||||
|
])}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="settings-page__content-item">
|
<div className="settings-page__content-item">
|
||||||
<div className="settings-page__content-item-col">
|
<div className="settings-page__content-item-col">
|
||||||
<ToggleButton
|
<Dropdown
|
||||||
value={useLedgerLive}
|
id="select-ledger-transport-type"
|
||||||
onToggle={(value) => setLedgerLivePreference(!value)}
|
options={transportTypeOptions}
|
||||||
offLabel={t('off')}
|
selectedOption={ledgerTransportType}
|
||||||
onLabel={t('on')}
|
onChange={async (transportType) => {
|
||||||
disabled={getPlatform() === PLATFORM_FIREFOX}
|
setLedgerLivePreference(transportType);
|
||||||
|
if (
|
||||||
|
transportType === LEDGER_TRANSPORT_TYPES.WEBHID &&
|
||||||
|
userHasALedgerAccount
|
||||||
|
) {
|
||||||
|
await window.navigator.hid.requestDevice({
|
||||||
|
filters: [{ vendorId: LEDGER_USB_VENDOR_ID }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import TextField from '../../../components/ui/text-field';
|
import TextField from '../../../components/ui/text-field';
|
||||||
|
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets';
|
||||||
import AdvancedTab from './advanced-tab.component';
|
import AdvancedTab from './advanced-tab.component';
|
||||||
|
|
||||||
describe('AdvancedTab Component', () => {
|
describe('AdvancedTab Component', () => {
|
||||||
@ -15,7 +16,7 @@ describe('AdvancedTab Component', () => {
|
|||||||
setThreeBoxSyncingPermission={() => undefined}
|
setThreeBoxSyncingPermission={() => undefined}
|
||||||
threeBoxDisabled
|
threeBoxDisabled
|
||||||
threeBoxSyncingAllowed={false}
|
threeBoxSyncingAllowed={false}
|
||||||
useLedgerLive={false}
|
ledgerTransportType={LEDGER_TRANSPORT_TYPES.U2F}
|
||||||
setLedgerLivePreference={() => undefined}
|
setLedgerLivePreference={() => undefined}
|
||||||
setDismissSeedBackUpReminder={() => undefined}
|
setDismissSeedBackUpReminder={() => undefined}
|
||||||
dismissSeedBackUpReminder={false}
|
dismissSeedBackUpReminder={false}
|
||||||
@ -41,7 +42,7 @@ describe('AdvancedTab Component', () => {
|
|||||||
setThreeBoxSyncingPermission={() => undefined}
|
setThreeBoxSyncingPermission={() => undefined}
|
||||||
threeBoxDisabled
|
threeBoxDisabled
|
||||||
threeBoxSyncingAllowed={false}
|
threeBoxSyncingAllowed={false}
|
||||||
useLedgerLive={false}
|
ledgerTransportType={LEDGER_TRANSPORT_TYPES.U2F}
|
||||||
setLedgerLivePreference={() => undefined}
|
setLedgerLivePreference={() => undefined}
|
||||||
setDismissSeedBackUpReminder={() => undefined}
|
setDismissSeedBackUpReminder={() => undefined}
|
||||||
dismissSeedBackUpReminder={false}
|
dismissSeedBackUpReminder={false}
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
setDismissSeedBackUpReminder,
|
setDismissSeedBackUpReminder,
|
||||||
} from '../../../store/actions';
|
} from '../../../store/actions';
|
||||||
import { getPreferences } from '../../../selectors';
|
import { getPreferences } from '../../../selectors';
|
||||||
|
import { doesUserHaveALedgerAccount } from '../../../ducks/metamask/metamask';
|
||||||
import AdvancedTab from './advanced-tab.component';
|
import AdvancedTab from './advanced-tab.component';
|
||||||
|
|
||||||
export const mapStateToProps = (state) => {
|
export const mapStateToProps = (state) => {
|
||||||
@ -28,11 +29,13 @@ export const mapStateToProps = (state) => {
|
|||||||
threeBoxDisabled,
|
threeBoxDisabled,
|
||||||
useNonceField,
|
useNonceField,
|
||||||
ipfsGateway,
|
ipfsGateway,
|
||||||
useLedgerLive,
|
ledgerTransportType,
|
||||||
dismissSeedBackUpReminder,
|
dismissSeedBackUpReminder,
|
||||||
} = metamask;
|
} = metamask;
|
||||||
const { showFiatInTestnets, autoLockTimeLimit } = getPreferences(state);
|
const { showFiatInTestnets, autoLockTimeLimit } = getPreferences(state);
|
||||||
|
|
||||||
|
const userHasALedgerAccount = doesUserHaveALedgerAccount(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
warning,
|
warning,
|
||||||
sendHexData,
|
sendHexData,
|
||||||
@ -43,8 +46,9 @@ export const mapStateToProps = (state) => {
|
|||||||
threeBoxDisabled,
|
threeBoxDisabled,
|
||||||
useNonceField,
|
useNonceField,
|
||||||
ipfsGateway,
|
ipfsGateway,
|
||||||
useLedgerLive,
|
ledgerTransportType,
|
||||||
dismissSeedBackUpReminder,
|
dismissSeedBackUpReminder,
|
||||||
|
userHasALedgerAccount,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,6 +233,13 @@
|
|||||||
margin-left: 1.875rem;
|
margin-left: 1.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__inline-link {
|
||||||
|
@include H6;
|
||||||
|
|
||||||
|
display: initial;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
.settings-page {
|
.settings-page {
|
||||||
&__content {
|
&__content {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { stripHexPrefix } from 'ethereumjs-util';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { addHexPrefix } from '../../app/scripts/lib/util';
|
import { addHexPrefix } from '../../app/scripts/lib/util';
|
||||||
import {
|
import {
|
||||||
@ -8,7 +7,11 @@ import {
|
|||||||
NETWORK_TYPE_RPC,
|
NETWORK_TYPE_RPC,
|
||||||
NATIVE_CURRENCY_TOKEN_IMAGE_MAP,
|
NATIVE_CURRENCY_TOKEN_IMAGE_MAP,
|
||||||
} from '../../shared/constants/network';
|
} from '../../shared/constants/network';
|
||||||
import { KEYRING_TYPES } from '../../shared/constants/hardware-wallets';
|
import {
|
||||||
|
KEYRING_TYPES,
|
||||||
|
WEBHID_CONNECTED_STATUSES,
|
||||||
|
LEDGER_TRANSPORT_TYPES,
|
||||||
|
} from '../../shared/constants/hardware-wallets';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||||
@ -36,7 +39,11 @@ import {
|
|||||||
getConversionRate,
|
getConversionRate,
|
||||||
isNotEIP1559Network,
|
isNotEIP1559Network,
|
||||||
isEIP1559Network,
|
isEIP1559Network,
|
||||||
|
getLedgerTransportType,
|
||||||
|
isAddressLedger,
|
||||||
|
findKeyringForAddress,
|
||||||
} from '../ducks/metamask/metamask';
|
} from '../ducks/metamask/metamask';
|
||||||
|
import { getLedgerWebHidConnectedStatus } from '../ducks/app/app';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One of the only remaining valid uses of selecting the network subkey of the
|
* One of the only remaining valid uses of selecting the network subkey of the
|
||||||
@ -77,14 +84,7 @@ export function getCurrentKeyring(state) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const simpleAddress = stripHexPrefix(identity.address).toLowerCase();
|
const keyring = findKeyringForAddress(state, identity.address);
|
||||||
|
|
||||||
const keyring = state.metamask.keyrings.find((kr) => {
|
|
||||||
return (
|
|
||||||
kr.accounts.includes(simpleAddress) ||
|
|
||||||
kr.accounts.includes(identity.address)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return keyring;
|
return keyring;
|
||||||
}
|
}
|
||||||
@ -646,3 +646,16 @@ export function getUseTokenDetection(state) {
|
|||||||
export function getTokenList(state) {
|
export function getTokenList(state) {
|
||||||
return state.metamask.tokenList;
|
return state.metamask.tokenList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doesAddressRequireLedgerHidConnection(state, address) {
|
||||||
|
const addressIsLedger = isAddressLedger(state, address);
|
||||||
|
const transportTypePreferenceIsWebHID =
|
||||||
|
getLedgerTransportType(state) === LEDGER_TRANSPORT_TYPES.WEBHID;
|
||||||
|
const webHidIsNotConnected =
|
||||||
|
getLedgerWebHidConnectedStatus(state) !==
|
||||||
|
WEBHID_CONNECTED_STATUSES.CONNECTED;
|
||||||
|
|
||||||
|
return (
|
||||||
|
addressIsLedger && transportTypePreferenceIsWebHID && webHidIsNotConnected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -77,6 +77,10 @@ export const COMPLETE_ONBOARDING = 'COMPLETE_ONBOARDING';
|
|||||||
|
|
||||||
export const SET_MOUSE_USER_STATE = 'SET_MOUSE_USER_STATE';
|
export const SET_MOUSE_USER_STATE = 'SET_MOUSE_USER_STATE';
|
||||||
|
|
||||||
|
// Ledger
|
||||||
|
|
||||||
|
export const SET_WEBHID_CONNECTED_STATUS = 'SET_WEBHID_CONNECTED_STATUS';
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
export const SET_PENDING_TOKENS = 'SET_PENDING_TOKENS';
|
export const SET_PENDING_TOKENS = 'SET_PENDING_TOKENS';
|
||||||
export const CLEAR_PENDING_TOKENS = 'CLEAR_PENDING_TOKENS';
|
export const CLEAR_PENDING_TOKENS = 'CLEAR_PENDING_TOKENS';
|
||||||
|
@ -28,6 +28,10 @@ import { computeEstimatedGasLimit, resetSendState } from '../ducks/send';
|
|||||||
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
|
import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account';
|
||||||
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
|
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
|
||||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||||
|
import {
|
||||||
|
LEDGER_TRANSPORT_TYPES,
|
||||||
|
LEDGER_USB_VENDOR_ID,
|
||||||
|
} from '../../shared/constants/hardware-wallets';
|
||||||
import * as actionConstants from './actionConstants';
|
import * as actionConstants from './actionConstants';
|
||||||
|
|
||||||
let background = null;
|
let background = null;
|
||||||
@ -395,15 +399,31 @@ export function forgetDevice(deviceName) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function connectHardware(deviceName, page, hdPath) {
|
export function connectHardware(deviceName, page, hdPath, t) {
|
||||||
log.debug(`background.connectHardware`, deviceName, page, hdPath);
|
log.debug(`background.connectHardware`, deviceName, page, hdPath);
|
||||||
return async (dispatch) => {
|
return async (dispatch, getState) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
showLoadingIndication(`Looking for your ${capitalize(deviceName)}...`),
|
showLoadingIndication(`Looking for your ${capitalize(deviceName)}...`),
|
||||||
);
|
);
|
||||||
|
|
||||||
let accounts;
|
let accounts;
|
||||||
try {
|
try {
|
||||||
|
const { ledgerTransportType } = getState().metamask;
|
||||||
|
if (
|
||||||
|
deviceName === 'ledger' &&
|
||||||
|
ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID
|
||||||
|
) {
|
||||||
|
const connectedDevices = await window.navigator.hid.requestDevice({
|
||||||
|
filters: [{ vendorId: LEDGER_USB_VENDOR_ID }],
|
||||||
|
});
|
||||||
|
const userApprovedWebHidConnection = connectedDevices.some(
|
||||||
|
(device) => device.vendorId === Number(LEDGER_USB_VENDOR_ID),
|
||||||
|
);
|
||||||
|
if (!userApprovedWebHidConnection) {
|
||||||
|
throw new Error(t('ledgerWebHIDNotConnectedErrorMessage'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
accounts = await promisifiedBackground.connectHardware(
|
accounts = await promisifiedBackground.connectHardware(
|
||||||
deviceName,
|
deviceName,
|
||||||
page,
|
page,
|
||||||
@ -2745,7 +2765,7 @@ export function getCurrentWindowTab() {
|
|||||||
export function setLedgerLivePreference(value) {
|
export function setLedgerLivePreference(value) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(showLoadingIndication());
|
dispatch(showLoadingIndication());
|
||||||
await promisifiedBackground.setLedgerLivePreference(value);
|
await promisifiedBackground.setLedgerTransportPreference(value);
|
||||||
dispatch(hideLoadingIndication());
|
dispatch(hideLoadingIndication());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2821,10 +2821,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-6.0.0.tgz#ec53e8ab278073e882411ed89705bc7d06b78c81"
|
resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-6.0.0.tgz#ec53e8ab278073e882411ed89705bc7d06b78c81"
|
||||||
integrity sha512-LyakGYGwM8UQOGhwWa+5erAI1hXuiTgf/y7USzOomX6H9KiuY09IAUYnPh7ToPG2sedD2F48UF1bUm8yvCoZOw==
|
integrity sha512-LyakGYGwM8UQOGhwWa+5erAI1hXuiTgf/y7USzOomX6H9KiuY09IAUYnPh7ToPG2sedD2F48UF1bUm8yvCoZOw==
|
||||||
|
|
||||||
"@metamask/eth-ledger-bridge-keyring@^0.7.0":
|
"@metamask/eth-ledger-bridge-keyring@^0.9.0":
|
||||||
version "0.7.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.7.0.tgz#7d80e1e3dfab91ba2b6a1a2a5e352320e948b568"
|
resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.9.0.tgz#42e98e7dfeaaa08e7c9ceff261facddd7320df80"
|
||||||
integrity sha512-0UOEb/c3/fkatDK+se3gOHaGQ0RTRLbG5DqsoeowZ/JcO4wcMxBhOiIgOY4domOqUTekKKVPNC7Pc0mHpM9sAQ==
|
integrity sha512-EuNKvodbdJxQPzr+zAE5TE1iKUzuIRWKeVaYoYwpi18RjjtSQMKmZcb3VXY8hmQu+Fj4Ld/ujj22qSYjYAjtPg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ethereumjs/tx" "^3.2.0"
|
"@ethereumjs/tx" "^3.2.0"
|
||||||
eth-sig-util "^2.0.0"
|
eth-sig-util "^2.0.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user