Skip to content

Commit 0f1e328

Browse files
committed
Update ledger module to new format
1 parent 252163e commit 0f1e328

File tree

1 file changed

+136
-64
lines changed

1 file changed

+136
-64
lines changed

src/modules/select/wallets/ledger.ts

Lines changed: 136 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import {
77
import ledgerIcon from '../wallet-icons/icon-ledger'
88

99
import createProvider from './providerEngine'
10+
import { generateAddresses, isValidPath } from './hd-wallet'
1011

1112
import TransportU2F from '@ledgerhq/hw-transport-u2f'
1213
import Eth from '@ledgerhq/hw-app-eth'
1314
import * as EthereumTx from 'ethereumjs-tx'
1415

1516
import buffer from 'buffer'
17+
import { getAddress } from '../../../utilities'
18+
19+
const LEDGER_LIVE_PATH = `m/44'/60'`
20+
const ACCOUNTS_TO_GET = 5
1621

1722
function ledger(options: LedgerOptions & CommonWalletOptions): WalletModule {
1823
const { rpcUrl, networkId, preferred, label, iconSrc, svg } = options
@@ -36,7 +41,7 @@ function ledger(options: LedgerOptions & CommonWalletOptions): WalletModule {
3641
interface: {
3742
name: 'Ledger',
3843
connect: provider.enable,
39-
disconnect: () => provider.stop(),
44+
disconnect: provider.disconnect,
4045
address: {
4146
get: async () => provider.getPrimaryAddress()
4247
},
@@ -65,10 +70,15 @@ async function ledgerProvider(options: {
6570
networkName: (id: number) => string
6671
}) {
6772
const { networkId, rpcUrl, BigNumber, networkName } = options
68-
const basePath = networkIdToDerivationPath(networkId)
6973

74+
let dPath = ''
7075
let addressToPath = new Map()
7176
let enabled: boolean = false
77+
let customPath = false
78+
79+
let account:
80+
| undefined
81+
| { publicKey: string; chainCode: string; path: string }
7282

7383
const provider = createProvider({
7484
getAccounts: (callback: any) => {
@@ -84,37 +94,66 @@ async function ledgerProvider(options: {
8494
rpcUrl
8595
})
8696

87-
provider.getPrimaryAddress = getPrimaryAddress
88-
provider.getAllAccountsAndBalances = getAllAccountsAndBalances
97+
provider.setPath = setPath
98+
provider.dPath = dPath
8999
provider.enable = enable
90100
provider.setPrimaryAccount = setPrimaryAccount
101+
provider.getPrimaryAddress = getPrimaryAddress
102+
provider.getAccounts = getAccounts
103+
provider.getMoreAccounts = getMoreAccounts
91104
provider.getBalance = getBalance
105+
provider.getBalances = getBalances
92106
provider.send = provider.sendAsync
107+
provider.disconnect = disconnect
108+
provider.isCustomPath = isCustomPath
93109

94-
function enable() {
95-
enabled = true
96-
return getAccounts(1)
110+
let transport
111+
let eth: any
112+
113+
try {
114+
transport = await TransportU2F.create()
115+
eth = new Eth(transport)
116+
} catch (error) {
117+
throw new Error('Error connecting to Ledger wallet')
97118
}
98119

99-
function addresses() {
100-
return Array.from(addressToPath.keys())
120+
function disconnect() {
121+
console.log('resetting')
122+
dPath = ''
123+
addressToPath = new Map()
124+
enabled = false
125+
provider.stop()
101126
}
102127

103-
function getPrimaryAddress() {
104-
return enabled ? addresses()[0] : undefined
128+
async function setPath(path: string, custom?: boolean) {
129+
if (custom) {
130+
if (!isValidPath(path)) {
131+
return false
132+
}
133+
134+
const address = await getAddress(path)
135+
addressToPath.set(address, path)
136+
customPath = true
137+
return true
138+
}
139+
140+
customPath = false
141+
dPath = path
142+
143+
return true
105144
}
106145

107-
async function getAllAccountsAndBalances(amountToGet: number = 5) {
108-
const accounts = await getAccounts(amountToGet, true)
109-
return Promise.all(
110-
accounts.map(
111-
(address: string) =>
112-
new Promise(async resolve => {
113-
const balance = await getBalance(address)
114-
resolve({ address, balance })
115-
})
116-
)
117-
)
146+
function isCustomPath() {
147+
return customPath
148+
}
149+
150+
function enable() {
151+
enabled = true
152+
return getAccounts()
153+
}
154+
155+
function addresses() {
156+
return Array.from(addressToPath.keys())
118157
}
119158

120159
function setPrimaryAccount(address: string) {
@@ -129,65 +168,98 @@ async function ledgerProvider(options: {
129168
addressToPath = new Map(accounts)
130169
}
131170

132-
function getAccounts(
133-
numberToGet: number = 1,
134-
getMore?: boolean
135-
): Promise<any[]> {
136-
return new Promise(async (resolve, reject) => {
137-
if (!enabled) {
138-
resolve([null])
139-
}
171+
async function getAddress(path: string) {
172+
try {
173+
const result = await eth.getAddress(path)
174+
return result.address
175+
} catch (error) {}
176+
}
140177

141-
const addressesAlreadyFetched = addressToPath.size
178+
async function getPublicKey(eth: any) {
179+
if (!dPath) {
180+
throw new Error('a derivation path is needed to get the public key')
181+
}
182+
183+
try {
184+
const result = await eth.getAddress(dPath, false, true)
185+
const { publicKey, chainCode } = result
142186

143-
if (addressesAlreadyFetched > 0 && !getMore) {
144-
return resolve(addresses())
187+
account = {
188+
publicKey,
189+
chainCode,
190+
path: dPath
145191
}
146192

147-
const paths = []
193+
return account
194+
} catch (error) {
195+
throw new Error(
196+
'There was a problem getting access to your Ledger accounts.'
197+
)
198+
}
199+
}
148200

149-
if (numberToGet > 1) {
150-
for (
151-
let i = addressesAlreadyFetched;
152-
i < numberToGet + addressesAlreadyFetched;
153-
i++
154-
) {
155-
const ledgerLive = `${basePath}/${i}'/0/0`
156-
const legacy = `${basePath}/0'/${i}'`
201+
function getPrimaryAddress() {
202+
return enabled ? addresses()[0] : undefined
203+
}
157204

158-
paths.push(ledgerLive, legacy)
159-
}
160-
} else {
161-
paths.push(`${basePath}/0'/0`)
162-
}
205+
async function getMoreAccounts() {
206+
const accounts = await getAccounts(true)
207+
return accounts && getBalances(accounts)
208+
}
209+
210+
async function getAccounts(getMore?: boolean) {
211+
if (!enabled) {
212+
return [undefined]
213+
}
163214

164-
let transport
165-
let eth
215+
if (addressToPath.size > 0 && !getMore) {
216+
return addresses()
217+
}
166218

167-
try {
168-
transport = await TransportU2F.create()
169-
eth = new Eth(transport)
170-
} catch (error) {
171-
reject({ message: 'Error connecting to Ledger wallet' })
219+
if (dPath === LEDGER_LIVE_PATH) {
220+
const currentAccounts = addressToPath.size
221+
const paths = []
222+
for (
223+
let i = currentAccounts;
224+
i < ACCOUNTS_TO_GET + currentAccounts;
225+
i++
226+
) {
227+
paths.push(`${LEDGER_LIVE_PATH}/${i}'/0/0`)
172228
}
173229

174230
for (const path of paths) {
175231
try {
176-
const { address } = await eth.getAddress(path)
177-
addressToPath.set(address.toLowerCase(), path)
178-
} catch (err) {
179-
return reject({
180-
message: 'There was a problem trying to connect to your Ledger.'
181-
})
232+
const res = await eth.getAddress(path)
233+
addressToPath.set(res.address, path)
234+
} catch (error) {
235+
console.log({ error })
182236
}
183237
}
238+
} else {
239+
const accountInfo = account || (await getPublicKey(eth))
184240

185-
const allAddresses = addresses()
241+
if (!accountInfo) return [undefined]
186242

187-
transport.close()
243+
const addressInfo = generateAddresses(accountInfo, addressToPath.size)
188244

189-
resolve(allAddresses)
190-
})
245+
addressInfo.forEach(({ dPath, address }) => {
246+
addressToPath.set(address, dPath)
247+
})
248+
}
249+
250+
return addresses()
251+
}
252+
253+
function getBalances(addresses: Array<string>) {
254+
return Promise.all(
255+
addresses.map(
256+
address =>
257+
new Promise(async resolve => {
258+
const balance = await getBalance(address)
259+
resolve({ address, balance })
260+
})
261+
)
262+
)
191263
}
192264

193265
function getBalance(address: string) {

0 commit comments

Comments
 (0)