Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Commit

Permalink
Merge pull request #359 from keep-network/ledger-live-path-fix
Browse files Browse the repository at this point in the history
Ledger Live derivation path pattern

This PR fixes the derivation path for Ledger Live accounts. The pattern for Ledger
Live should be m/44'/60'/x'/0/0 instead of m/44'/60'/0'/0/x. The default implementation
of LedgerSubprovider from 0x/subproviders package doesn't support this pattern.
  • Loading branch information
Shadowfiend authored Sep 16, 2020
2 parents 4a24b96 + ec78575 commit 76a3d1c
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 13 deletions.
90 changes: 85 additions & 5 deletions src/components/lib/ConnectWalletDialog.js
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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({
Expand All @@ -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",
Expand All @@ -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 shouldSetState = true

if (
chosenWallet.isHardwareWallet &&
chosenWallet.name &&
chosenWallet.name.includes("Ledger")
) {
setIsFetching(true)
chosenWallet.connector
.getAccounts(5, accountsOffset)
.then((accounts) => {
if (shouldSetState) {
setAvailableAccounts(accounts)
setIsFetching(false)
}
})
.catch((error) => {
if (shouldSetState) {
setIsFetching(false)
setError(error.toString())
}
})
}

return () => {
shouldSetState = 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)
}
Expand Down Expand Up @@ -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}
/>
</div>
</div>
Expand Down Expand Up @@ -210,8 +252,19 @@ const ChooseAccount = ({
availableAccounts,
active,
onAccountSelect,
withPagination,
onNext,
onPrev,
shouldDisplayPrev,
isFetching,
}) => {
if (wallet.isHardwareWallet && availableAccounts.length !== 0 && !active) {
if (isFetching) {
return <div>Loading wallet accounts...</div>
} else if (
wallet.isHardwareWallet &&
availableAccounts.length !== 0 &&
!active
) {
return (
<>
<div className="title mb-2">Select account</div>
Expand All @@ -224,6 +277,28 @@ const ChooseAccount = ({
{account}
</div>
))}
{withPagination && (
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
{shouldDisplayPrev && (
<span className="cursor-pointer" onClick={onPrev}>
prev
</span>
)}
<span
className="cursor-pointer"
style={{ marginLeft: "auto" }}
onClick={onNext}
>
next
</span>
</div>
)}
</>
)
}
Expand All @@ -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 }) => {
Expand Down
7 changes: 5 additions & 2 deletions src/connectors/ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
54 changes: 48 additions & 6 deletions src/connectors/ledger_subprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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
Expand Down

0 comments on commit 76a3d1c

Please sign in to comment.