From ed17c2b303079dc5c5509acf2daf355e697bbbed Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 15 Sep 2020 12:35:39 +0200 Subject: [PATCH 1/3] Fix ledger live derivation path pattern For Ledger Live derivation path pattern is `m/44'/60'/x'/0/0` where `x` means an index of account. The default implementation of `LedgerSubprovider` from `@0x/subproviders` pavkage doesn't support thi pattern. Here we added a custom implementation of fetching Ledger Live/Legacy accounts. --- src/connectors/ledger.js | 7 ++-- src/connectors/ledger_subprovider.js | 54 ++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/connectors/ledger.js b/src/connectors/ledger.js index e6096fee..597f3608 100644 --- a/src/connectors/ledger.js +++ b/src/connectors/ledger.js @@ -97,8 +97,11 @@ export class LedgerConnector extends AbstractConnector { return this.defaultAccount } - async getAccounts(numberOfAccounts = 15) { - return await this.provider._providers[0].getAccountsAsync(numberOfAccounts) + async getAccounts(numberOfAccounts = 5, accountsOffSet = 0) { + return await this.provider._providers[0].getAccountsAsync( + numberOfAccounts, + accountsOffSet + ) } setDefaultAccount(account) { diff --git a/src/connectors/ledger_subprovider.js b/src/connectors/ledger_subprovider.js index 99e21724..3f482f51 100644 --- a/src/connectors/ledger_subprovider.js +++ b/src/connectors/ledger_subprovider.js @@ -2,6 +2,11 @@ import { LedgerSubprovider as LedgerSubprovider0x } from "@0x/subproviders/lib/s import web3Utils from "web3-utils" import { hexToPaddedBuffer, buildTransactionForChain } from "./utils" +export const LEDGER_DERIVATION_PATHS = { + LEDGER_LIVE: `m/44'/60'/x'/0/0`, + LEDGER_LEGACY: `m/44'/60'/0'/x`, +} + /** * A custom Ledger subprovider, inheriting from the 0x Subprovider. * @@ -10,27 +15,64 @@ import { hexToPaddedBuffer, buildTransactionForChain } from "./utils" */ class LedgerSubprovider extends LedgerSubprovider0x { chainId + addressToPathMap = {} + pathToAddressMap = {} constructor(config) { super(config) this.chainId = config.chainId } + async getAccountsAsync(numberOfAccounts, accountsOffSet = 0) { + const addresses = [] + for ( + let index = accountsOffSet; + index < numberOfAccounts + accountsOffSet; + index++ + ) { + const address = await this.getAddress(index) + addresses.push(address) + } + + return addresses + } + + async getAddress(index) { + const path = this._baseDerivationPath.replace("x", index) + + let ledgerResponse + try { + this._ledgerClientIfExists = await this._createLedgerClientAsync() + ledgerResponse = await this._ledgerClientIfExists.getAddress( + path, + this._shouldAlwaysAskForConfirmation, + true + ) + } finally { + await this._destroyLedgerClientAsync() + } + + const address = web3Utils.toChecksumAddress(ledgerResponse.address) + + this.addressToPathMap[address] = path + this.pathToAddressMap[path] = address + + return address + } + async signTransactionAsync(txParams) { LedgerSubprovider._validateTxParams(txParams) if (txParams.from === undefined || !web3Utils.isAddress(txParams.from)) { throw new Error("Invalid address") } - const initialDerivedKeyInfo = await this._initialDerivedKeyInfoAsync() - const derivedKeyInfo = this._findDerivedKeyInfoForAddress( - initialDerivedKeyInfo, - txParams.from - ) + + const fullDerivationPath = this.addressToPathMap[ + web3Utils.toChecksumAddress(txParams.from) + ] this._ledgerClientIfExists = await this._createLedgerClientAsync() try { - const fullDerivationPath = derivedKeyInfo.derivationPath const tx = buildTransactionForChain(txParams, this.chainId) // Set the EIP155 bits From ec996747daf159f5e9cef7dda44a8037ca39c9cd Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 15 Sep 2020 12:45:36 +0200 Subject: [PATCH 2/3] Add accounts pagination for Ledger Fetching 5 accounts per page. --- src/components/lib/ConnectWalletDialog.js | 90 +++++++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/src/components/lib/ConnectWalletDialog.js b/src/components/lib/ConnectWalletDialog.js index 82efffa2..fe38ea07 100644 --- a/src/components/lib/ConnectWalletDialog.js +++ b/src/components/lib/ConnectWalletDialog.js @@ -1,8 +1,9 @@ -import React, { useState } from "react" +import React, { useState, useEffect } from "react" import PropTypes from "prop-types" import { useWeb3React } from "@web3-react/core" import { InjectedConnector } from "@web3-react/injected-connector" import { LedgerConnector } from "../../connectors/ledger" +import { LEDGER_DERIVATION_PATHS } from "../../connectors/ledger_subprovider" import { TrezorConnector } from "../../connectors/trezor" import { getChainId, getWsUrl } from "../../connectors/utils" @@ -15,13 +16,13 @@ const injectedConnector = new InjectedConnector({}) const ledgerLiveConnector = new LedgerConnector({ chainId: CHAIN_ID, url: ETH_RPC_URL, - baseDerivationPath: "44'/60'/0'/0", + baseDerivationPath: LEDGER_DERIVATION_PATHS.LEDGER_LIVE, }) const ledgerLegacyConnector = new LedgerConnector({ chainId: CHAIN_ID, url: ETH_RPC_URL, - baseDerivationPath: "44'/60'/0'", + baseDerivationPath: LEDGER_DERIVATION_PATHS.LEDGER_LEGACY, }) const trezorConnector = new TrezorConnector({ @@ -48,12 +49,14 @@ const WALLETS = [ icon: "/images/ledger.svg", connector: ledgerLegacyConnector, isHardwareWallet: true, + withAccountPagination: true, }, { name: "Ledger Live", icon: "/images/ledger.svg", connector: ledgerLiveConnector, isHardwareWallet: true, + withAccountPagination: true, }, { name: "Trezor", @@ -69,13 +72,47 @@ export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => { const [chosenWallet, setChosenWallet] = useState({}) const [error, setError] = useState(null) const [availableAccounts, setAvailableAccounts] = useState([]) + const [isFetching, setIsFetching] = useState(false) + const [accountsOffset, setAccountsOffset] = useState(0) + + useEffect(() => { + let shoudlSetState = true + + if ( + chosenWallet.isHardwareWallet && + chosenWallet.name && + chosenWallet.name.includes("Ledger") + ) { + setIsFetching(true) + chosenWallet.connector + .getAccounts(5, accountsOffset) + .then((accounts) => { + if (shoudlSetState) { + setAvailableAccounts(accounts) + setIsFetching(false) + } + }) + .catch((error) => { + if (shoudlSetState) { + setIsFetching(false) + setError(error.toString()) + } + }) + } + + return () => { + shoudlSetState = false + } + }, [accountsOffset, chosenWallet]) async function chooseWallet(wallet) { try { setChosenWallet(wallet) if (wallet.isHardwareWallet) { await wallet.connector.activate() - setAvailableAccounts(await wallet.connector.getAccounts()) + if (!wallet.withAccountPagination) { + setAvailableAccounts(await wallet.connector.getAccounts()) + } } else { await activateProvider(null, wallet) } @@ -127,6 +164,11 @@ export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => { availableAccounts={availableAccounts} active={active} onAccountSelect={activateProvider} + withPagination={chosenWallet && chosenWallet.withAccountPagination} + onNext={() => setAccountsOffset((prevOffset) => prevOffset + 5)} + onPrev={() => setAccountsOffset((prevOffset) => prevOffset - 5)} + shouldDisplayPrev={accountsOffset > 0} + isFetching={isFetching} /> @@ -210,8 +252,19 @@ const ChooseAccount = ({ availableAccounts, active, onAccountSelect, + withPagination, + onNext, + onPrev, + shouldDisplayPrev, + isFetching, }) => { - if (wallet.isHardwareWallet && availableAccounts.length !== 0 && !active) { + if (isFetching) { + return
Loading wallet accounts...
+ } else if ( + wallet.isHardwareWallet && + availableAccounts.length !== 0 && + !active + ) { return ( <>
Select account
@@ -224,6 +277,28 @@ const ChooseAccount = ({ {account} ))} + {withPagination && ( +
+ {shouldDisplayPrev && ( + + prev + + )} + + next + +
+ )} ) } @@ -236,6 +311,11 @@ ChooseAccount.propTypes = { availableAccounts: PropTypes.arrayOf(PropTypes.string), active: PropTypes.bool, onAccountSelect: PropTypes.func, + onNext: PropTypes.func, + onPrev: PropTypes.func, + shouldDisplayPrev: PropTypes.bool, + isFetching: PropTypes.bool, + withPagination: PropTypes.bool, } const ConnectedView = ({ wallet, account }) => { From 263c14bdb931ed7ad8890ecd0322e3954b36940b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 16 Sep 2020 08:41:30 +0200 Subject: [PATCH 3/3] Fix typo shoudl* -> should* --- src/components/lib/ConnectWalletDialog.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/lib/ConnectWalletDialog.js b/src/components/lib/ConnectWalletDialog.js index fe38ea07..c6754f8a 100644 --- a/src/components/lib/ConnectWalletDialog.js +++ b/src/components/lib/ConnectWalletDialog.js @@ -76,7 +76,7 @@ export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => { const [accountsOffset, setAccountsOffset] = useState(0) useEffect(() => { - let shoudlSetState = true + let shouldSetState = true if ( chosenWallet.isHardwareWallet && @@ -87,13 +87,13 @@ export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => { chosenWallet.connector .getAccounts(5, accountsOffset) .then((accounts) => { - if (shoudlSetState) { + if (shouldSetState) { setAvailableAccounts(accounts) setIsFetching(false) } }) .catch((error) => { - if (shoudlSetState) { + if (shouldSetState) { setIsFetching(false) setError(error.toString()) } @@ -101,7 +101,7 @@ export const ConnectWalletDialog = ({ shown, onConnected, onClose }) => { } return () => { - shoudlSetState = false + shouldSetState = false } }, [accountsOffset, chosenWallet])