From 5ef80495cfd47a8f5e4caf4b16842155420de62e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 10 Aug 2018 21:54:34 -0400 Subject: refactor to support multiple hw wallets --- app/manifest.json | 3 +- app/scripts/eth-ledger-keyring-listener.js | 105 +++++++++++++++++ app/scripts/metamask-controller.js | 130 +++++++++------------ app/vendor/ledger/content-script.js | 18 +++ package-lock.json | 23 ++++ package.json | 1 + ui/app/actions.js | 8 +- .../connect-hardware/account-list.js | 9 +- .../connect-hardware/connect-screen.js | 14 ++- .../pages/create-account/connect-hardware/index.js | 39 ++++--- 10 files changed, 247 insertions(+), 103 deletions(-) create mode 100644 app/scripts/eth-ledger-keyring-listener.js create mode 100644 app/vendor/ledger/content-script.js diff --git a/app/manifest.json b/app/manifest.json index 84cedd687..0bdf84ef1 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -48,7 +48,8 @@ "https://*/*" ], "js": [ - "contentscript.js" + "contentscript.js", + "vendor/ledger/content-script.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js new file mode 100644 index 000000000..a6db49772 --- /dev/null +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -0,0 +1,105 @@ + const extension = require('extensionizer') +const {EventEmitter} = require('events') + + +// HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger +const hdPathString = `m/44'/60'/0'` +const type = 'Ledger Hardware Keyring' + +class LedgerKeyring extends EventEmitter { + constructor (opts = {}) { + super() + this.type = type + this.deserialize(opts) + } + + serialize () { + return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts}) + } + + deserialize (opts = {}) { + this.hdPath = opts.hdPath || hdPathString + this.accounts = opts.accounts || [] + return Promise.resolve() + } + + async addAccounts (n = 1) { + return new Promise((resolve, reject) => { + extension.runtime.sendMessage({ + action: 'ledger-add-account', + n, + }) + + extension.runtime.onMessage.addListener(({action, success, payload}) => { + if (action === 'ledger-sign-transaction') { + if (success) { + resolve(payload) + } else { + reject(payload) + } + } + }) + }) + } + + async getAccounts () { + return this.accounts.slice() + } + + // tx is an instance of the ethereumjs-transaction class. + async signTransaction (address, tx) { + return new Promise((resolve, reject) => { + extension.runtime.sendMessage({ + action: 'ledger-sign-transaction', + address, + tx, + }) + + extension.runtime.onMessage.addListener(({action, success, payload}) => { + if (action === 'ledger-sign-transaction') { + if (success) { + resolve(payload) + } else { + reject(payload) + } + } + }) + }) + } + + async signMessage (withAccount, data) { + throw new Error('Not supported on this device') + } + + // For personal_sign, we need to prefix the message: + async signPersonalMessage (withAccount, message) { + return new Promise((resolve, reject) => { + extension.runtime.sendMessage({ + action: 'ledger-sign-personal-message', + withAccount, + message, + }) + + extension.runtime.onMessage.addListener(({action, success, payload}) => { + if (action === 'ledger-sign-personal-message') { + if (success) { + resolve(payload) + } else { + reject(payload) + } + } + }) + }) + } + + async signTypedData (withAccount, typedData) { + throw new Error('Not supported on this device') + } + + async exportAccount (address) { + throw new Error('Not supported on this device') + } +} + +LedgerKeyring.type = type +module.exports = LedgerKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index db323e3fe..593b722ff 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,6 +49,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') +const LedgerKeyring = require('./eth-ledger-keyring-listener') module.exports = class MetamaskController extends EventEmitter { @@ -127,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // key mgmt - const additionalKeyrings = [TrezorKeyring] + const additionalKeyrings = [TrezorKeyring, LedgerKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, @@ -377,9 +378,7 @@ module.exports = class MetamaskController extends EventEmitter { connectHardware: nodeify(this.connectHardware, this), forgetDevice: nodeify(this.forgetDevice, this), checkHardwareStatus: nodeify(this.checkHardwareStatus, this), - - // TREZOR - unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), + unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this), // vault management submitPassword: nodeify(this.submitPassword, this), @@ -540,6 +539,28 @@ module.exports = class MetamaskController extends EventEmitter { // Hardware // + async getKeyringForDevice (deviceName) { + let keyringName = null + switch (deviceName) { + case 'trezor': + keyringName = TrezorKeyring.type + break + case 'ledger': + keyringName = TrezorKeyring.type + break + default: + throw new Error('MetamaskController:connectHardware - Unknown device') + } + + let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] + if (!keyring) { + keyring = await this.keyringController.addNewKeyring(keyringName) + } + + return keyring + + } + /** * Fetch account list from a trezor device. * @@ -547,38 +568,26 @@ module.exports = class MetamaskController extends EventEmitter { */ async connectHardware (deviceName, page) { - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const oldAccounts = await keyringController.getAccounts() - let keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - keyring = await this.keyringController.addNewKeyring('Trezor Hardware') - } - let accounts = [] - - switch (page) { - case -1: - accounts = await keyring.getPreviousPage() - break - case 1: - accounts = await keyring.getNextPage() - break - default: - accounts = await keyring.getFirstPage() - } - - // Merge with existing accounts - // and make sure addresses are not repeated - const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] - this.accountTracker.syncWithAddresses(accountsToTrack) - return accounts - - default: - throw new Error('MetamaskController:connectHardware - Unknown device') + const oldAccounts = await this.keyringController.getAccounts() + const keyring = await this.getKeyringForDevice(deviceName) + let accounts = [] + + switch (page) { + case -1: + accounts = await keyring.getPreviousPage() + break + case 1: + accounts = await keyring.getNextPage() + break + default: + accounts = await keyring.getFirstPage() } + + // Merge with existing accounts + // and make sure addresses are not repeated + const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] + this.accountTracker.syncWithAddresses(accountsToTrack) + return accounts } /** @@ -587,20 +596,8 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {Promise} */ async checkHardwareStatus (deviceName) { - - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - return false - } - return keyring.isUnlocked() - default: - throw new Error('MetamaskController:checkHardwareStatus - Unknown device') - } + const keyring = await this.getKeyringForDevice(deviceName) + return keyring.isUnlocked() } /** @@ -610,20 +607,9 @@ module.exports = class MetamaskController extends EventEmitter { */ async forgetDevice (deviceName) { - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found') - } - keyring.forgetDevice() - return true - default: - throw new Error('MetamaskController:forgetDevice - Unknown device') - } + const keyring = await this.getKeyringForDevice(deviceName) + keyring.forgetDevice() + return true } /** @@ -631,23 +617,17 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockTrezorAccount (index) { - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - throw new Error('MetamaskController - No Trezor Hardware Keyring found') - } + async unlockHardwareWalletAccount (deviceName, index) { + const keyring = await this.getKeyringForDevice(deviceName) keyring.setAccountToUnlock(index) - const oldAccounts = await keyringController.getAccounts() - const keyState = await keyringController.addNewAccount(keyring) - const newAccounts = await keyringController.getAccounts() + const oldAccounts = await this.keyringController.getAccounts() + const keyState = await this.keyringController.addNewAccount(keyring) + const newAccounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`) + this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} #${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } }) diff --git a/app/vendor/ledger/content-script.js b/app/vendor/ledger/content-script.js new file mode 100644 index 000000000..425ff07a3 --- /dev/null +++ b/app/vendor/ledger/content-script.js @@ -0,0 +1,18 @@ +/* +Passing messages from background script to popup +*/ +let port = chrome.runtime.connect({ name: 'ledger' }); +port.onMessage.addListener(message => { + window.postMessage(message, window.location.origin); +}); +port.onDisconnect.addListener(d => { + port = null; +}); + /* +Passing messages from popup to background script +*/ + window.addEventListener('message', event => { + if (port && event.source === window && event.data) { + port.postMessage(event.data); + } +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6bee47cd6..d48415cbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -317,6 +317,29 @@ "through2": "^2.0.3" } }, + "@ledgerhq/hw-app-eth": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.21.0.tgz", + "integrity": "sha1-LYv75fCbkujWlRrmhQNtnVrqlv8=", + "requires": { + "@ledgerhq/hw-transport": "^4.21.0" + } + }, + "@ledgerhq/hw-transport": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.21.0.tgz", + "integrity": "sha1-UPhc/hFbo/nVv5R1XHAeknF1eU8=", + "requires": { + "events": "^2.0.0" + }, + "dependencies": { + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + } + } + }, "@material-ui/core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-1.0.0.tgz", diff --git a/package.json b/package.json index 29295a65b..c8a37f564 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ ] }, "dependencies": { + "@ledgerhq/hw-app-eth": "^4.21.0", "@material-ui/core": "1.0.0", "@zxing/library": "^0.7.0", "abi-decoder": "^1.0.9", diff --git a/ui/app/actions.js b/ui/app/actions.js index bd5d25327..04af9d7c8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -91,7 +91,7 @@ var actions = { connectHardware, checkHardwareStatus, forgetDevice, - unlockTrezorAccount, + unlockHardwareWalletAccount, NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN', navigateToNewAccountScreen, resetAccount, @@ -702,12 +702,12 @@ function connectHardware (deviceName, page) { } } -function unlockTrezorAccount (index) { - log.debug(`background.unlockTrezorAccount`, index) +function unlockHardwareWalletAccount (index, deviceName) { + log.debug(`background.unlockHardwareWalletAccount`, index, deviceName) return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.unlockTrezorAccount(index, (err, accounts) => { + background.unlockHardwareWalletAccount(index, deviceName, (err, accounts) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index c722d1f55..730f2df34 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -61,7 +61,7 @@ class AccountList extends Component { h( 'button.hw-list-pagination__button', { - onClick: () => this.props.getPage(-1), + onClick: () => this.props.getPage(-1, this.props.device), }, `< ${this.context.t('prev')}` ), @@ -69,7 +69,7 @@ class AccountList extends Component { h( 'button.hw-list-pagination__button', { - onClick: () => this.props.getPage(1), + onClick: () => this.props.getPage(1, this.props.device), }, `${this.context.t('next')} >` ), @@ -95,7 +95,7 @@ class AccountList extends Component { h( `button.btn-primary.btn--large.new-account-connect-form__button.unlock ${disabled ? '.btn-primary--disabled' : ''}`, { - onClick: this.props.onUnlockAccount.bind(this), + onClick: this.props.onUnlockAccount.bind(this, this.props.device), ...buttonProps, }, [this.context.t('unlock')] @@ -106,7 +106,7 @@ class AccountList extends Component { renderForgetDevice () { return h('div.hw-forget-device-container', {}, [ h('a', { - onClick: this.props.onForgetDevice.bind(this), + onClick: this.props.onForgetDevice.bind(this, this.props.device), }, this.context.t('forgetDevice')), ]) } @@ -125,6 +125,7 @@ class AccountList extends Component { AccountList.propTypes = { + device: PropTypes.string.isRequired, accounts: PropTypes.array.isRequired, onAccountChange: PropTypes.func.isRequired, onForgetDevice: PropTypes.func.isRequired, diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js index cb2b86595..af144d410 100644 --- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js @@ -49,11 +49,19 @@ class ConnectScreen extends Component { renderConnectToTrezorButton () { return h( 'button.btn-primary.btn--large', - { onClick: this.props.connectToTrezor.bind(this) }, + { onClick: this.props.connectToHardwareWallet.bind(this, 'trezor') }, this.props.btnText ) } + renderConnectToLedgerButton () { + return h( + 'button.btn-primary.btn--large', + { onClick: this.props.connectToHardwareWallet.bind(this, 'ledger') }, + this.props.btnText.replace('Trezor', 'Ledger') + ) + } + scrollToTutorial = (e) => { if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) } @@ -103,6 +111,7 @@ class ConnectScreen extends Component { h('div.hw-connect__footer', {}, [ h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)), this.renderConnectToTrezorButton(), + this.renderConnectToLedgerButton(), h('p.hw-connect__footer__msg', {}, [ this.context.t(`havingTroubleConnecting`), h('a.hw-connect__footer__link', { @@ -120,6 +129,7 @@ class ConnectScreen extends Component { this.renderHeader(), this.renderTrezorAffiliateLink(), this.renderConnectToTrezorButton(), + this.renderConnectToLedgerButton(), this.renderLearnMore(), this.renderTutorialSteps(), this.renderFooter(), @@ -136,7 +146,7 @@ class ConnectScreen extends Component { } ConnectScreen.propTypes = { - connectToTrezor: PropTypes.func.isRequired, + connectToHardwareWallet: PropTypes.func.isRequired, btnText: PropTypes.string.isRequired, browserSupported: PropTypes.bool.isRequired, } diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 3f66e7098..646ba8bec 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -18,6 +18,7 @@ class ConnectHardwareForm extends Component { accounts: [], browserSupported: true, unlocked: false, + device: null } } @@ -38,19 +39,22 @@ class ConnectHardwareForm extends Component { } async checkIfUnlocked () { - const unlocked = await this.props.checkHardwareStatus('trezor') - if (unlocked) { - this.setState({unlocked: true}) - this.getPage(0) - } + ['trezor', 'ledger'].forEach(async device => { + const unlocked = await this.props.checkHardwareStatus(device) + if (unlocked) { + this.setState({unlocked: true}) + this.getPage(0, device) + } + }) } - connectToTrezor = () => { + connectToHardwareWallet = (device) => { + debugger if (this.state.accounts.length) { return null } this.setState({ btnText: this.context.t('connecting')}) - this.getPage(0) + this.getPage(0, device) } onAccountChange = (account) => { @@ -65,9 +69,9 @@ class ConnectHardwareForm extends Component { }, 5000) } - getPage = (page) => { + getPage = (page, device) => { this.props - .connectHardware('trezor', page) + .connectHardware(device, page) .then(accounts => { if (accounts.length) { @@ -77,7 +81,7 @@ class ConnectHardwareForm extends Component { this.showTemporaryAlert() } - const newState = { unlocked: true } + const newState = { unlocked: true, device } // Default to the first account if (this.state.selectedAccount === null) { accounts.forEach((a, i) => { @@ -110,8 +114,8 @@ class ConnectHardwareForm extends Component { }) } - onForgetDevice = () => { - this.props.forgetDevice('trezor') + onForgetDevice = (device) => { + this.props.forgetDevice(device) .then(_ => { this.setState({ error: null, @@ -131,7 +135,7 @@ class ConnectHardwareForm extends Component { this.setState({ error: this.context.t('accountSelectionRequired') }) } - this.props.unlockTrezorAccount(this.state.selectedAccount) + this.props.unlockHardwareWalletAccount(this.state.selectedAccount, this.state.device) .then(_ => { this.props.history.push(DEFAULT_ROUTE) }).catch(e => { @@ -152,13 +156,14 @@ class ConnectHardwareForm extends Component { renderContent () { if (!this.state.accounts.length) { return h(ConnectScreen, { - connectToTrezor: this.connectToTrezor, + connectToHardwareWallet: this.connectToHardwareWallet, btnText: this.state.btnText, browserSupported: this.state.browserSupported, }) } return h(AccountList, { + device: this.state.device, accounts: this.state.accounts, selectedAccount: this.state.selectedAccount, onAccountChange: this.onAccountChange, @@ -188,7 +193,7 @@ ConnectHardwareForm.propTypes = { forgetDevice: PropTypes.func, showAlert: PropTypes.func, hideAlert: PropTypes.func, - unlockTrezorAccount: PropTypes.func, + unlockHardwareWalletAccount: PropTypes.func, numberOfExistingAccounts: PropTypes.number, history: PropTypes.object, t: PropTypes.func, @@ -222,8 +227,8 @@ const mapDispatchToProps = dispatch => { forgetDevice: (deviceName) => { return dispatch(actions.forgetDevice(deviceName)) }, - unlockTrezorAccount: index => { - return dispatch(actions.unlockTrezorAccount(index)) + unlockHardwareWalletAccount: (index, deviceName) => { + return dispatch(actions.unlockHardwareWalletAccount(index, deviceName)) }, showImportPage: () => dispatch(actions.showImportPage()), showConnectPage: () => dispatch(actions.showConnectPage()), -- cgit v1.2.3 From 78a1cd3314455d6e4e08839a3eea3ec2868f4f59 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 02:35:20 -0400 Subject: iframe communication working --- app/manifest.json | 3 +- app/scripts/background.js | 3 +- app/scripts/contentscript.js | 2 +- app/scripts/eth-ledger-keyring-listener.js | 203 +++++++++++++++++---- app/scripts/lib/setupLedgerIframe.js | 40 ++++ app/scripts/metamask-controller.js | 6 +- app/vendor/ledger/content-script.js | 18 -- .../pages/create-account/connect-hardware/index.js | 1 - 8 files changed, 209 insertions(+), 67 deletions(-) create mode 100644 app/scripts/lib/setupLedgerIframe.js delete mode 100644 app/vendor/ledger/content-script.js diff --git a/app/manifest.json b/app/manifest.json index 0bdf84ef1..84cedd687 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -48,8 +48,7 @@ "https://*/*" ], "js": [ - "contentscript.js", - "vendor/ledger/content-script.js" + "contentscript.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/scripts/background.js b/app/scripts/background.js index 3d3afdd4e..bff559469 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -68,8 +68,6 @@ initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() - - /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -446,3 +444,4 @@ extension.runtime.onInstalled.addListener(function (details) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } }) + diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index e0a2b0061..01df9e327 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -200,4 +200,4 @@ function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = extensionURL -} +} \ No newline at end of file diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index a6db49772..1c02f0610 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,68 +1,182 @@ - const extension = require('extensionizer') +const extension = require('extensionizer') const {EventEmitter} = require('events') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `m/44'/60'/0'` const type = 'Ledger Hardware Keyring' +const ORIGIN = 'http://localhost:9000' class LedgerKeyring extends EventEmitter { constructor (opts = {}) { super() this.type = type + this.page = 0 + this.perPage = 5 + this.unlockedAccount = 0 + this.paths = {} + this.iframe = null + this.setupIframe() this.deserialize(opts) } + setupIframe(){ + this.iframe = document.createElement('iframe') + this.iframe.src = ORIGIN + console.log('Injecting ledger iframe') + document.head.appendChild(this.iframe) + + + /* + Passing messages from iframe to background script + */ + console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') + + } + + sendMessage(msg, cb) { + console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) + this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') + window.addEventListener('message', event => { + if(event.origin !== ORIGIN) return false + if (event.data && event.data.action && event.data.action.search(name) !== -1) { + console.log('[LEDGER]: GOT MESAGE FROM IFRAME', event.data) + cb(event.data) + } + }) + } + serialize () { return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts}) } deserialize (opts = {}) { this.hdPath = opts.hdPath || hdPathString + this.unlocked = opts.unlocked || false this.accounts = opts.accounts || [] return Promise.resolve() } - async addAccounts (n = 1) { + isUnlocked () { + return this.unlocked + } + + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + unlock () { + + if (this.isUnlocked()) return Promise.resolve('already unlocked') + return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-add-account', - n, + this.sendMessage({ + action: 'ledger-unlock', + params: { + hdPath: this.hdPath, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } }) + }) + } - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { + async addAccounts (n = 1) { + return new Promise((resolve, reject) => { + this.unlock() + .then(_ => { + this.sendMessage({ + action: 'ledger-add-account', + params: { + n, + }, + }, + ({action, success, payload}) => { if (success) { resolve(payload) } else { reject(payload) - } - } + } + }) }) }) } - async getAccounts () { - return this.accounts.slice() + getFirstPage () { + this.page = 0 + return this.__getPage(1) } - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { + getNextPage () { + return this.__getPage(1) + } + + getPreviousPage () { + return this.__getPage(-1) + } + + __getPage (increment) { + + this.page += increment + + if (this.page <= 0) { this.page = 1 } + return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-sign-transaction', - address, - tx, + this.unlock() + .then(_ => { + this.sendMessage({ + action: 'ledger-get-page', + params: { + page: this.page, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) + }) + } - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { - if (success) { - resolve(payload) - } else { - reject(payload) - } - } + getAccounts () { + return Promise.resolve(this.accounts.slice()) + } + + removeAccount (address) { + if (!this.accounts.map(a => a.toLowerCase()).includes(address.toLowerCase())) { + throw new Error(`Address ${address} not found in this keyring`) + } + this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) + } + + // tx is an instance of the ethereumjs-transaction class. + async signTransaction (address, tx) { + return new Promise((resolve, reject) => { + this.unlock() + .then(_ => { + console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') + this.sendMessage({ + action: 'ledger-sign-transaction', + params: { + address, + tx, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) }) } @@ -74,20 +188,23 @@ class LedgerKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-sign-personal-message', - withAccount, - message, - }) - - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-personal-message') { - if (success) { - resolve(payload) - } else { - reject(payload) - } - } + this.unlock() + .then(_ => { + console.log('[LEDGER]: sending message ', 'ledger-sign-personal-message') + this.sendMessage({ + action: 'ledger-sign-personal-message', + params: { + withAccount, + message, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) }) } @@ -99,6 +216,14 @@ class LedgerKeyring extends EventEmitter { async exportAccount (address) { throw new Error('Not supported on this device') } + + forgetDevice () { + this.accounts = [] + this.unlocked = false + this.page = 0 + this.unlockedAccount = 0 + this.paths = {} + } } LedgerKeyring.type = type diff --git a/app/scripts/lib/setupLedgerIframe.js b/app/scripts/lib/setupLedgerIframe.js new file mode 100644 index 000000000..2831d072e --- /dev/null +++ b/app/scripts/lib/setupLedgerIframe.js @@ -0,0 +1,40 @@ +const extension = require('extensionizer') +module.exports = setupLedgerIframe +/** + * Injects an iframe into the current document to + * enable the interaction with ledger devices + */ +function setupLedgerIframe () { + const ORIGIN = 'http://localhost:9000' + const ledgerIframe = document.createElement('iframe') + ledgerIframe.src = ORIGIN + console.log('Injecting ledger iframe') + document.head.appendChild(ledgerIframe) + + console.log('[LEDGER]: LEDGER BG LISTENER READY') + extension.runtime.onMessage.addListener(({action, params}) => { + console.log('[LEDGER]: GOT MSG FROM THE KEYRING', action, params) + if (action.search('ledger-') !== -1) { + //Forward messages from the keyring to the iframe + sendMessage({action, params}) + } + }) + + function sendMessage(msg) { + ledgerIframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') + } + + /* + Passing messages from iframe to background script + */ + console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') + window.addEventListener('message', event => { + if(event.origin !== ORIGIN) return false + if (event.data && event.data.action && event.data.action.search('ledger-') !== -1) { + // Forward messages from the iframe to the keyring + console.log('[LEDGER] : forwarding msg', event.data) + extension.runtime.sendMessage(event.data) + } + }) + + } \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 593b722ff..58bab9789 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -546,12 +546,11 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = TrezorKeyring.type break case 'ledger': - keyringName = TrezorKeyring.type + keyringName = LedgerKeyring.type break default: throw new Error('MetamaskController:connectHardware - Unknown device') } - let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) @@ -568,10 +567,8 @@ module.exports = class MetamaskController extends EventEmitter { */ async connectHardware (deviceName, page) { - const oldAccounts = await this.keyringController.getAccounts() const keyring = await this.getKeyringForDevice(deviceName) let accounts = [] - switch (page) { case -1: accounts = await keyring.getPreviousPage() @@ -585,6 +582,7 @@ module.exports = class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated + const oldAccounts = await this.keyringController.getAccounts() const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] this.accountTracker.syncWithAddresses(accountsToTrack) return accounts diff --git a/app/vendor/ledger/content-script.js b/app/vendor/ledger/content-script.js deleted file mode 100644 index 425ff07a3..000000000 --- a/app/vendor/ledger/content-script.js +++ /dev/null @@ -1,18 +0,0 @@ -/* -Passing messages from background script to popup -*/ -let port = chrome.runtime.connect({ name: 'ledger' }); -port.onMessage.addListener(message => { - window.postMessage(message, window.location.origin); -}); -port.onDisconnect.addListener(d => { - port = null; -}); - /* -Passing messages from popup to background script -*/ - window.addEventListener('message', event => { - if (port && event.source === window && event.data) { - port.postMessage(event.data); - } -}); \ No newline at end of file diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 646ba8bec..86a4d7257 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -49,7 +49,6 @@ class ConnectHardwareForm extends Component { } connectToHardwareWallet = (device) => { - debugger if (this.state.accounts.length) { return null } -- cgit v1.2.3 From 8e842a8947997b7ea47cf35d1348788d21f2f467 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 05:02:02 -0400 Subject: able to add accounts --- app/scripts/eth-ledger-keyring-listener.js | 139 +++++++++++++++------ app/scripts/metamask-controller.js | 2 +- .../connect-hardware/account-list.js | 2 +- .../pages/create-account/connect-hardware/index.js | 4 +- 4 files changed, 107 insertions(+), 40 deletions(-) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 1c02f0610..b9b8eb3c8 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,11 +1,16 @@ const extension = require('extensionizer') const {EventEmitter} = require('events') +const HDKey = require('hdkey') +const ethUtil = require('ethereumjs-util') +const sigUtil = require('eth-sig-util') +const Transaction = require('ethereumjs-tx') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `m/44'/60'/0'` const type = 'Ledger Hardware Keyring' -const ORIGIN = 'http://localhost:9000' +const ORIGIN = 'https://localhost:3000' +const pathBase = 'm' class LedgerKeyring extends EventEmitter { constructor (opts = {}) { @@ -14,6 +19,7 @@ class LedgerKeyring extends EventEmitter { this.page = 0 this.perPage = 5 this.unlockedAccount = 0 + this.hdk = new HDKey() this.paths = {} this.iframe = null this.setupIframe() @@ -26,10 +32,6 @@ class LedgerKeyring extends EventEmitter { console.log('Injecting ledger iframe') document.head.appendChild(this.iframe) - - /* - Passing messages from iframe to background script - */ console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') } @@ -78,32 +80,39 @@ class LedgerKeyring extends EventEmitter { }, ({action, success, payload}) => { if (success) { - resolve(payload) + this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') + this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') + resolve('just unlocked') } else { - reject(payload) + reject(payload.error || 'Unknown error') } }) }) } - async addAccounts (n = 1) { + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + addAccounts (n = 1) { + return new Promise((resolve, reject) => { this.unlock() - .then(_ => { - this.sendMessage({ - action: 'ledger-add-account', - params: { - n, - }, - }, - ({action, success, payload}) => { - if (success) { - resolve(payload) - } else { - reject(payload) - } + .then(_ => { + const from = this.unlockedAccount + const to = from + n + this.accounts = [] + + for (let i = from; i < to; i++) { + const address = this._addressFromIndex(pathBase, i) + this.accounts.push(address) + this.page = 0 + } + resolve(this.accounts) + }) + .catch(e => { + reject(e) }) - }) }) } @@ -129,20 +138,27 @@ class LedgerKeyring extends EventEmitter { return new Promise((resolve, reject) => { this.unlock() .then(_ => { - this.sendMessage({ - action: 'ledger-get-page', - params: { - page: this.page, - }, - }, - ({action, success, payload}) => { - if (success) { - resolve(payload) - } else { - reject(payload) - } - }) - }) + + const from = (this.page - 1) * this.perPage + const to = from + this.perPage + + const accounts = [] + + for (let i = from; i < to; i++) { + const address = this._addressFromIndex(pathBase, i) + accounts.push({ + address: address, + balance: null, + index: i, + }) + this.paths[ethUtil.toChecksumAddress(address)] = i + + } + resolve(accounts) + }) + .catch(e => { + reject(e) + }) }) } @@ -157,6 +173,7 @@ class LedgerKeyring extends EventEmitter { this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) } + // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { return new Promise((resolve, reject) => { @@ -224,6 +241,56 @@ class LedgerKeyring extends EventEmitter { this.unlockedAccount = 0 this.paths = {} } + + /* PRIVATE METHODS */ + + _padLeftEven (hex) { + return hex.length % 2 !== 0 ? `0${hex}` : hex + } + + _normalize (buf) { + return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + } + + _addressFromIndex (pathBase, i) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + return ethUtil.toChecksumAddress(address) + } + + _pathFromAddress (address) { + const checksummedAddress = ethUtil.toChecksumAddress(address) + let index = this.paths[checksummedAddress] + if (typeof index === 'undefined') { + for (let i = 0; i < MAX_INDEX; i++) { + if (checksummedAddress === this._addressFromIndex(pathBase, i)) { + index = i + break + } + } + } + + if (typeof index === 'undefined') { + throw new Error('Unknown address') + } + return `${this.hdPath}/${index}` + } + + _toAscii (hex) { + let str = '' + let i = 0; const l = hex.length + if (hex.substring(0, 2) === '0x') { + i = 2 + } + for (; i < l; i += 2) { + const code = parseInt(hex.substr(i, 2), 16) + str += String.fromCharCode(code) + } + + return str + } } LedgerKeyring.type = type diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 58bab9789..c79e5141e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -615,7 +615,7 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockHardwareWalletAccount (deviceName, index) { + async unlockHardwareWalletAccount (index, deviceName) { const keyring = await this.getKeyringForDevice(deviceName) keyring.setAccountToUnlock(index) diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index 730f2df34..ac020345a 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -11,7 +11,7 @@ class AccountList extends Component { renderHeader () { return ( h('div.hw-connect', [ - h('h3.hw-connect__title', {}, this.context.t('selectAnAccount')), + h('h3.hw-connect__title', {}, `${this.props.device.toUpperCase()} - ${this.context.t('selectAnAccount')}`), h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')), ]) ) diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 86a4d7257..644742172 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -128,13 +128,13 @@ class ConnectHardwareForm extends Component { }) } - onUnlockAccount = () => { + onUnlockAccount = (device) => { if (this.state.selectedAccount === null) { this.setState({ error: this.context.t('accountSelectionRequired') }) } - this.props.unlockHardwareWalletAccount(this.state.selectedAccount, this.state.device) + this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device) .then(_ => { this.props.history.push(DEFAULT_ROUTE) }).catch(e => { -- cgit v1.2.3 From aa6a42e3deda1725cb5e4e0df97549f9facc18f5 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 05:11:21 -0400 Subject: rename keyring --- app/scripts/eth-ledger-keyring-listener.js | 2 +- ui/app/components/account-menu/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index b9b8eb3c8..1b30c4d6d 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -8,7 +8,7 @@ const Transaction = require('ethereumjs-tx') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `m/44'/60'/0'` -const type = 'Ledger Hardware Keyring' +const type = 'Ledger Hardware' const ORIGIN = 'https://localhost:3000' const pathBase = 'm' diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 9c063d31e..bcada41e3 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -229,6 +229,7 @@ AccountMenu.prototype.renderKeyringType = function (keyring) { let label switch (type) { case 'Trezor Hardware': + case 'Ledger Hardware': label = this.context.t('hardware') break case 'Simple Key Pair': -- cgit v1.2.3 From 011cc141b3f2971522a1d77cea7370f61bbc08d1 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 16:00:29 -0400 Subject: tx signing should work --- app/scripts/eth-ledger-keyring-listener.js | 49 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 1b30c4d6d..ad214138f 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -7,10 +7,11 @@ const Transaction = require('ethereumjs-tx') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger -const hdPathString = `m/44'/60'/0'` +const hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' const ORIGIN = 'https://localhost:3000' const pathBase = 'm' +const MAX_INDEX = 1000 class LedgerKeyring extends EventEmitter { constructor (opts = {}) { @@ -39,11 +40,11 @@ class LedgerKeyring extends EventEmitter { sendMessage(msg, cb) { console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') - window.addEventListener('message', event => { - if(event.origin !== ORIGIN) return false - if (event.data && event.data.action && event.data.action.search(name) !== -1) { - console.log('[LEDGER]: GOT MESAGE FROM IFRAME', event.data) - cb(event.data) + window.addEventListener('message', ({ origin, data }) => { + if(origin !== ORIGIN) return false + if (data && data.action && data.action === `${msg.action}-reply`) { + console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) + cb(data) } }) } @@ -176,20 +177,41 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { + return new Promise((resolve, reject) => { this.unlock() .then(_ => { console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') + this.sendMessage({ action: 'ledger-sign-transaction', params: { - address, - tx, + tx: { + to: this._normalize(tx.to), + value: this._normalize(tx.value), + data: this._normalize(tx.data), + chainId: tx._chainId, + nonce: this._fixNonce(this._normalize(tx.nonce)), + gasLimit: this._normalize(tx.gasLimit), + gasPrice: this._normalize(tx.gasPrice), + }, + path: this._pathFromAddress(address) }, }, ({action, success, payload}) => { if (success) { - resolve(payload) + console.log('[LEDGER]: got tx signed!', payload.txData) + const signedTx = new Transaction(payload.txData) + // Validate that the signature matches the right address + const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) + const correctAddress = ethUtil.toChecksumAddress(address) + if (addressSignedWith !== correctAddress) { + reject('signature doesnt match the right address') + } + console.log('[LEDGER]: all good!', signedTx.toJSON()) + console.log('[LEDGER]: signedTX', `0x${signedTx.serialize().toString('hex')}`) + + resolve(signedTx) } else { reject(payload) } @@ -249,7 +271,7 @@ class LedgerKeyring extends EventEmitter { } _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) } _addressFromIndex (pathBase, i) { @@ -291,6 +313,13 @@ class LedgerKeyring extends EventEmitter { return str } + + _fixNonce(nonce){ + if(nonce === '0x'){ + return `${nonce}0` + } + return nonce + } } LedgerKeyring.type = type -- cgit v1.2.3 From 12b41b8fc213b930eb96f77f49157e78b141ff43 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 18:20:50 -0400 Subject: tx signature is valid --- app/scripts/eth-ledger-keyring-listener.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index ad214138f..92c85f600 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -187,6 +187,7 @@ class LedgerKeyring extends EventEmitter { action: 'ledger-sign-transaction', params: { tx: { + from: this._normalize(address), to: this._normalize(tx.to), value: this._normalize(tx.value), data: this._normalize(tx.data), -- cgit v1.2.3 From 068bf43615fa0d0038f43798fbf9e04d03369e68 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 19:27:24 -0400 Subject: working --- app/scripts/controllers/transactions/index.js | 4 ++-- app/scripts/eth-ledger-keyring-listener.js | 34 ++++++++++----------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e2288aed..7ac6ec2e6 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -289,10 +289,10 @@ class TransactionController extends EventEmitter { // sign tx const fromAddress = txParams.from const ethTx = new Transaction(txParams) - await this.signEthTx(ethTx, fromAddress) + const signedTx = await this.signEthTx(ethTx, fromAddress) // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) - const rawTx = ethUtil.bufferToHex(ethTx.serialize()) + const rawTx = ethUtil.bufferToHex(signedTx.serialize()) return rawTx } diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 92c85f600..0043058e6 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -9,7 +9,7 @@ const Transaction = require('ethereumjs-tx') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' -const ORIGIN = 'https://localhost:3000' +const ORIGIN = 'https://localhost:3000' const pathBase = 'm' const MAX_INDEX = 1000 @@ -27,21 +27,18 @@ class LedgerKeyring extends EventEmitter { this.deserialize(opts) } - setupIframe(){ + setupIframe () { this.iframe = document.createElement('iframe') this.iframe.src = ORIGIN console.log('Injecting ledger iframe') document.head.appendChild(this.iframe) - - console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') - } - sendMessage(msg, cb) { + sendMessage (msg, cb) { console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') window.addEventListener('message', ({ origin, data }) => { - if(origin !== ORIGIN) return false + if (origin !== ORIGIN) return false if (data && data.action && data.action === `${msg.action}-reply`) { console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) cb(data) @@ -79,7 +76,7 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, }, }, - ({action, success, payload}) => { + ({action, success, payload}) => { if (success) { this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') @@ -177,12 +174,10 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { - + return new Promise((resolve, reject) => { this.unlock() .then(_ => { - console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') - this.sendMessage({ action: 'ledger-sign-transaction', params: { @@ -196,26 +191,22 @@ class LedgerKeyring extends EventEmitter { gasLimit: this._normalize(tx.gasLimit), gasPrice: this._normalize(tx.gasPrice), }, - path: this._pathFromAddress(address) + path: this._pathFromAddress(address), }, }, ({action, success, payload}) => { if (success) { - console.log('[LEDGER]: got tx signed!', payload.txData) const signedTx = new Transaction(payload.txData) // Validate that the signature matches the right address const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) const correctAddress = ethUtil.toChecksumAddress(address) if (addressSignedWith !== correctAddress) { reject('signature doesnt match the right address') - } - console.log('[LEDGER]: all good!', signedTx.toJSON()) - console.log('[LEDGER]: signedTX', `0x${signedTx.serialize().toString('hex')}`) - + } resolve(signedTx) } else { reject(payload) - } + } }) }) }) @@ -230,7 +221,6 @@ class LedgerKeyring extends EventEmitter { return new Promise((resolve, reject) => { this.unlock() .then(_ => { - console.log('[LEDGER]: sending message ', 'ledger-sign-personal-message') this.sendMessage({ action: 'ledger-sign-personal-message', params: { @@ -243,7 +233,7 @@ class LedgerKeyring extends EventEmitter { resolve(payload) } else { reject(payload) - } + } }) }) }) @@ -315,8 +305,8 @@ class LedgerKeyring extends EventEmitter { return str } - _fixNonce(nonce){ - if(nonce === '0x'){ + _fixNonce (nonce) { + if (nonce === '0x') { return `${nonce}0` } return nonce -- cgit v1.2.3 From e6d64cecf84d91f1db3aab5777db3530c9cd8ab7 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 20:26:34 -0400 Subject: message signing works --- app/scripts/eth-ledger-keyring-listener.js | 22 +++++++++++++++++----- app/scripts/lib/createLoggerMiddleware.js | 2 +- ui/app/css/itcss/components/new-account.scss | 1 + 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 0043058e6..bc5801106 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,4 +1,3 @@ -const extension = require('extensionizer') const {EventEmitter} = require('events') const HDKey = require('hdkey') const ethUtil = require('ethereumjs-util') @@ -202,7 +201,7 @@ class LedgerKeyring extends EventEmitter { const correctAddress = ethUtil.toChecksumAddress(address) if (addressSignedWith !== correctAddress) { reject('signature doesnt match the right address') - } + } resolve(signedTx) } else { reject(payload) @@ -218,19 +217,32 @@ class LedgerKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { + const humanReadableMsg = this._toAscii(message) + const bufferMsg = Buffer.from(humanReadableMsg).toString('hex') return new Promise((resolve, reject) => { this.unlock() .then(_ => { this.sendMessage({ action: 'ledger-sign-personal-message', params: { - withAccount, - message, + path: this._pathFromAddress(withAccount ), + message: bufferMsg, }, }, ({action, success, payload}) => { if (success) { - resolve(payload) + const { result } = payload + let v = result['v'] - 27 + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${result['r']}${result['s']}${v}` + const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) + if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { + reject('signature doesnt match the right address') + } + resolve(signature) } else { reject(payload) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 996c3477c..53913921c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -14,7 +14,7 @@ function createLoggerMiddleware (opts) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - log.info(`RPC (${opts.origin}):`, req, '->', res) + //log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index b12afb124..8a6201805 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -412,6 +412,7 @@ min-height: 54px; font-weight: 300; font-size: 14px; + margin-bottom: 20px } &__button.unlock { -- cgit v1.2.3 From 2355573340d0287582d0cd6a467e13126fbddd7c Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 00:06:36 -0400 Subject: clean up --- app/scripts/eth-ledger-keyring-listener.js | 81 +++++++++++++----------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index bc5801106..91507455a 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -4,11 +4,9 @@ const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const Transaction = require('ethereumjs-tx') - -// HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' -const ORIGIN = 'https://localhost:3000' +const BRIDGE_URL = 'https://localhost:3000' const pathBase = 'm' const MAX_INDEX = 1000 @@ -28,18 +26,15 @@ class LedgerKeyring extends EventEmitter { setupIframe () { this.iframe = document.createElement('iframe') - this.iframe.src = ORIGIN - console.log('Injecting ledger iframe') + this.iframe.src = BRIDGE_URL document.head.appendChild(this.iframe) } sendMessage (msg, cb) { - console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') window.addEventListener('message', ({ origin, data }) => { - if (origin !== ORIGIN) return false + if (origin !== BRIDGE_URL) return false if (data && data.action && data.action === `${msg.action}-reply`) { - console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) cb(data) } }) @@ -75,7 +70,7 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') @@ -87,10 +82,6 @@ class LedgerKeyring extends EventEmitter { }) } - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - addAccounts (n = 1) { return new Promise((resolve, reject) => { @@ -173,36 +164,44 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { - return new Promise((resolve, reject) => { this.unlock() .then(_ => { + + const newTx = new Transaction({ + from: this._normalize(address), + to: this._normalize(tx.to), + value: this._normalize(tx.value), + data: this._normalize(tx.data), + chainId: tx._chainId, + nonce: this._normalize(tx.nonce), + gasLimit: this._normalize(tx.gasLimit), + gasPrice: this._normalize(tx.gasPrice), + v: ethUtil.bufferToHex(tx.getChainId()), + r: '0x00', + s: '0x00', + }) + this.sendMessage({ action: 'ledger-sign-transaction', params: { - tx: { - from: this._normalize(address), - to: this._normalize(tx.to), - value: this._normalize(tx.value), - data: this._normalize(tx.data), - chainId: tx._chainId, - nonce: this._fixNonce(this._normalize(tx.nonce)), - gasLimit: this._normalize(tx.gasLimit), - gasPrice: this._normalize(tx.gasPrice), - }, - path: this._pathFromAddress(address), + tx: newTx.serialize().toString('hex'), + hdPath: this._pathFromAddress(address), }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { - const signedTx = new Transaction(payload.txData) - // Validate that the signature matches the right address - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) - const correctAddress = ethUtil.toChecksumAddress(address) - if (addressSignedWith !== correctAddress) { - reject('signature doesnt match the right address') + + newTx.v = Buffer.from(payload.v, 'hex') + newTx.r = Buffer.from(payload.r, 'hex') + newTx.s = Buffer.from(payload.s, 'hex') + + const valid = newTx.verifySignature() + if (valid) { + resolve(newTx) + } else { + reject('The transaction signature is not valid') } - resolve(signedTx) } else { reject(payload) } @@ -225,19 +224,18 @@ class LedgerKeyring extends EventEmitter { this.sendMessage({ action: 'ledger-sign-personal-message', params: { - path: this._pathFromAddress(withAccount ), + hdPath: this._pathFromAddress(withAccount), message: bufferMsg, }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { - const { result } = payload - let v = result['v'] - 27 + let v = payload['v'] - 27 v = v.toString(16) if (v.length < 2) { v = `0${v}` } - const signature = `0x${result['r']}${result['s']}${v}` + const signature = `0x${payload['r']}${payload['s']}${v}` const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { reject('signature doesnt match the right address') @@ -316,13 +314,6 @@ class LedgerKeyring extends EventEmitter { return str } - - _fixNonce (nonce) { - if (nonce === '0x') { - return `${nonce}0` - } - return nonce - } } LedgerKeyring.type = type -- cgit v1.2.3 From 0b9b892c6b27c55a016378387cb8d227957f8528 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 01:34:01 -0400 Subject: this should be ready to go --- app/scripts/eth-ledger-keyring-listener.js | 320 ----------------------------- app/scripts/metamask-controller.js | 6 +- package-lock.json | 56 +++++ package.json | 1 + 4 files changed, 60 insertions(+), 323 deletions(-) delete mode 100644 app/scripts/eth-ledger-keyring-listener.js diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js deleted file mode 100644 index 91507455a..000000000 --- a/app/scripts/eth-ledger-keyring-listener.js +++ /dev/null @@ -1,320 +0,0 @@ -const {EventEmitter} = require('events') -const HDKey = require('hdkey') -const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') -const Transaction = require('ethereumjs-tx') - -const hdPathString = `44'/60'/0'` -const type = 'Ledger Hardware' -const BRIDGE_URL = 'https://localhost:3000' -const pathBase = 'm' -const MAX_INDEX = 1000 - -class LedgerKeyring extends EventEmitter { - constructor (opts = {}) { - super() - this.type = type - this.page = 0 - this.perPage = 5 - this.unlockedAccount = 0 - this.hdk = new HDKey() - this.paths = {} - this.iframe = null - this.setupIframe() - this.deserialize(opts) - } - - setupIframe () { - this.iframe = document.createElement('iframe') - this.iframe.src = BRIDGE_URL - document.head.appendChild(this.iframe) - } - - sendMessage (msg, cb) { - this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') - window.addEventListener('message', ({ origin, data }) => { - if (origin !== BRIDGE_URL) return false - if (data && data.action && data.action === `${msg.action}-reply`) { - cb(data) - } - }) - } - - serialize () { - return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts}) - } - - deserialize (opts = {}) { - this.hdPath = opts.hdPath || hdPathString - this.unlocked = opts.unlocked || false - this.accounts = opts.accounts || [] - return Promise.resolve() - } - - isUnlocked () { - return this.unlocked - } - - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - - unlock () { - - if (this.isUnlocked()) return Promise.resolve('already unlocked') - - return new Promise((resolve, reject) => { - this.sendMessage({ - action: 'ledger-unlock', - params: { - hdPath: this.hdPath, - }, - }, - ({success, payload}) => { - if (success) { - this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') - this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') - resolve('just unlocked') - } else { - reject(payload.error || 'Unknown error') - } - }) - }) - } - - addAccounts (n = 1) { - - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - const from = this.unlockedAccount - const to = from + n - this.accounts = [] - - for (let i = from; i < to; i++) { - const address = this._addressFromIndex(pathBase, i) - this.accounts.push(address) - this.page = 0 - } - resolve(this.accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - getFirstPage () { - this.page = 0 - return this.__getPage(1) - } - - getNextPage () { - return this.__getPage(1) - } - - getPreviousPage () { - return this.__getPage(-1) - } - - __getPage (increment) { - - this.page += increment - - if (this.page <= 0) { this.page = 1 } - - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - - const from = (this.page - 1) * this.perPage - const to = from + this.perPage - - const accounts = [] - - for (let i = from; i < to; i++) { - const address = this._addressFromIndex(pathBase, i) - accounts.push({ - address: address, - balance: null, - index: i, - }) - this.paths[ethUtil.toChecksumAddress(address)] = i - - } - resolve(accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - getAccounts () { - return Promise.resolve(this.accounts.slice()) - } - - removeAccount (address) { - if (!this.accounts.map(a => a.toLowerCase()).includes(address.toLowerCase())) { - throw new Error(`Address ${address} not found in this keyring`) - } - this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) - } - - - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - - const newTx = new Transaction({ - from: this._normalize(address), - to: this._normalize(tx.to), - value: this._normalize(tx.value), - data: this._normalize(tx.data), - chainId: tx._chainId, - nonce: this._normalize(tx.nonce), - gasLimit: this._normalize(tx.gasLimit), - gasPrice: this._normalize(tx.gasPrice), - v: ethUtil.bufferToHex(tx.getChainId()), - r: '0x00', - s: '0x00', - }) - - this.sendMessage({ - action: 'ledger-sign-transaction', - params: { - tx: newTx.serialize().toString('hex'), - hdPath: this._pathFromAddress(address), - }, - }, - ({success, payload}) => { - if (success) { - - newTx.v = Buffer.from(payload.v, 'hex') - newTx.r = Buffer.from(payload.r, 'hex') - newTx.s = Buffer.from(payload.s, 'hex') - - const valid = newTx.verifySignature() - if (valid) { - resolve(newTx) - } else { - reject('The transaction signature is not valid') - } - } else { - reject(payload) - } - }) - }) - }) - } - - async signMessage (withAccount, data) { - throw new Error('Not supported on this device') - } - - // For personal_sign, we need to prefix the message: - async signPersonalMessage (withAccount, message) { - const humanReadableMsg = this._toAscii(message) - const bufferMsg = Buffer.from(humanReadableMsg).toString('hex') - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - this.sendMessage({ - action: 'ledger-sign-personal-message', - params: { - hdPath: this._pathFromAddress(withAccount), - message: bufferMsg, - }, - }, - ({success, payload}) => { - if (success) { - let v = payload['v'] - 27 - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload['r']}${payload['s']}${v}` - const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) - if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { - reject('signature doesnt match the right address') - } - resolve(signature) - } else { - reject(payload) - } - }) - }) - }) - } - - async signTypedData (withAccount, typedData) { - throw new Error('Not supported on this device') - } - - async exportAccount (address) { - throw new Error('Not supported on this device') - } - - forgetDevice () { - this.accounts = [] - this.unlocked = false - this.page = 0 - this.unlockedAccount = 0 - this.paths = {} - } - - /* PRIVATE METHODS */ - - _padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) - } - - _addressFromIndex (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(address) - } - - _pathFromAddress (address) { - const checksummedAddress = ethUtil.toChecksumAddress(address) - let index = this.paths[checksummedAddress] - if (typeof index === 'undefined') { - for (let i = 0; i < MAX_INDEX; i++) { - if (checksummedAddress === this._addressFromIndex(pathBase, i)) { - index = i - break - } - } - } - - if (typeof index === 'undefined') { - throw new Error('Unknown address') - } - return `${this.hdPath}/${index}` - } - - _toAscii (hex) { - let str = '' - let i = 0; const l = hex.length - if (hex.substring(0, 2) === '0x') { - i = 2 - } - for (; i < l; i += 2) { - const code = parseInt(hex.substr(i, 2), 16) - str += String.fromCharCode(code) - } - - return str - } -} - -LedgerKeyring.type = type -module.exports = LedgerKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c79e5141e..fed00077e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,7 +49,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') -const LedgerKeyring = require('./eth-ledger-keyring-listener') +const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') module.exports = class MetamaskController extends EventEmitter { @@ -128,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // key mgmt - const additionalKeyrings = [TrezorKeyring, LedgerKeyring] + const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, @@ -546,7 +546,7 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = TrezorKeyring.type break case 'ledger': - keyringName = LedgerKeyring.type + keyringName = LedgerBridgeKeyring.type break default: throw new Error('MetamaskController:connectHardware - Unknown device') diff --git a/package-lock.json b/package-lock.json index d48415cbc..249a78d3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8458,6 +8458,62 @@ } } }, + "eth-ledger-bridge-keyring": { + "version": "github:brunobar79/eth-ledger-bridge-keyring#f8f05925519a34e2d5ee3083ca95960fa70ddd11", + "from": "github:brunobar79/eth-ledger-bridge-keyring", + "requires": { + "eth-sig-util": "^1.4.2", + "ethereumjs-tx": "^1.3.4", + "ethereumjs-util": "^5.1.5", + "events": "^2.0.0", + "hdkey": "0.8.0" + }, + "dependencies": { + "ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + }, + "hdkey": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-0.8.0.tgz", + "integrity": "sha512-oYsdlK22eobT68N5faWI3776f6tOLyqxLLYwxMx+TP0rkWzuCs0oiOm2VbLWcxdpHFP4LtiRR8udaIX8VkEaZQ==", + "requires": { + "coinstring": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } + } + }, "eth-lib": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz", diff --git a/package.json b/package.json index c8a37f564..718e1e60d 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "eth-hd-keyring": "^1.2.2", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", + "eth-ledger-bridge-keyring": "github:brunobar79/eth-ledger-bridge-keyring", "eth-method-registry": "^1.0.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", -- cgit v1.2.3 From 8f9a0a535cbe904b0816c954c170c71843f62da9 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 01:43:41 -0400 Subject: clean up --- app/scripts/background.js | 3 ++- app/scripts/contentscript.js | 2 +- app/scripts/lib/createLoggerMiddleware.js | 2 +- app/scripts/lib/setupLedgerIframe.js | 40 ------------------------------- 4 files changed, 4 insertions(+), 43 deletions(-) delete mode 100644 app/scripts/lib/setupLedgerIframe.js diff --git a/app/scripts/background.js b/app/scripts/background.js index bff559469..3d3afdd4e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -68,6 +68,8 @@ initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() + + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -444,4 +446,3 @@ extension.runtime.onInstalled.addListener(function (details) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } }) - diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 01df9e327..e0a2b0061 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -200,4 +200,4 @@ function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = extensionURL -} \ No newline at end of file +} diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 53913921c..996c3477c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -14,7 +14,7 @@ function createLoggerMiddleware (opts) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - //log.info(`RPC (${opts.origin}):`, req, '->', res) + log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } diff --git a/app/scripts/lib/setupLedgerIframe.js b/app/scripts/lib/setupLedgerIframe.js deleted file mode 100644 index 2831d072e..000000000 --- a/app/scripts/lib/setupLedgerIframe.js +++ /dev/null @@ -1,40 +0,0 @@ -const extension = require('extensionizer') -module.exports = setupLedgerIframe -/** - * Injects an iframe into the current document to - * enable the interaction with ledger devices - */ -function setupLedgerIframe () { - const ORIGIN = 'http://localhost:9000' - const ledgerIframe = document.createElement('iframe') - ledgerIframe.src = ORIGIN - console.log('Injecting ledger iframe') - document.head.appendChild(ledgerIframe) - - console.log('[LEDGER]: LEDGER BG LISTENER READY') - extension.runtime.onMessage.addListener(({action, params}) => { - console.log('[LEDGER]: GOT MSG FROM THE KEYRING', action, params) - if (action.search('ledger-') !== -1) { - //Forward messages from the keyring to the iframe - sendMessage({action, params}) - } - }) - - function sendMessage(msg) { - ledgerIframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') - } - - /* - Passing messages from iframe to background script - */ - console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') - window.addEventListener('message', event => { - if(event.origin !== ORIGIN) return false - if (event.data && event.data.action && event.data.action.search('ledger-') !== -1) { - // Forward messages from the iframe to the keyring - console.log('[LEDGER] : forwarding msg', event.data) - extension.runtime.sendMessage(event.data) - } - }) - - } \ No newline at end of file -- cgit v1.2.3 From 77ad856730d5b54974b86e9bed7952ff1dc10fe0 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 01:46:09 -0400 Subject: remove ledger lib --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 718e1e60d..c29cef694 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ ] }, "dependencies": { - "@ledgerhq/hw-app-eth": "^4.21.0", "@material-ui/core": "1.0.0", "@zxing/library": "^0.7.0", "abi-decoder": "^1.0.9", -- cgit v1.2.3 From 4e1d8ba19db729f5c282c4e3c6b43433b562a45e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 13 Aug 2018 19:29:43 -0400 Subject: good progress adding paths --- app/scripts/metamask-controller.js | 19 ++++++---- package-lock.json | 43 ++++------------------ package.json | 2 +- ui/app/actions.js | 27 +++++++++----- .../connect-hardware/account-list.js | 41 ++++++++++++++++++++- .../pages/create-account/connect-hardware/index.js | 43 ++++++++++++++++------ ui/app/css/itcss/components/new-account.scss | 19 ++++++++++ ui/app/reducers/app.js | 13 +++++++ 8 files changed, 140 insertions(+), 67 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fed00077e..beaf04c0d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -539,7 +539,7 @@ module.exports = class MetamaskController extends EventEmitter { // Hardware // - async getKeyringForDevice (deviceName) { + async getKeyringForDevice (deviceName, hdPath = null) { let keyringName = null switch (deviceName) { case 'trezor': @@ -555,6 +555,10 @@ module.exports = class MetamaskController extends EventEmitter { if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) } + if (hdPath) { + console.log('[LEDGER]: HDPATH set', hdPath) + keyring.hdPath = hdPath + } return keyring @@ -565,9 +569,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns [] accounts */ - async connectHardware (deviceName, page) { - - const keyring = await this.getKeyringForDevice(deviceName) + async connectHardware (deviceName, page, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) let accounts = [] switch (page) { case -1: @@ -593,8 +596,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {Promise} */ - async checkHardwareStatus (deviceName) { - const keyring = await this.getKeyringForDevice(deviceName) + async checkHardwareStatus (deviceName, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) return keyring.isUnlocked() } @@ -615,8 +618,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockHardwareWalletAccount (index, deviceName) { - const keyring = await this.getKeyringForDevice(deviceName) + async unlockHardwareWalletAccount (index, deviceName, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) keyring.setAccountToUnlock(index) const oldAccounts = await this.keyringController.getAccounts() diff --git a/package-lock.json b/package-lock.json index 249a78d3b..4c45357fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -317,29 +317,6 @@ "through2": "^2.0.3" } }, - "@ledgerhq/hw-app-eth": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-4.21.0.tgz", - "integrity": "sha1-LYv75fCbkujWlRrmhQNtnVrqlv8=", - "requires": { - "@ledgerhq/hw-transport": "^4.21.0" - } - }, - "@ledgerhq/hw-transport": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-4.21.0.tgz", - "integrity": "sha1-UPhc/hFbo/nVv5R1XHAeknF1eU8=", - "requires": { - "events": "^2.0.0" - }, - "dependencies": { - "events": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", - "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" - } - } - }, "@material-ui/core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-1.0.0.tgz", @@ -8459,8 +8436,8 @@ } }, "eth-ledger-bridge-keyring": { - "version": "github:brunobar79/eth-ledger-bridge-keyring#f8f05925519a34e2d5ee3083ca95960fa70ddd11", - "from": "github:brunobar79/eth-ledger-bridge-keyring", + "version": "github:MetaMask/eth-ledger-bridge-keyring#d882deaae4c2ab0b83c3fac495f1972c47a1c8cd", + "from": "github:MetaMask/eth-ledger-bridge-keyring#master", "requires": { "eth-sig-util": "^1.4.2", "ethereumjs-tx": "^1.3.4", @@ -8653,13 +8630,12 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -30519,7 +30495,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -31524,7 +31499,6 @@ "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz", "integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=", "requires": { - "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", @@ -31533,7 +31507,7 @@ "dependencies": { "bignumber.js": { "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", - "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" } } }, @@ -32032,8 +32006,7 @@ "dev": true, "requires": { "underscore": "1.8.3", - "web3-core-helpers": "1.0.0-beta.34", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" + "web3-core-helpers": "1.0.0-beta.34" }, "dependencies": { "underscore": { @@ -32044,8 +32017,7 @@ }, "websocket": { "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", - "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible", - "dev": true, + "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "requires": { "debug": "^2.2.0", "nan": "^2.3.3", @@ -33395,8 +33367,7 @@ "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index c29cef694..237868dcd 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "eth-hd-keyring": "^1.2.2", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", - "eth-ledger-bridge-keyring": "github:brunobar79/eth-ledger-bridge-keyring", + "eth-ledger-bridge-keyring": "github:MetaMask/eth-ledger-bridge-keyring#master", "eth-method-registry": "^1.0.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", diff --git a/ui/app/actions.js b/ui/app/actions.js index 04af9d7c8..6bcc64e17 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -235,6 +235,8 @@ var actions = { UPDATE_TOKENS: 'UPDATE_TOKENS', setRpcTarget: setRpcTarget, setProviderType: setProviderType, + SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH', + setHardwareWalletDefaultHdPath, updateProviderType, // loading overlay SHOW_LOADING: 'SHOW_LOADING_INDICATION', @@ -639,12 +641,12 @@ function addNewAccount () { } } -function checkHardwareStatus (deviceName) { - log.debug(`background.checkHardwareStatus`, deviceName) +function checkHardwareStatus (deviceName, hdPath) { + log.debug(`background.checkHardwareStatus`, deviceName, hdPath) return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.checkHardwareStatus(deviceName, (err, unlocked) => { + background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) @@ -681,12 +683,12 @@ function forgetDevice (deviceName) { } } -function connectHardware (deviceName, page) { - log.debug(`background.connectHardware`, deviceName, page) +function connectHardware (deviceName, page, hdPath) { + log.debug(`background.connectHardware`, deviceName, page, hdPath) return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.connectHardware(deviceName, page, (err, accounts) => { + background.connectHardware(deviceName, page, hdPath, (err, accounts) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) @@ -702,12 +704,12 @@ function connectHardware (deviceName, page) { } } -function unlockHardwareWalletAccount (index, deviceName) { - log.debug(`background.unlockHardwareWalletAccount`, index, deviceName) +function unlockHardwareWalletAccount (index, deviceName, hdPath) { + log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath) return (dispatch, getState) => { dispatch(actions.showLoadingIndication()) return new Promise((resolve, reject) => { - background.unlockHardwareWalletAccount(index, deviceName, (err, accounts) => { + background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => { if (err) { log.error(err) dispatch(actions.displayWarning(err.message)) @@ -1854,6 +1856,13 @@ function showLoadingIndication (message) { } } +function setHardwareWalletDefaultHdPath ({ device, path }) { + return { + type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH, + value: {device, path}, + } +} + function hideLoadingIndication () { return { type: actions.HIDE_LOADING, diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index ac020345a..4c6cc67f9 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -2,16 +2,53 @@ const { Component } = require('react') const PropTypes = require('prop-types') const h = require('react-hyperscript') const genAccountLink = require('../../../../../lib/account-link.js') +const Select = require('react-select').default class AccountList extends Component { constructor (props, context) { super(props) } + getHdPaths () { + return [ + { + label: `m/44'/60'/0' (Legacy)`, + value: `m/44'/60'/0'`, + }, + { + label: `m/44'/60'/0'/0`, + value: `m/44'/60'/0'/0'`, + }, + ] + } + + renderHdPathSelector () { + const { onPathChange, selectedPath } = this.props + + const options = this.getHdPaths() + return h('div.hw-connect__hdPath', [ + h('h3.hw-connect__hdPath__title', {}, `HD Path`), + h(Select, { + className: 'hw-connect__hdPath__select', + name: 'hd-path-select', + clearable: false, + value: selectedPath, + options, + onChange: (opt) => { + onPathChange(opt.value) + }, + }), + ]) + } renderHeader () { + const { device } = this.props return ( h('div.hw-connect', [ - h('h3.hw-connect__title', {}, `${this.props.device.toUpperCase()} - ${this.context.t('selectAnAccount')}`), + + h('h3.hw-connect__title', {}, `${device.toUpperCase()} - ${this.context.t('selectAnAccount')}`), + + device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null, + h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')), ]) ) @@ -125,6 +162,8 @@ class AccountList extends Component { AccountList.propTypes = { + onPathChange: PropTypes.func.isRequired, + selectedPath: PropTypes.string.isRequired, device: PropTypes.string.isRequired, accounts: PropTypes.array.isRequired, onAccountChange: PropTypes.func.isRequired, diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 644742172..0eb2aa16f 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -18,7 +18,7 @@ class ConnectHardwareForm extends Component { accounts: [], browserSupported: true, unlocked: false, - device: null + device: null, } } @@ -40,10 +40,10 @@ class ConnectHardwareForm extends Component { async checkIfUnlocked () { ['trezor', 'ledger'].forEach(async device => { - const unlocked = await this.props.checkHardwareStatus(device) + const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device]) if (unlocked) { this.setState({unlocked: true}) - this.getPage(0, device) + this.getPage(0, device, this.props.defaultHdPaths[device]) } }) } @@ -52,8 +52,16 @@ class ConnectHardwareForm extends Component { if (this.state.accounts.length) { return null } + + // Default values this.setState({ btnText: this.context.t('connecting')}) - this.getPage(0, device) + this.getPage(0, device, this.props.defaultHdPaths[device]) + } + + onPathChange = (path) => { + console.log('BRUNO: path changed', path) + this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path}) + this.getPage(0, this.state.device, path) } onAccountChange = (account) => { @@ -68,9 +76,9 @@ class ConnectHardwareForm extends Component { }, 5000) } - getPage = (page, device) => { + getPage = (page, device, hdPath) => { this.props - .connectHardware(device, page) + .connectHardware(device, page, hdPath) .then(accounts => { if (accounts.length) { @@ -162,6 +170,8 @@ class ConnectHardwareForm extends Component { } return h(AccountList, { + onPathChange: this.onPathChange, + selectedPath: this.props.defaultHdPaths[this.state.device], device: this.state.device, accounts: this.state.accounts, selectedAccount: this.state.selectedAccount, @@ -193,12 +203,14 @@ ConnectHardwareForm.propTypes = { showAlert: PropTypes.func, hideAlert: PropTypes.func, unlockHardwareWalletAccount: PropTypes.func, + setHardwareWalletDefaultHdPath: PropTypes.func, numberOfExistingAccounts: PropTypes.number, history: PropTypes.object, t: PropTypes.func, network: PropTypes.string, accounts: PropTypes.object, address: PropTypes.string, + defaultHdPaths: PropTypes.object, } const mapStateToProps = state => { @@ -206,28 +218,35 @@ const mapStateToProps = state => { metamask: { network, selectedAddress, identities = {}, accounts = [] }, } = state const numberOfExistingAccounts = Object.keys(identities).length + const { + appState: { defaultHdPaths }, + } = state return { network, accounts, address: selectedAddress, numberOfExistingAccounts, + defaultHdPaths, } } const mapDispatchToProps = dispatch => { return { - connectHardware: (deviceName, page) => { - return dispatch(actions.connectHardware(deviceName, page)) + setHardwareWalletDefaultHdPath: ({device, path}) => { + return dispatch(actions.setHardwareWalletDefaultHdPath({device, path})) + }, + connectHardware: (deviceName, page, hdPath) => { + return dispatch(actions.connectHardware(deviceName, hdPath, page)) }, - checkHardwareStatus: (deviceName) => { - return dispatch(actions.checkHardwareStatus(deviceName)) + checkHardwareStatus: (deviceName, hdPath) => { + return dispatch(actions.checkHardwareStatus(deviceName, hdPath)) }, forgetDevice: (deviceName) => { return dispatch(actions.forgetDevice(deviceName)) }, - unlockHardwareWalletAccount: (index, deviceName) => { - return dispatch(actions.unlockHardwareWalletAccount(index, deviceName)) + unlockHardwareWalletAccount: (index, deviceName, hdPath) => { + return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath)) }, showImportPage: () => dispatch(actions.showImportPage()), showConnectPage: () => dispatch(actions.showConnectPage()), diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index 8a6201805..10d101601 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -178,6 +178,25 @@ } } + &__hdPath { + display: flex; + flex-direction: row; + margin-top: 15px; + margin-bottom: 15px; + font-size: 14px; + + &__title { + display: flex; + margin-top: 10px; + margin-right: 15px; + } + + &__select { + display: flex; + flex: 1; + } + } + &__learn-more { margin-top: 15px; font-size: 14px; diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 98d467163..4d70d2718 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -67,6 +67,10 @@ function reduceApp (state, action) { isMouseUser: false, gasIsLoading: false, networkNonce: null, + defaultHdPaths: { + trezor: `m/44'/60'/0'/0`, + ledger: `m/44'/60'/0'`, + }, }, state.appState) switch (action.type) { @@ -525,6 +529,15 @@ function reduceApp (state, action) { warning: '', }) + case actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH: + const { device, path } = action.value + const newDefaults = {...appState.defaultHdPaths} + newDefaults[device] = path + + return extend(appState, { + defaultHdPaths: newDefaults, + }) + case actions.SHOW_LOADING: return extend(appState, { isLoading: true, -- cgit v1.2.3 From 61a279204a804ddf4815d0db6103c19c5640bbb9 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 01:26:18 -0400 Subject: legacy and new hd path working --- app/_locales/en/messages.json | 6 +++ app/scripts/metamask-controller.js | 3 +- .../connect-hardware/account-list.js | 52 ++++++++++++++-------- .../pages/create-account/connect-hardware/index.js | 15 ++++--- ui/app/reducers/app.js | 2 +- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 62ec4ce37..a4ee97525 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -538,6 +538,9 @@ "learnMore": { "message": "Learn more" }, + "ledgerAccountRestriction": { + "message": "You need to make use your last account before you can add a new one." + }, "lessThanMax": { "message": "must be less than or equal to $1.", "description": "helper for inputting hex as decimal input" @@ -922,6 +925,9 @@ "selectAnAccountHelp": { "message": "These are the accounts available in your hardware wallet. Select the one you’d like to use in MetaMask." }, + "selectPathHelp": { + "message": "If you don't see your existing Ledger address(es), please try selecting a different HD Path \"Legacy (MEW / MyCrypto)\"" + }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index beaf04c0d..b1473390b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -556,10 +556,11 @@ module.exports = class MetamaskController extends EventEmitter { keyring = await this.keyringController.addNewKeyring(keyringName) } if (hdPath) { - console.log('[LEDGER]: HDPATH set', hdPath) keyring.hdPath = hdPath } + keyring.network = this.networkController.getProviderConfig().type + return keyring } diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index 4c6cc67f9..c8fb5030b 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -12,32 +12,47 @@ class AccountList extends Component { getHdPaths () { return [ { - label: `m/44'/60'/0' (Legacy)`, - value: `m/44'/60'/0'`, + label: `Ledger Live`, + value: `m/44'/60'/0'/0/0`, }, { - label: `m/44'/60'/0'/0`, - value: `m/44'/60'/0'/0'`, + label: `Legacy (MEW / MyCrypto)`, + value: `m/44'/60'/0'`, }, ] } + goToNextPage = () => { + if (this.props.accounts === 5) { + this.props.getPage(this.props.device, 1, this.props.selectedPath) + } else { + this.props.onAccountRestriction() + } + } + + goToPreviousPage = () => { + this.props.getPage(this.props.device, -1, this.props.selectedPath) + } + renderHdPathSelector () { const { onPathChange, selectedPath } = this.props const options = this.getHdPaths() - return h('div.hw-connect__hdPath', [ - h('h3.hw-connect__hdPath__title', {}, `HD Path`), - h(Select, { - className: 'hw-connect__hdPath__select', - name: 'hd-path-select', - clearable: false, - value: selectedPath, - options, - onChange: (opt) => { - onPathChange(opt.value) - }, - }), + return h('div', [ + h('div.hw-connect__hdPath', [ + h('h3.hw-connect__hdPath__title', {}, `HD Path`), + h(Select, { + className: 'hw-connect__hdPath__select', + name: 'hd-path-select', + clearable: false, + value: selectedPath, + options, + onChange: (opt) => { + onPathChange(opt.value) + }, + }), + ]), + h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')), ]) } renderHeader () { @@ -98,7 +113,7 @@ class AccountList extends Component { h( 'button.hw-list-pagination__button', { - onClick: () => this.props.getPage(-1, this.props.device), + onClick: this.goToPreviousPage, }, `< ${this.context.t('prev')}` ), @@ -106,7 +121,7 @@ class AccountList extends Component { h( 'button.hw-list-pagination__button', { - onClick: () => this.props.getPage(1, this.props.device), + onClick: this.goToNextPage, }, `${this.context.t('next')} >` ), @@ -174,6 +189,7 @@ AccountList.propTypes = { history: PropTypes.object, onUnlockAccount: PropTypes.func, onCancel: PropTypes.func, + onAccountRestriction: PropTypes.func, } AccountList.contextTypes = { diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 0eb2aa16f..e7e94686a 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -43,7 +43,7 @@ class ConnectHardwareForm extends Component { const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device]) if (unlocked) { this.setState({unlocked: true}) - this.getPage(0, device, this.props.defaultHdPaths[device]) + this.getPage(device, 0, this.props.defaultHdPaths[device]) } }) } @@ -55,19 +55,23 @@ class ConnectHardwareForm extends Component { // Default values this.setState({ btnText: this.context.t('connecting')}) - this.getPage(0, device, this.props.defaultHdPaths[device]) + this.getPage(device, 0, this.props.defaultHdPaths[device]) } onPathChange = (path) => { console.log('BRUNO: path changed', path) this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path}) - this.getPage(0, this.state.device, path) + this.getPage(this.state.device, 0, path) } onAccountChange = (account) => { this.setState({selectedAccount: account.toString(), error: null}) } + onAccountRestriction = () => { + this.setState({error: this.context.t('ledgerAccountRestriction') }) + } + showTemporaryAlert () { this.props.showAlert(this.context.t('hardwareWalletConnected')) // Autohide the alert after 5 seconds @@ -76,7 +80,7 @@ class ConnectHardwareForm extends Component { }, 5000) } - getPage = (page, device, hdPath) => { + getPage = (device, page, hdPath) => { this.props .connectHardware(device, page, hdPath) .then(accounts => { @@ -182,6 +186,7 @@ class ConnectHardwareForm extends Component { onUnlockAccount: this.onUnlockAccount, onForgetDevice: this.onForgetDevice, onCancel: this.onCancel, + onAccountRestriction: this.onAccountRestriction, }) } @@ -237,7 +242,7 @@ const mapDispatchToProps = dispatch => { return dispatch(actions.setHardwareWalletDefaultHdPath({device, path})) }, connectHardware: (deviceName, page, hdPath) => { - return dispatch(actions.connectHardware(deviceName, hdPath, page)) + return dispatch(actions.connectHardware(deviceName, page, hdPath)) }, checkHardwareStatus: (deviceName, hdPath) => { return dispatch(actions.checkHardwareStatus(deviceName, hdPath)) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 4d70d2718..c246e7904 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -69,7 +69,7 @@ function reduceApp (state, action) { networkNonce: null, defaultHdPaths: { trezor: `m/44'/60'/0'/0`, - ledger: `m/44'/60'/0'`, + ledger: `m/44'/60'/0'/0/0`, }, }, state.appState) -- cgit v1.2.3 From b77cc3d9690304362471fbfe207bdad461c2ca3a Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 03:42:23 -0400 Subject: fix tx tests --- app/scripts/controllers/transactions/index.js | 4 ++-- app/scripts/metamask-controller.js | 6 ++--- .../app/controllers/metamask-controller-test.js | 26 +++++++++++++++------- .../connect-hardware/account-list.js | 3 ++- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 7ac6ec2e6..8e2288aed 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -289,10 +289,10 @@ class TransactionController extends EventEmitter { // sign tx const fromAddress = txParams.from const ethTx = new Transaction(txParams) - const signedTx = await this.signEthTx(ethTx, fromAddress) + await this.signEthTx(ethTx, fromAddress) // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) - const rawTx = ethUtil.bufferToHex(signedTx.serialize()) + const rawTx = ethUtil.bufferToHex(ethTx.serialize()) return rawTx } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b1473390b..c813c58ac 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -549,14 +549,14 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = LedgerBridgeKeyring.type break default: - throw new Error('MetamaskController:connectHardware - Unknown device') + throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) } - if (hdPath) { - keyring.hdPath = hdPath + if (hdPath && keyring.setHdPath) { + keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 9164fe246..79412260c 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -226,9 +226,9 @@ describe('MetaMaskController', function () { it('should throw if it receives an unknown device name', async function () { try { - await metamaskController.connectHardware('Some random device name', 0) + await metamaskController.connectHardware('Some random device name', 0, `m/44/0'/0'`) } catch (e) { - assert.equal(e, 'Error: MetamaskController:connectHardware - Unknown device') + assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') } }) @@ -242,14 +242,24 @@ describe('MetaMaskController', function () { assert.equal(keyrings.length, 1) }) + it('should add the Ledger Hardware keyring', async function () { + sinon.spy(metamaskController.keyringController, 'addNewKeyring') + await metamaskController.connectHardware('ledger', 0).catch((e) => null) + const keyrings = await metamaskController.keyringController.getKeyringsByType( + 'Ledger Hardware' + ) + assert.equal(metamaskController.keyringController.addNewKeyring.getCall(0).args, 'Ledger Hardware') + assert.equal(keyrings.length, 1) + }) + }) describe('checkHardwareStatus', function () { it('should throw if it receives an unknown device name', async function () { try { - await metamaskController.checkHardwareStatus('Some random device name') + await metamaskController.checkHardwareStatus('Some random device name', `m/44/0'/0'`) } catch (e) { - assert.equal(e, 'Error: MetamaskController:checkHardwareStatus - Unknown device') + assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') } }) @@ -265,7 +275,7 @@ describe('MetaMaskController', function () { try { await metamaskController.forgetDevice('Some random device name') } catch (e) { - assert.equal(e, 'Error: MetamaskController:forgetDevice - Unknown device') + assert.equal(e, 'Error: MetamaskController:getKeyringForDevice - Unknown device') } }) @@ -282,7 +292,7 @@ describe('MetaMaskController', function () { }) }) - describe('unlockTrezorAccount', function () { + describe('unlockHardwareWalletAccount', function () { let accountToUnlock let windowOpenStub let addNewAccountStub @@ -305,8 +315,8 @@ describe('MetaMaskController', function () { sinon.spy(metamaskController.preferencesController, 'setAddresses') sinon.spy(metamaskController.preferencesController, 'setSelectedAddress') sinon.spy(metamaskController.preferencesController, 'setAccountLabel') - await metamaskController.connectHardware('trezor', 0).catch((e) => null) - await metamaskController.unlockTrezorAccount(accountToUnlock).catch((e) => null) + await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`) + await metamaskController.unlockHardwareWalletAccount('trezor', accountToUnlock, `m/44/0'/0'`) }) afterEach(function () { diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index c8fb5030b..0acaded6b 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -23,7 +23,8 @@ class AccountList extends Component { } goToNextPage = () => { - if (this.props.accounts === 5) { + // If we have < 5 accounts, it's restricted by BIP-44 + if (this.props.accounts.length === 5) { this.props.getPage(this.props.device, 1, this.props.selectedPath) } else { this.props.onAccountRestriction() -- cgit v1.2.3 From c72ced79aeb3d79d12ae240b680e0561122e5209 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 19:22:00 -0400 Subject: ui fixes --- app/_locales/en/messages.json | 19 ++++++++--------- ui/app/components/modals/account-details-modal.js | 16 +++++++++++++-- .../connect-hardware/connect-screen.js | 15 +++++++------- .../pages/create-account/connect-hardware/index.js | 24 +++++++++++++--------- ui/app/css/itcss/components/new-account.scss | 6 +++--- 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index a4ee97525..36a7a0d13 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -119,8 +119,8 @@ "close": { "message": "Close" }, - "chromeRequiredForTrezor":{ - "message": "You need to use Metamask on Google Chrome in order to connect to your TREZOR device." + "chromeRequiredForHardwareWallets":{ + "message": "You need to use Metamask on Google Chrome in order to connect to your Hardware Wallet." }, "confirm": { "message": "Confirm" @@ -149,11 +149,8 @@ "connectToTrezor": { "message": "Connect to Trezor" }, - "connectToTrezorHelp": { - "message": "Metamask is able to access your TREZOR ethereum accounts. First make sure your device is connected and unlocked." - }, - "connectToTrezorTrouble": { - "message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware." + "connectToLedger": { + "message": "Connect to Ledger" }, "continue": { "message": "Continue" @@ -426,11 +423,11 @@ "hardwareWalletConnected": { "message": "Hardware wallet connected" }, - "hardwareSupport": { - "message": "Hardware Support" + "hardwareWallets": { + "message": "Hardware Wallets" }, - "hardwareSupportMsg": { - "message": "You can now view your Hardware accounts in MetaMask! Scroll down and read how it works." + "hardwareWalletsMsg": { + "message": "You can now view your Hardware wallet accounts in MetaMask! Scroll down and read how it works." }, "havingTroubleConnecting": { "message": "Having trouble connecting?" diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index 5607cf051..cc90cf578 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -14,6 +14,7 @@ function mapStateToProps (state) { return { network: state.metamask.network, selectedIdentity: getSelectedIdentity(state), + keyrings: state.metamask.keyrings, } } @@ -50,9 +51,20 @@ AccountDetailsModal.prototype.render = function () { network, showExportPrivateKeyModal, setAccountLabel, + keyrings, } = this.props const { name, address } = selectedIdentity + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(address) + }) + + let exportPrivateKeyFeatureEnabled = true + // This feature is disabled for hardware wallets + if (keyring.type.search('Hardware') !== -1) { + exportPrivateKeyFeatureEnabled = false + } + return h(AccountModalContainer, {}, [ h(EditableLabel, { className: 'account-modal__name', @@ -73,9 +85,9 @@ AccountDetailsModal.prototype.render = function () { }, this.context.t('etherscanView')), // Holding on redesign for Export Private Key functionality - h('button.btn-primary.account-modal__button', { + exportPrivateKeyFeatureEnabled ? h('button.btn-primary.account-modal__button', { onClick: () => showExportPrivateKeyModal(), - }, this.context.t('exportPrivateKey')), + }, this.context.t('exportPrivateKey')) : null, ]) } diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js index af144d410..0a62f1c1e 100644 --- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js @@ -12,7 +12,7 @@ class ConnectScreen extends Component { h('div.new-account-connect-form.unsupported-browser', {}, [ h('div.hw-connect', [ h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')), - h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForTrezor')), + h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')), ]), h( 'button.btn-primary.btn--large', @@ -30,8 +30,8 @@ class ConnectScreen extends Component { renderHeader () { return ( h('div.hw-connect__header', {}, [ - h('h3.hw-connect__header__title', {}, this.context.t(`hardwareSupport`)), - h('p.hw-connect__header__msg', {}, this.context.t(`hardwareSupportMsg`)), + h('h3.hw-connect__header__title', {}, this.context.t(`hardwareWallets`)), + h('p.hw-connect__header__msg', {}, this.context.t(`hardwareWalletsMsg`)), ]) ) } @@ -50,7 +50,7 @@ class ConnectScreen extends Component { return h( 'button.btn-primary.btn--large', { onClick: this.props.connectToHardwareWallet.bind(this, 'trezor') }, - this.props.btnText + this.context.t('connectToTrezor') ) } @@ -58,7 +58,7 @@ class ConnectScreen extends Component { return h( 'button.btn-primary.btn--large', { onClick: this.props.connectToHardwareWallet.bind(this, 'ledger') }, - this.props.btnText.replace('Trezor', 'Ledger') + this.context.t('connectToLedger') ) } @@ -127,9 +127,9 @@ class ConnectScreen extends Component { return ( h('div.new-account-connect-form', {}, [ this.renderHeader(), - this.renderTrezorAffiliateLink(), - this.renderConnectToTrezorButton(), this.renderConnectToLedgerButton(), + this.renderConnectToTrezorButton(), + this.renderTrezorAffiliateLink(), this.renderLearnMore(), this.renderTutorialSteps(), this.renderFooter(), @@ -147,7 +147,6 @@ class ConnectScreen extends Component { ConnectScreen.propTypes = { connectToHardwareWallet: PropTypes.func.isRequired, - btnText: PropTypes.string.isRequired, browserSupported: PropTypes.bool.isRequired, } diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index e7e94686a..068b27cc2 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -6,14 +6,14 @@ const actions = require('../../../../actions') const ConnectScreen = require('./connect-screen') const AccountList = require('./account-list') const { DEFAULT_ROUTE } = require('../../../../routes') -const { formatBalance } = require('../../../../util') +const { formatBalance, getPlatform } = require('../../../../../../app/scripts/lib/util') +const { PLATFORM_FIREFOX } = require('../../../../../../app/scripts/lib/enums') class ConnectHardwareForm extends Component { constructor (props, context) { super(props) this.state = { error: null, - btnText: context.t('connectToTrezor'), selectedAccount: null, accounts: [], browserSupported: true, @@ -49,17 +49,22 @@ class ConnectHardwareForm extends Component { } connectToHardwareWallet = (device) => { + // None of the hardware wallets are supported + // At least for now + if (getPlatform() === PLATFORM_FIREFOX) { + this.setState({ browserSupported: false, error: null}) + return null + } + if (this.state.accounts.length) { return null } // Default values - this.setState({ btnText: this.context.t('connecting')}) this.getPage(device, 0, this.props.defaultHdPaths[device]) } onPathChange = (path) => { - console.log('BRUNO: path changed', path) this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path}) this.getPage(this.state.device, 0, path) } @@ -92,7 +97,7 @@ class ConnectHardwareForm extends Component { this.showTemporaryAlert() } - const newState = { unlocked: true, device } + const newState = { unlocked: true, device, error: null } // Default to the first account if (this.state.selectedAccount === null) { accounts.forEach((a, i) => { @@ -119,9 +124,10 @@ class ConnectHardwareForm extends Component { }) .catch(e => { if (e === 'Window blocked') { - this.setState({ browserSupported: false }) + this.setState({ browserSupported: false, error: null}) + } else { + this.setState({ error: e.toString() }) } - this.setState({ btnText: this.context.t('connectToTrezor') }) }) } @@ -130,7 +136,6 @@ class ConnectHardwareForm extends Component { .then(_ => { this.setState({ error: null, - btnText: this.context.t('connectToTrezor'), selectedAccount: null, accounts: [], unlocked: false, @@ -160,7 +165,7 @@ class ConnectHardwareForm extends Component { renderError () { return this.state.error - ? h('span.error', { style: { marginBottom: 40 } }, this.state.error) + ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error) : null } @@ -168,7 +173,6 @@ class ConnectHardwareForm extends Component { if (!this.state.accounts.length) { return h(ConnectScreen, { connectToHardwareWallet: this.connectToHardwareWallet, - btnText: this.state.btnText, browserSupported: this.state.browserSupported, }) } diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index 10d101601..b9e6ac000 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -174,7 +174,7 @@ font-size: 14px; color: #9b9b9b; margin-top: 10px; - margin-bottom: 0px; + margin-bottom: 20px; } } @@ -257,8 +257,8 @@ &__get-trezor { width: 100%; - padding-bottom: 20px; - padding-top: 20px; + padding-bottom: 10px; + padding-top: 10px; &__msg { font-size: 14px; -- cgit v1.2.3 From 19d1988715edfb6ee3ba8251af8ee9da20342234 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 19:24:15 -0400 Subject: fix --- ui/app/components/pages/create-account/connect-hardware/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 068b27cc2..4f01a2b0c 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -125,7 +125,7 @@ class ConnectHardwareForm extends Component { .catch(e => { if (e === 'Window blocked') { this.setState({ browserSupported: false, error: null}) - } else { + } else if (e !== 'Window closed') { this.setState({ error: e.toString() }) } }) -- cgit v1.2.3 From 53dcad5a3b89ca4f3a67c3f6b4d6b8f73ebd02e5 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 19:38:23 -0400 Subject: more ui --- ui/app/components/pages/create-account/connect-hardware/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 4f01a2b0c..547df5223 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -6,7 +6,8 @@ const actions = require('../../../../actions') const ConnectScreen = require('./connect-screen') const AccountList = require('./account-list') const { DEFAULT_ROUTE } = require('../../../../routes') -const { formatBalance, getPlatform } = require('../../../../../../app/scripts/lib/util') +const { formatBalance } = require('../../../../util') +const { getPlatform } = require('../../../../../../app/scripts/lib/util') const { PLATFORM_FIREFOX } = require('../../../../../../app/scripts/lib/enums') class ConnectHardwareForm extends Component { -- cgit v1.2.3 From fdf202efb066008f6625ba15cec8bcaef1edfec6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 21:19:01 -0400 Subject: fixed unit tests --- app/scripts/metamask-controller.js | 6 ++++++ test/unit/app/controllers/metamask-controller-test.js | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c813c58ac..ac188c4df 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -551,15 +551,21 @@ module.exports = class MetamaskController extends EventEmitter { default: throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } + console.log('getting keyring for device ', deviceName, hdPath) let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] + console.log('got Keyring', keyring) if (!keyring) { + console.log('we did not so lets add it', keyringName) keyring = await this.keyringController.addNewKeyring(keyringName) + console.log('what about now', keyring) } + console.log('setting hdPath', hdPath) if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type + console.log('setting network', keyring.network) return keyring diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 79412260c..9f25cf376 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -315,16 +315,20 @@ describe('MetaMaskController', function () { sinon.spy(metamaskController.preferencesController, 'setAddresses') sinon.spy(metamaskController.preferencesController, 'setSelectedAddress') sinon.spy(metamaskController.preferencesController, 'setAccountLabel') - await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`) - await metamaskController.unlockHardwareWalletAccount('trezor', accountToUnlock, `m/44/0'/0'`) + await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch((e) => null) + await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`) }) afterEach(function () { - metamaskController.keyringController.addNewAccount.restore() window.open.restore() + metamaskController.keyringController.addNewAccount.restore() + metamaskController.keyringController.getAccounts.restore() + metamaskController.preferencesController.setAddresses.restore() + metamaskController.preferencesController.setSelectedAddress.restore() + metamaskController.preferencesController.setAccountLabel.restore() }) - it('should set accountToUnlock in the keyring', async function () { + it('should set unlockedAccount in the keyring', async function () { const keyrings = await metamaskController.keyringController.getKeyringsByType( 'Trezor Hardware' ) @@ -332,7 +336,7 @@ describe('MetaMaskController', function () { }) - it('should call keyringController.addNewAccount', async function () { + it('should call keyringController.addNewAccount', async function () { assert(metamaskController.keyringController.addNewAccount.calledOnce) }) -- cgit v1.2.3 From 82a5ed1e03e0a6a9bc19b946bc178b236d9aaa98 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 22:40:29 -0400 Subject: remove console logs --- app/scripts/metamask-controller.js | 8 +------- ui/app/selectors/confirm-transaction.js | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ac188c4df..1e1aa035f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -551,21 +551,15 @@ module.exports = class MetamaskController extends EventEmitter { default: throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } - console.log('getting keyring for device ', deviceName, hdPath) let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] - console.log('got Keyring', keyring) if (!keyring) { - console.log('we did not so lets add it', keyringName) keyring = await this.keyringController.addNewKeyring(keyringName) - console.log('what about now', keyring) } - console.log('setting hdPath', hdPath) if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type - console.log('setting network', keyring.network) return keyring @@ -635,7 +629,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} #${parseInt(index, 10) + 1}`) + this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} ${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } }) diff --git a/ui/app/selectors/confirm-transaction.js b/ui/app/selectors/confirm-transaction.js index aa1fc5404..6e760c429 100644 --- a/ui/app/selectors/confirm-transaction.js +++ b/ui/app/selectors/confirm-transaction.js @@ -159,7 +159,7 @@ export const approveTokenAmountAndToAddressSelector = createSelector( if (tokenDecimals) { tokenAmount = calcTokenAmount(value, tokenDecimals) } - + tokenAmount = roundExponential(tokenAmount) } -- cgit v1.2.3 From e54b8507d2b21540487165178fb9128b42d3d8cc Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 23:02:01 -0400 Subject: use eth-ledger-bridge-keyring from npm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 237868dcd..07c163edb 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "eth-hd-keyring": "^1.2.2", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", - "eth-ledger-bridge-keyring": "github:MetaMask/eth-ledger-bridge-keyring#master", + "eth-ledger-bridge-keyring": "^0.1.0", "eth-method-registry": "^1.0.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", -- cgit v1.2.3 From d2d8d38346e0c25e972da535eec572338ad035e6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 23:08:50 -0400 Subject: update package-lock.json --- package-lock.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c45357fe..653661bcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8436,8 +8436,9 @@ } }, "eth-ledger-bridge-keyring": { - "version": "github:MetaMask/eth-ledger-bridge-keyring#d882deaae4c2ab0b83c3fac495f1972c47a1c8cd", - "from": "github:MetaMask/eth-ledger-bridge-keyring#master", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.1.0.tgz", + "integrity": "sha512-fZQry1rxA23swq7Qw9JolFltRePwIbKXCn9Vo6Qfr122cqqA3MBzV3WSI+ABQvwf3obQrMpbtqP5tiRxpX/0Vg==", "requires": { "eth-sig-util": "^1.4.2", "ethereumjs-tx": "^1.3.4", -- cgit v1.2.3 From 837be704f531b0b8851d285f269ed48849b0a425 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 15 Aug 2018 11:44:00 -0400 Subject: change Metamask for MetaMask --- app/_locales/en/messages.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1614ede81..515f8becb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -120,7 +120,7 @@ "message": "Close" }, "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." }, "confirm": { "message": "Confirm" @@ -152,12 +152,6 @@ "connectToTrezor": { "message": "Connect to Trezor" }, - "connectToTrezorHelp": { - "message": "MetaMask is able to access your TREZOR Ethereum accounts. First make sure your device is connected and unlocked." - }, - "connectToTrezorTrouble": { - "message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware." - }, "continue": { "message": "Continue" }, -- cgit v1.2.3 From 2ea05e303dedfd75cad6fdfddfa82da2ee32e92d Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 19:39:52 -0400 Subject: connect screen ready --- app/_locales/en/messages.json | 10 +-- app/images/ledger-logo.svg | 1 + app/images/trezor-logo.svg | 1 + .../connect-hardware/connect-screen.js | 88 ++++++++++++++++------ ui/app/css/itcss/components/new-account.scss | 63 +++++++++++++++- 5 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 app/images/ledger-logo.svg create mode 100644 app/images/trezor-logo.svg diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 515f8becb..52d11ff1c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -286,8 +286,8 @@ "downloadStateLogs": { "message": "Download State Logs" }, - "dontHaveATrezorWallet": { - "message": "Don't have a TREZOR hardware wallet?" + "dontHaveAHardwareWallet": { + "message": "Don’t have a hardware wallet?" }, "dropped": { "message": "Dropped" @@ -424,10 +424,10 @@ "message": "Hardware wallet connected" }, "hardwareWallets": { - "message": "Hardware Wallets" + "message": "Connect a hardware wallet" }, "hardwareWalletsMsg": { - "message": "You can now view your Hardware wallet accounts in MetaMask! Scroll down and read how it works." + "message": "Select a hardware wallet you'd like to use with MetaMask" }, "havingTroubleConnecting": { "message": "Having trouble connecting?" @@ -908,7 +908,7 @@ "description": "displays token symbol" }, "orderOneHere": { - "message": "Order one here." + "message": "Order a Trezor or Ledger and keep your funds in cold storage" }, "searchTokens": { "message": "Search Tokens" diff --git a/app/images/ledger-logo.svg b/app/images/ledger-logo.svg new file mode 100644 index 000000000..21b99d0e5 --- /dev/null +++ b/app/images/ledger-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/trezor-logo.svg b/app/images/trezor-logo.svg new file mode 100644 index 000000000..b8d85e3af --- /dev/null +++ b/app/images/trezor-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js index 0a62f1c1e..ba78daed0 100644 --- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js @@ -5,6 +5,52 @@ const h = require('react-hyperscript') class ConnectScreen extends Component { constructor (props, context) { super(props) + this.state = { + selectedDevice: null, + } + } + + connect = () => { + if (this.state.selectedDevice) { + this.props.connectToHardwareWallet(this.state.selectedDevice) + } + return null + } + + renderConnectToTrezorButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'trezor'}) }, + h('img.hw-connect__btn__img', { + src: 'images/trezor-logo.svg', + }) + ) + } + + renderConnectToLedgerButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'ledger'}) }, + h('img.hw-connect__btn__img', { + src: 'images/ledger-logo.svg', + }) + ) + } + + renderButtons () { + return ( + h('div', {}, [ + h('div.hw-connect__btn-wrapper', {}, [ + this.renderConnectToLedgerButton(), + this.renderConnectToTrezorButton(), + ]), + h( + `button.hw-connect__connect-btn${!this.state.selectedDevice ? '.disabled' : ''}`, + { onClick: this.connect }, + this.context.t('connect') + ), + ]) + ) } renderUnsupportedBrowser () { @@ -36,32 +82,26 @@ class ConnectScreen extends Component { ) } - renderTrezorAffiliateLink () { - return h('div.hw-connect__get-trezor', {}, [ - h('p.hw-connect__get-trezor__msg', {}, this.context.t(`dontHaveATrezorWallet`)), - h('a.hw-connect__get-trezor__link', { - href: 'https://shop.trezor.io/?a=metamask', - target: '_blank', - }, this.context.t('orderOneHere')), - ]) - } + getAffiliateLinks () { + const links = { + trezor: `Trezor`, + ledger: `Ledger`, + } - renderConnectToTrezorButton () { - return h( - 'button.btn-primary.btn--large', - { onClick: this.props.connectToHardwareWallet.bind(this, 'trezor') }, - this.context.t('connectToTrezor') - ) + const text = this.context.t('orderOneHere') + const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) + + return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }}) } - renderConnectToLedgerButton () { - return h( - 'button.btn-primary.btn--large', - { onClick: this.props.connectToHardwareWallet.bind(this, 'ledger') }, - this.context.t('connectToLedger') - ) + renderTrezorAffiliateLink () { + return h('div.hw-connect__get-hw', {}, [ + h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)), + this.getAffiliateLinks(), + ]) } + scrollToTutorial = (e) => { if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) } @@ -110,8 +150,7 @@ class ConnectScreen extends Component { return ( h('div.hw-connect__footer', {}, [ h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)), - this.renderConnectToTrezorButton(), - this.renderConnectToLedgerButton(), + this.renderButtons(), h('p.hw-connect__footer__msg', {}, [ this.context.t(`havingTroubleConnecting`), h('a.hw-connect__footer__link', { @@ -127,8 +166,7 @@ class ConnectScreen extends Component { return ( h('div.new-account-connect-form', {}, [ this.renderHeader(), - this.renderConnectToLedgerButton(), - this.renderConnectToTrezorButton(), + this.renderButtons(), this.renderTrezorAffiliateLink(), this.renderLearnMore(), this.renderTutorialSteps(), diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index b9e6ac000..ded5d11b5 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -167,7 +167,6 @@ margin-top: 5px; margin-bottom: 15px; font-size: 22px; - text-align: center; } &__msg { @@ -178,6 +177,66 @@ } } + &__btn-wrapper { + flex: 1; + flex-direction: row; + display: flex; + } + + &__connect-btn { + background-color: #259De5; + color: #fff; + border: none; + width: 315px; + min-height: 54px; + font-weight: 300; + font-size: 14px; + margin-bottom: 20px; + margin-top: 20px; + border-radius: 5px; + display: flex; + flex: 1; + margin-left: 20px; + margin-right: 20px; + justify-content: center; + text-transform: uppercase; + } + + &__connect-btn.disabled { + cursor: not-allowed; + opacity: .5; + } + + &__btn { + background: #fbfbfb; + border: 1px solid #e5e5e5; + height: 100px; + width: 150px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + + &__img { + width: 95px; + } + } + + &__btn.selected { + border: 2px solid #00a8ee; + width: 149px; + } + + &__btn:first-child { + margin-right: 15px; + margin-left: 20px; + } + + &__btn:last-child { + margin-right: 20px; + } + &__hdPath { display: flex; flex-direction: row; @@ -255,7 +314,7 @@ } } - &__get-trezor { + &__get-hw { width: 100%; padding-bottom: 10px; padding-top: 10px; -- cgit v1.2.3 From 285814646fa147560ffe6a8903eec7220eff09bb Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 20:41:23 -0400 Subject: ui ready --- app/_locales/en/messages.json | 7 +++++-- .../create-account/connect-hardware/account-list.js | 12 +++++++++--- ui/app/components/wallet-view.js | 16 +++++++++++++--- ui/app/css/itcss/components/new-account.scss | 18 +++++++++++++++--- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 52d11ff1c..a25a2bd59 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -920,10 +920,13 @@ "message": "Select an Account" }, "selectAnAccountHelp": { - "message": "These are the accounts available in your hardware wallet. Select the one you’d like to use in MetaMask." + "message": "Select the account to view in MetaMask" + }, + "selectHdPath": { + "message": "Select HD Path" }, "selectPathHelp": { - "message": "If you don't see your existing Ledger address(es), please try selecting a different HD Path \"Legacy (MEW / MyCrypto)\"" + "message": "If you don't see your existing Ledger accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\"" }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" diff --git a/ui/app/components/pages/create-account/connect-hardware/account-list.js b/ui/app/components/pages/create-account/connect-hardware/account-list.js index 0acaded6b..488a189ea 100644 --- a/ui/app/components/pages/create-account/connect-hardware/account-list.js +++ b/ui/app/components/pages/create-account/connect-hardware/account-list.js @@ -40,8 +40,9 @@ class AccountList extends Component { const options = this.getHdPaths() return h('div', [ + h('h3.hw-connect__hdPath__title', {}, this.context.t('selectHdPath')), + h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')), h('div.hw-connect__hdPath', [ - h('h3.hw-connect__hdPath__title', {}, `HD Path`), h(Select, { className: 'hw-connect__hdPath__select', name: 'hd-path-select', @@ -53,18 +54,23 @@ class AccountList extends Component { }, }), ]), - h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')), ]) } + + capitalizeDevice (device) { + return device.slice(0, 1).toUpperCase() + device.slice(1) + } + renderHeader () { const { device } = this.props return ( h('div.hw-connect', [ - h('h3.hw-connect__title', {}, `${device.toUpperCase()} - ${this.context.t('selectAnAccount')}`), + h('h3.hw-connect__unlock-title', {}, `${this.context.t('unlock')} ${this.capitalizeDevice(device)}`), device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null, + h('h3.hw-connect__hdPath__title', {}, this.context.t('selectAnAccount')), h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')), ]) ) diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 20c2be0f1..8e092364c 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -118,8 +118,18 @@ WalletView.prototype.render = function () { return kr.accounts.includes(selectedAddress) }) - const type = keyring.type - const isLoose = type !== 'HD Key Tree' + let label = '' + let type + if (keyring) { + type = keyring.type + if (type !== 'HD Key Tree') { + if (type.toLowerCase().search('hardware') !== -1) { + label = this.context.t('hardware') + } else { + label = this.context.t('imported') + } + } + } return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), { style: {}, @@ -133,7 +143,7 @@ WalletView.prototype.render = function () { onClick: hideSidebar, }), - h('div.wallet-view__keyring-label.allcaps', isLoose ? this.context.t('imported') : ''), + h('div.wallet-view__keyring-label.allcaps', label), h('div.flex-column.flex-center.wallet-view__name-container', { style: { margin: '0 auto' }, diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss index ded5d11b5..e4c7a4e0d 100644 --- a/ui/app/css/itcss/components/new-account.scss +++ b/ui/app/css/itcss/components/new-account.scss @@ -162,6 +162,8 @@ } .hw-connect { + width: 100%; + &__header { &__title { margin-top: 5px; @@ -241,7 +243,7 @@ display: flex; flex-direction: row; margin-top: 15px; - margin-bottom: 15px; + margin-bottom: 30px; font-size: 14px; &__title { @@ -279,6 +281,13 @@ font-size: 18px; } + &__unlock-title { + padding-top: 10px; + font-weight: 400; + font-size: 22px; + margin-bottom: 15px; + } + &__msg { font-size: 14px; color: #9b9b9b; @@ -291,8 +300,6 @@ } &__footer { - width: 100%; - &__title { padding-top: 15px; padding-bottom: 12px; @@ -306,6 +313,9 @@ color: #9b9b9b; margin-top: 12px; margin-bottom: 27px; + width: 100%; + display: block; + margin-left: 20px; } &__link { @@ -468,6 +478,8 @@ &.account-list { height: auto; + padding-left: 20px; + padding-right: 20px; } &__buttons { -- cgit v1.2.3 From b369560569df435ba2644638413264b3490e2093 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 20:59:11 -0400 Subject: fix e2e tests --- test/e2e/beta/from-import-beta-ui.spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index 5582d4697..6f06cd82f 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -359,7 +359,10 @@ describe('Using MetaMask with an existing account', function () { }) it('should open the TREZOR Connect popup', async () => { - const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect to Trezor')]`)) + const trezorButton = await findElements(driver, By.css('.hw-connect__btn')) + await trezorButton[1].click() + await delay(regularDelayMs) + const connectButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) await connectButtons[0].click() await delay(regularDelayMs) const allWindows = await driver.getAllWindowHandles() -- cgit v1.2.3 From 51e4a6d3355524cd8622d6d2893cc878a64dc53e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 21:15:50 -0400 Subject: fix ledger affiliate link --- .../components/pages/create-account/connect-hardware/connect-screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js index ba78daed0..b3dfa4ee2 100644 --- a/ui/app/components/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/components/pages/create-account/connect-hardware/connect-screen.js @@ -85,7 +85,7 @@ class ConnectScreen extends Component { getAffiliateLinks () { const links = { trezor: `Trezor`, - ledger: `Ledger`, + ledger: `Ledger`, } const text = this.context.t('orderOneHere') -- cgit v1.2.3