mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #5084 from MetaMask/ledger-support-without-provider
Ledger support (in case of rollback)
This commit is contained in:
commit
097c1e90e3
@ -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"
|
||||
@ -146,15 +146,12 @@
|
||||
"connecting": {
|
||||
"message": "Connecting..."
|
||||
},
|
||||
"connectToLedger": {
|
||||
"message": "Connect to Ledger"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@ -289,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"
|
||||
@ -426,11 +423,11 @@
|
||||
"hardwareWalletConnected": {
|
||||
"message": "Hardware wallet connected"
|
||||
},
|
||||
"hardwareSupport": {
|
||||
"message": "Hardware Support"
|
||||
"hardwareWallets": {
|
||||
"message": "Connect a hardware wallet"
|
||||
},
|
||||
"hardwareSupportMsg": {
|
||||
"message": "You can now view your Hardware accounts in MetaMask! Scroll down and read how it works."
|
||||
"hardwareWalletsMsg": {
|
||||
"message": "Select a hardware wallet you'd like to use with MetaMask"
|
||||
},
|
||||
"havingTroubleConnecting": {
|
||||
"message": "Having trouble connecting?"
|
||||
@ -538,6 +535,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"
|
||||
@ -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"
|
||||
@ -920,7 +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 accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\""
|
||||
},
|
||||
"sendTokensAnywhere": {
|
||||
"message": "Send Tokens to anyone with an Ethereum account"
|
||||
|
1
app/images/ledger-logo.svg
Normal file
1
app/images/ledger-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1916.3 516.8" width="2500" height="674"><style>.st0{fill:#333745}</style><g id="squares_1_"><path class="st0" d="M578.2 392.7V24.3h25.6v344.1h175.3v24.3H578.2zm327.5 5.1c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5H804.6c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.3 2.6-28.9 3.9-43.5 3.8zM898 135.6c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm238-21.8c19.2 0 37.1 3.8 51.2 10.2 14.1 7.7 26.9 19.2 38.4 37.1h1.3c-1.3-21.7-1.3-42.2-1.3-62.7V0h24.3v392.7h-16.6l-6.4-42.2c-20.5 30.7-51.2 47.3-89.6 47.3s-66.5-11.5-87-35.8c-20.5-23-29.4-57.6-29.4-102.3 0-47.3 10.2-83.2 29.4-108.7 19.2-25.6 48.6-37.2 85.7-37.2zm0 21.8c-29.4 0-52.4 10.2-67.8 32-15.3 20.5-23 51.2-23 92.1 0 78 30.7 116.4 90.8 116.4 30.7 0 53.7-9 67.8-26.9 14.1-17.9 21.7-47.3 21.7-89.6v-3.8c0-42.2-7.7-72.9-21.7-90.8-12.8-20.5-35.8-29.4-67.8-29.4zm379.9-16.6v17.9l-56.3 3.8c15.3 19.2 23 39.7 23 61.4 0 26.9-9 47.3-26.9 64-17.9 16.6-40.9 24.3-70.4 24.3-12.8 0-21.7 0-25.6-1.3-10.2 5.1-17.9 11.5-23 17.9-5.1 7.7-7.7 14.1-7.7 23s3.8 15.3 10.2 19.2c6.4 3.8 17.9 6.4 33.3 6.4h47.3c29.4 0 52.4 6.4 67.8 17.9s24.3 29.4 24.3 53.7c0 29.4-11.5 51.2-34.5 66.5-23 15.3-56.3 23-99.8 23-34.5 0-61.4-6.4-80.6-20.5-19.2-12.8-28.1-32-28.1-55 0-19.2 6.4-34.5 17.9-47.3s28.1-20.5 47.3-25.6c-7.7-3.8-15.3-9-19.2-15.3-5-6.2-7.7-13.8-7.7-21.7 0-17.9 11.5-34.5 34.5-48.6-15.3-6.4-28.1-16.6-37.1-30.7-9-14.1-12.8-30.7-12.8-48.6 0-26.9 9-49.9 25.6-66.5 17.9-16.6 40.9-24.3 70.4-24.3 17.9 0 32 1.3 42.2 5.1h85.7v1.3h.2zm-222.6 319.8c0 37.1 28.1 56.3 84.4 56.3 71.6 0 107.5-23 107.5-69.1 0-16.6-5.1-28.1-16.6-35.8-11.5-7.7-29.4-11.5-55-11.5h-44.8c-49.9 1.2-75.5 20.4-75.5 60.1zm21.8-235.4c0 21.7 6.4 37.1 19.2 49.9 12.8 11.5 29.4 17.9 51.2 17.9 23 0 40.9-6.4 52.4-17.9 12.8-11.5 17.9-28.1 17.9-49.9 0-23-6.4-40.9-19.2-52.4-12.8-11.5-29.4-17.9-52.4-17.9-21.7 0-39.7 6.4-51.2 19.2-12.8 11.4-17.9 29.3-17.9 51.1z"/><path class="st0" d="M1640 397.8c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5h-197c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.1 2.6-28.2 3.8-44.8 3.8zm-6.4-262.2c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm245.6-21.8c11.5 0 24.3 1.3 37.1 3.8l-5.1 24.3c-11.8-2.6-23.8-3.9-35.8-3.8-23 0-42.2 10.2-57.6 29.4-15.3 20.5-23 44.8-23 75.5v149.7h-25.6V119h21.7l2.6 49.9h1.3c11.5-20.5 23-34.5 35.8-42.2 15.4-9 30.7-12.9 48.6-12.9zM333.9 12.8h-183v245.6h245.6V76.7c.1-34.5-28.1-63.9-62.6-63.9zm-239.2 0H64c-34.5 0-64 28.1-64 64v30.7h94.7V12.8zM0 165h94.7v94.7H0V165zm301.9 245.6h30.7c34.5 0 64-28.1 64-64V316h-94.7v94.6zm-151-94.6h94.7v94.7h-94.7V316zM0 316v30.7c0 34.5 28.1 64 64 64h30.7V316H0z"/></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
1
app/images/trezor-logo.svg
Normal file
1
app/images/trezor-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="trezor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2567.5 722.3" width="2567.5" height="722.3"><style>.st0{display:none;fill:#fff;stroke:#000}</style><path id="rect25" class="st0" d="M1186 2932.6h46.2v147H1186v-147z"/><path id="path7" d="M249 0C149.9 0 69.7 80.2 69.7 179.3v67.2C34.9 252.8 0 261.2 0 272.1v350.7s0 9.7 10.9 14.3c39.5 16 194.9 71 230.6 83.6 4.6 1.7 5.9 1.7 7.1 1.7 1.7 0 2.5 0 7.1-1.7 35.7-12.6 191.5-67.6 231-83.6 10.1-4.2 10.5-13.9 10.5-13.9V272.1c0-10.9-34.4-19.7-69.3-25.6v-67.2C428.4 80.2 347.7 0 249 0zm0 85.7c58.4 0 93.7 35.3 93.7 93.7v58.4c-65.5-4.6-121.4-4.6-187.3 0v-58.4c0-58.5 35.3-93.7 93.6-93.7zm-.4 238.1c81.5 0 149.9 6.3 149.9 17.6v218.8c0 3.4-.4 3.8-3.4 5-2.9 1.3-139 50.4-139 50.4s-5.5 1.7-7.1 1.7c-1.7 0-7.1-2.1-7.1-2.1s-136.1-49.1-139-50.4-3.4-1.7-3.4-5V341c-.8-11.3 67.6-17.2 149.1-17.2z"/><g id="g3222" transform="translate(91.363 -287.434) scale(.95575)"><path id="path13" d="M666.6 890V639.3H575v-89.9h285.6v89.9h-90.7V890H666.6z"/><path id="path15" d="M1092 890l-47-107.1h-37.4V890H904.3V549.4h181.8c79.8 0 122.6 52.9 122.6 116.7 0 58.8-34 89.9-61.3 103.3l61.7 120.5H1092zm12.2-223.9c0-18.5-16.4-26.5-33.6-26.5h-63v53.8h63c17.2-.4 33.6-8.4 33.6-27.3z"/><path id="path17" d="M1262.9 890V549.4h258.3v89.9h-155.4v33.6h151.6v89.9h-151.6v37.4h155.4V890h-258.3z"/><path id="path19" d="M1574.9 890.4v-81.9l129.8-168.8h-129.8v-89.9h265.8v81.1l-130.2 169.7h134v89.9l-269.6-.1z"/><path id="path21" d="M1869.7 720.3c0-104.6 81.1-176.4 186.5-176.4 105 0 186.5 71.4 186.5 176.4 0 104.6-81.1 176-186.5 176s-186.5-71.4-186.5-176zm268 0c0-47.5-32.3-85.3-81.9-85.3-49.6 0-81.9 37.8-81.9 85.3s32.3 85.3 81.9 85.3c50 0 81.9-37.8 81.9-85.3z"/><path id="path23" d="M2473.6 890.4l-47-107.1h-37.4v107.1h-103.3V549.8h181.8c79.8 0 122.6 52.9 122.6 116.7 0 58.8-34 89.9-61.3 103.3l61.7 120.5h-117.1zm12.6-224.3c0-18.5-16.4-26.5-33.6-26.5h-63v53.8h63c17.3-.4 33.6-8.4 33.6-27.3z"/></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -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 LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
|
||||
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
@ -127,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
|
||||
// key mgmt
|
||||
const additionalKeyrings = [TrezorKeyring]
|
||||
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
|
||||
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,45 +539,57 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Hardware
|
||||
//
|
||||
|
||||
async getKeyringForDevice (deviceName, hdPath = null) {
|
||||
let keyringName = null
|
||||
switch (deviceName) {
|
||||
case 'trezor':
|
||||
keyringName = TrezorKeyring.type
|
||||
break
|
||||
case 'ledger':
|
||||
keyringName = LedgerBridgeKeyring.type
|
||||
break
|
||||
default:
|
||||
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.setHdPath) {
|
||||
keyring.setHdPath(hdPath)
|
||||
}
|
||||
|
||||
keyring.network = this.networkController.getProviderConfig().type
|
||||
|
||||
return keyring
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch account list from a trezor device.
|
||||
*
|
||||
* @returns [] accounts
|
||||
*/
|
||||
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')
|
||||
async connectHardware (deviceName, page, hdPath) {
|
||||
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||
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 oldAccounts = await this.keyringController.getAccounts()
|
||||
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
|
||||
this.accountTracker.syncWithAddresses(accountsToTrack)
|
||||
return accounts
|
||||
}
|
||||
|
||||
/**
|
||||
@ -586,21 +597,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
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')
|
||||
}
|
||||
async checkHardwareStatus (deviceName, hdPath) {
|
||||
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||
return keyring.isUnlocked()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -610,20 +609,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 +619,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 (index, deviceName, hdPath) {
|
||||
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
73
package-lock.json
generated
73
package-lock.json
generated
@ -8435,6 +8435,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eth-ledger-bridge-keyring": {
|
||||
"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",
|
||||
"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",
|
||||
@ -8574,13 +8631,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"
|
||||
@ -30440,7 +30496,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"
|
||||
}
|
||||
@ -31445,7 +31500,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": "*",
|
||||
@ -31454,7 +31508,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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -31953,8 +32007,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": {
|
||||
@ -31965,8 +32018,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",
|
||||
@ -33316,8 +33368,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",
|
||||
|
@ -110,6 +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": "^0.1.0",
|
||||
"eth-method-registry": "^1.0.0",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
|
@ -366,7 +366,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()
|
||||
|
@ -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,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).catch((e) => null)
|
||||
await metamaskController.unlockTrezorAccount(accountToUnlock).catch((e) => null)
|
||||
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'
|
||||
)
|
||||
@ -322,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)
|
||||
})
|
||||
|
||||
|
@ -91,7 +91,7 @@ var actions = {
|
||||
connectHardware,
|
||||
checkHardwareStatus,
|
||||
forgetDevice,
|
||||
unlockTrezorAccount,
|
||||
unlockHardwareWalletAccount,
|
||||
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
|
||||
navigateToNewAccountScreen,
|
||||
resetAccount,
|
||||
@ -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 unlockTrezorAccount (index) {
|
||||
log.debug(`background.unlockTrezorAccount`, index)
|
||||
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
|
||||
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
|
||||
return (dispatch, getState) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
return new Promise((resolve, reject) => {
|
||||
background.unlockTrezorAccount(index, (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,
|
||||
|
@ -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':
|
||||
|
@ -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,
|
||||
|
||||
])
|
||||
}
|
||||
|
@ -2,16 +2,75 @@ 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: `Ledger Live`,
|
||||
value: `m/44'/60'/0'/0/0`,
|
||||
},
|
||||
{
|
||||
label: `Legacy (MEW / MyCrypto)`,
|
||||
value: `m/44'/60'/0'`,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
goToNextPage = () => {
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
goToPreviousPage = () => {
|
||||
this.props.getPage(this.props.device, -1, this.props.selectedPath)
|
||||
}
|
||||
|
||||
renderHdPathSelector () {
|
||||
const { onPathChange, selectedPath } = this.props
|
||||
|
||||
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(Select, {
|
||||
className: 'hw-connect__hdPath__select',
|
||||
name: 'hd-path-select',
|
||||
clearable: false,
|
||||
value: selectedPath,
|
||||
options,
|
||||
onChange: (opt) => {
|
||||
onPathChange(opt.value)
|
||||
},
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
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', {}, 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')),
|
||||
])
|
||||
)
|
||||
@ -61,7 +120,7 @@ class AccountList extends Component {
|
||||
h(
|
||||
'button.hw-list-pagination__button',
|
||||
{
|
||||
onClick: () => this.props.getPage(-1),
|
||||
onClick: this.goToPreviousPage,
|
||||
},
|
||||
`< ${this.context.t('prev')}`
|
||||
),
|
||||
@ -69,7 +128,7 @@ class AccountList extends Component {
|
||||
h(
|
||||
'button.hw-list-pagination__button',
|
||||
{
|
||||
onClick: () => this.props.getPage(1),
|
||||
onClick: this.goToNextPage,
|
||||
},
|
||||
`${this.context.t('next')} >`
|
||||
),
|
||||
@ -95,7 +154,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 +165,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 +184,9 @@ 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,
|
||||
onForgetDevice: PropTypes.func.isRequired,
|
||||
@ -134,6 +196,7 @@ AccountList.propTypes = {
|
||||
history: PropTypes.object,
|
||||
onUnlockAccount: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onAccountRestriction: PropTypes.func,
|
||||
}
|
||||
|
||||
AccountList.contextTypes = {
|
||||
|
@ -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 () {
|
||||
@ -12,7 +58,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,29 +76,31 @@ 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`)),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
getAffiliateLinks () {
|
||||
const links = {
|
||||
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=metamask' target='_blank'>Trezor</a>`,
|
||||
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s?r=17c4991a03fa&tracker=MY_TRACKER' target='_blank'>Ledger</a>`,
|
||||
}
|
||||
|
||||
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 }})
|
||||
}
|
||||
|
||||
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')),
|
||||
return h('div.hw-connect__get-hw', {}, [
|
||||
h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)),
|
||||
this.getAffiliateLinks(),
|
||||
])
|
||||
}
|
||||
|
||||
renderConnectToTrezorButton () {
|
||||
return h(
|
||||
'button.btn-primary.btn--large',
|
||||
{ onClick: this.props.connectToTrezor.bind(this) },
|
||||
this.props.btnText
|
||||
)
|
||||
}
|
||||
|
||||
scrollToTutorial = (e) => {
|
||||
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
||||
@ -102,7 +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.renderButtons(),
|
||||
h('p.hw-connect__footer__msg', {}, [
|
||||
this.context.t(`havingTroubleConnecting`),
|
||||
h('a.hw-connect__footer__link', {
|
||||
@ -118,8 +166,8 @@ class ConnectScreen extends Component {
|
||||
return (
|
||||
h('div.new-account-connect-form', {}, [
|
||||
this.renderHeader(),
|
||||
this.renderButtons(),
|
||||
this.renderTrezorAffiliateLink(),
|
||||
this.renderConnectToTrezorButton(),
|
||||
this.renderLearnMore(),
|
||||
this.renderTutorialSteps(),
|
||||
this.renderFooter(),
|
||||
@ -136,8 +184,7 @@ class ConnectScreen extends Component {
|
||||
}
|
||||
|
||||
ConnectScreen.propTypes = {
|
||||
connectToTrezor: PropTypes.func.isRequired,
|
||||
btnText: PropTypes.string.isRequired,
|
||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||
browserSupported: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
|
@ -7,17 +7,19 @@ const ConnectScreen = require('./connect-screen')
|
||||
const AccountList = require('./account-list')
|
||||
const { DEFAULT_ROUTE } = require('../../../../routes')
|
||||
const { formatBalance } = require('../../../../util')
|
||||
const { 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,
|
||||
unlocked: false,
|
||||
device: null,
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,25 +40,44 @@ 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, this.props.defaultHdPaths[device])
|
||||
if (unlocked) {
|
||||
this.setState({unlocked: true})
|
||||
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
connectToTrezor = () => {
|
||||
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
|
||||
}
|
||||
this.setState({ btnText: this.context.t('connecting')})
|
||||
this.getPage(0)
|
||||
|
||||
// Default values
|
||||
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||
}
|
||||
|
||||
onPathChange = (path) => {
|
||||
this.props.setHardwareWalletDefaultHdPath({device: 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
|
||||
@ -65,9 +86,9 @@ class ConnectHardwareForm extends Component {
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
getPage = (page) => {
|
||||
getPage = (device, page, hdPath) => {
|
||||
this.props
|
||||
.connectHardware('trezor', page)
|
||||
.connectHardware(device, page, hdPath)
|
||||
.then(accounts => {
|
||||
if (accounts.length) {
|
||||
|
||||
@ -77,7 +98,7 @@ class ConnectHardwareForm extends Component {
|
||||
this.showTemporaryAlert()
|
||||
}
|
||||
|
||||
const newState = { unlocked: true }
|
||||
const newState = { unlocked: true, device, error: null }
|
||||
// Default to the first account
|
||||
if (this.state.selectedAccount === null) {
|
||||
accounts.forEach((a, i) => {
|
||||
@ -104,18 +125,18 @@ class ConnectHardwareForm extends Component {
|
||||
})
|
||||
.catch(e => {
|
||||
if (e === 'Window blocked') {
|
||||
this.setState({ browserSupported: false })
|
||||
this.setState({ browserSupported: false, error: null})
|
||||
} else if (e !== 'Window closed') {
|
||||
this.setState({ error: e.toString() })
|
||||
}
|
||||
this.setState({ btnText: this.context.t('connectToTrezor') })
|
||||
})
|
||||
}
|
||||
|
||||
onForgetDevice = () => {
|
||||
this.props.forgetDevice('trezor')
|
||||
onForgetDevice = (device) => {
|
||||
this.props.forgetDevice(device)
|
||||
.then(_ => {
|
||||
this.setState({
|
||||
error: null,
|
||||
btnText: this.context.t('connectToTrezor'),
|
||||
selectedAccount: null,
|
||||
accounts: [],
|
||||
unlocked: false,
|
||||
@ -125,13 +146,13 @@ class ConnectHardwareForm extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
onUnlockAccount = () => {
|
||||
onUnlockAccount = (device) => {
|
||||
|
||||
if (this.state.selectedAccount === null) {
|
||||
this.setState({ error: this.context.t('accountSelectionRequired') })
|
||||
}
|
||||
|
||||
this.props.unlockTrezorAccount(this.state.selectedAccount)
|
||||
this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
|
||||
.then(_ => {
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
}).catch(e => {
|
||||
@ -145,20 +166,22 @@ 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
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
if (!this.state.accounts.length) {
|
||||
return h(ConnectScreen, {
|
||||
connectToTrezor: this.connectToTrezor,
|
||||
btnText: this.state.btnText,
|
||||
connectToHardwareWallet: this.connectToHardwareWallet,
|
||||
browserSupported: this.state.browserSupported,
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
onAccountChange: this.onAccountChange,
|
||||
@ -168,6 +191,7 @@ class ConnectHardwareForm extends Component {
|
||||
onUnlockAccount: this.onUnlockAccount,
|
||||
onForgetDevice: this.onForgetDevice,
|
||||
onCancel: this.onCancel,
|
||||
onAccountRestriction: this.onAccountRestriction,
|
||||
})
|
||||
}
|
||||
|
||||
@ -188,13 +212,15 @@ ConnectHardwareForm.propTypes = {
|
||||
forgetDevice: PropTypes.func,
|
||||
showAlert: PropTypes.func,
|
||||
hideAlert: PropTypes.func,
|
||||
unlockTrezorAccount: 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 => {
|
||||
@ -202,28 +228,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}))
|
||||
},
|
||||
checkHardwareStatus: (deviceName) => {
|
||||
return dispatch(actions.checkHardwareStatus(deviceName))
|
||||
connectHardware: (deviceName, page, hdPath) => {
|
||||
return dispatch(actions.connectHardware(deviceName, page, hdPath))
|
||||
},
|
||||
checkHardwareStatus: (deviceName, hdPath) => {
|
||||
return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
|
||||
},
|
||||
forgetDevice: (deviceName) => {
|
||||
return dispatch(actions.forgetDevice(deviceName))
|
||||
},
|
||||
unlockTrezorAccount: index => {
|
||||
return dispatch(actions.unlockTrezorAccount(index))
|
||||
unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
|
||||
return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
|
@ -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' },
|
||||
|
@ -162,19 +162,99 @@
|
||||
}
|
||||
|
||||
.hw-connect {
|
||||
width: 100%;
|
||||
|
||||
&__header {
|
||||
&__title {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__msg {
|
||||
font-size: 14px;
|
||||
color: #9b9b9b;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__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;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
&__select {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,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;
|
||||
@ -213,8 +300,6 @@
|
||||
}
|
||||
|
||||
&__footer {
|
||||
width: 100%;
|
||||
|
||||
&__title {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 12px;
|
||||
@ -228,6 +313,9 @@
|
||||
color: #9b9b9b;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 27px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
&__link {
|
||||
@ -236,10 +324,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__get-trezor {
|
||||
&__get-hw {
|
||||
width: 100%;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
|
||||
&__msg {
|
||||
font-size: 14px;
|
||||
@ -390,6 +478,8 @@
|
||||
|
||||
&.account-list {
|
||||
height: auto;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
@ -412,6 +502,7 @@
|
||||
min-height: 54px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px
|
||||
}
|
||||
|
||||
&__button.unlock {
|
||||
|
@ -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'/0/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,
|
||||
|
@ -159,7 +159,7 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
|
||||
if (tokenDecimals) {
|
||||
tokenAmount = calcTokenAmount(value, tokenDecimals)
|
||||
}
|
||||
|
||||
|
||||
tokenAmount = roundExponential(tokenAmount)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user