Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ledger account search & employ #203

Merged
merged 3 commits into from
Sep 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions dapp/src/utils/LedgerConnector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { ConnectorUpdate } from '@web3-react/types'
import { AbstractConnector } from '@web3-react/abstract-connector'
import Web3ProviderEngine from 'web3-provider-engine'
import { ledgerEthereumBrowserClientFactoryAsync } from '@0x/subproviders/lib/src' // https://github.com/0xProject/0x-monorepo/issues/1400
import { LedgerSubprovider } from '@0x/subproviders/lib/src/subproviders/ledger' // https://github.com/0xProject/0x-monorepo/issues/1400
import CacheSubprovider from 'web3-provider-engine/subproviders/cache.js'
import { RPCSubprovider } from '@0x/subproviders/lib/src/subproviders/rpc_subprovider' // https://github.com/0xProject/0x-monorepo/issues/1400

const LEDGER_LIVE_BASE_PATH = "44'/60'/0'/0"
const LEDGER_CHROME_BASE_PATH = "44'/60'/0'"
const MEW_BASE_PATH = "44'/60'/0"

function notZero(v) {
return !['0x0', '0', 0, '0x00'].includes(v)
}

function getRPCProvider(providers) {
for (const provider of providers) {
if (typeof provider._rpcUrl !== 'undefined') {
return provider
}
}
return null
}

/**
* Adapted from @web3-react's LedgerConnector with ways to
* figure out which derivation path to use.
*
* Ref: https://github.com/NoahZinsmeister/web3-react/blob/v6/packages/ledger-connector/src/index.ts
* Ref: https://github.com/MyCryptoHQ/MyCrypto/issues/2070
*
* TODO: Add support for multiple accounts and not just the first? The path
* prefix is not the only difference, either. Live uses `m/44'/60'/x'/0/0`
* and I think LedgerSubprovider assumes the appended path is `x` (e.g.
* `m/44'/60'/0'/0/x`)
*/
export class LedgerConnector extends AbstractConnector {
constructor({
chainId,
url,
pollingInterval,
requestTimeoutMs,
accountFetchingConfigs,
baseDerivationPath,
}) {
super({ supportedChainIds: [chainId] })

this.chainId = chainId
this.url = url
this.pollingInterval = pollingInterval
this.requestTimeoutMs = requestTimeoutMs
this.accountFetchingConfigs = accountFetchingConfigs
this.engine = null
}

async activate() {
if (!this.provider) {
this.engine = new Web3ProviderEngine({
pollingInterval: this.pollingInterval,
})

// Figure out which provider we want
const provider = await this.deriveProvider({
networkId: this.chainId,
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
accountFetchingConfigs: this.accountFetchingConfigs,
})
this.engine.addProvider(provider)

this.engine.addProvider(new CacheSubprovider())
this.engine.addProvider(
new RPCSubprovider(this.url, this.requestTimeoutMs)
)
this.provider = this.engine
}

this.provider.start()

return { provider: this.provider, chainId: this.chainId }
}

createProvider(providerOpts) {
return new LedgerSubprovider(providerOpts)
}

async deriveProvider(providerOpts) {
const providers = {
// Derivation used by Ledger Live
liveProvider: this.createProvider({
...providerOpts,
baseDerivationPath: LEDGER_LIVE_BASE_PATH,
}),
// Derivation used by the old Ledger chrome app
legacyProvider: this.createProvider({
...providerOpts,
baseDerivationPath: LEDGER_CHROME_BASE_PATH,
}),
}

try {
for (const key of Object.keys(providers)) {
const subprovider = providers[key]
const account = await this._getFirstAccount(subprovider)
const accountBalance = await this._getBalance(account)
if (notZero(accountBalance)) {
return subprovider
}
}
} catch (err) {
// We'll fallback to default so noop here
console.warn(err)
}

console.debug(
'Falling back to default Ledger provider (no funded account found)'
)

// Default to the newer Ledger Live derivation path
return providers.liveProvider
}

async getProvider() {
return this.provider
}

async getChainId() {
return this.chainId
}

async _getBalance(account) {
const rpcSubprovider = getRPCProvider(this.engine._providers)
return new Promise((resolve, reject) => {
rpcSubprovider.handleRequest(
{
id: 123,
method: 'eth_getBalance',
params: [account],
},
null,
(err, resp) => {
if (err) return reject(err)
resolve(resp)
}
)
})
}

async _getFirstAccount(subprovider) {
const accounts = await subprovider.getAccountsAsync(1)
return !!accounts ? accounts[0] : null
}

async getAccount() {
return this._getFirstAccount(this.provider._providers[0])
}

deactivate() {
this.provider.stop()
}
}
2 changes: 1 addition & 1 deletion dapp/src/utils/connectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InjectedConnector } from '@web3-react/injected-connector'
import { LedgerConnector } from '@web3-react/ledger-connector'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { LedgerConnector } from './LedgerConnector'

const POLLING_INTERVAL = 12000
const RPC_URLS = {
Expand Down