/
LedgerProvider.js
139 lines (118 loc) · 3.9 KB
/
LedgerProvider.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import WalletProvider from '@liquality/wallet-provider'
import { WalletError } from '@liquality/errors'
import Debug from '@liquality/debug'
import getTransport from './LedgerNodeTransport'
import { version } from '../package.json'
const debug = Debug('ledger')
export default class LedgerProvider extends WalletProvider {
static getTransport (config = { useWebBle: false }) {
return getTransport(config)
}
static isSupported (config) {
return getTransport(config).isSupported()
}
constructor (App, baseDerivationPath, network, ledgerScrambleKey) {
super(network)
this._App = App
this._baseDerivationPath = baseDerivationPath
this._network = network
// The ledger scramble key is required to be set on the ledger transport
// if communicating with the device using `transport.send` for the first time
this._ledgerScrambleKey = ledgerScrambleKey
this._addressCache = {}
}
useWebBle () {
this._useWebBle = true
return this
}
async createTransport () {
if (!this._transport) {
debug('creating ledger transport')
debug('useWebBle', this._useWebBle)
const Transport = LedgerProvider.getTransport({
useWebBle: this._useWebBle
})
this._transport = await Transport.create()
debug('ledger transport created')
this._transport.on('disconnect', () => {
debug('ledger disconnected')
this._appInstance = null
this._transport = null
})
}
}
errorProxy (target, func) {
const method = target[func]
const ctx = this
if (Object.getOwnPropertyNames(target).includes(func) && typeof method === 'function') {
return async (...args) => {
debug(`calling "${func}" on ledger object`, args)
try {
const result = await method.bind(target)(...args)
debug(`result from "${func}" on ledger object`, result)
return result
} catch (e) {
const { name, ...errorNoName } = e
ctx._transport = null
ctx._appInstance = null
throw new WalletError(e.toString(), errorNoName)
}
}
} else {
return method
}
}
async getApp () {
try {
await this.createTransport()
} catch (e) {
const { name, ...errorNoName } = e
throw new WalletError(e.toString(), errorNoName)
}
if (!this._appInstance) {
this._appInstance = new Proxy(new this._App(this._transport), { get: this.errorProxy.bind(this) })
}
return this._appInstance
}
async isWalletAvailable () {
const app = await this.getApp()
if (!app.transport.scrambleKey) { // scramble key required before calls
app.transport.setScrambleKey(this._ledgerScrambleKey)
}
const exchangeTimeout = app.transport.exchangeTimeout
app.transport.setExchangeTimeout(2000)
try {
// https://ledgerhq.github.io/btchip-doc/bitcoin-technical-beta.html#_get_random
await this._transport.send(0xe0, 0xc0, 0x00, 0x00)
} catch (e) {
return false
} finally {
app.transport.setExchangeTimeout(exchangeTimeout)
}
return true
}
async getConnectedNetwork () {
// Ledger apps do not provide connected network. It is separated in firmware.
return this._network
}
async getWalletAddress (address) {
let index = 0
let change = false
// A maximum number of addresses to lookup after which it is deemed
// that the wallet does not contain this address
const maxAddresses = 1000
const addressesPerCall = 50
while (index < maxAddresses) {
const addrs = await this.getAddresses(index, addressesPerCall)
const addr = addrs.find(addr => addr.equals(address))
if (addr) return addr
index += addressesPerCall
if (index === maxAddresses && change === false) {
index = 0
change = true
}
}
throw new Error('Ledger: Wallet does not contain address')
}
}
LedgerProvider.version = version