diff --git a/package.json b/package.json
index 67328fc4..84f1a205 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"@ledgerhq/hw-app-xrp": "^4.13.0",
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
- "@ledgerhq/ledger-core": "1.9.0",
+ "@ledgerhq/ledger-core": "2.0.0-rc.1",
"@ledgerhq/live-common": "2.30.0",
"async": "^2.6.1",
"axios": "^0.18.0",
diff --git a/src/commands/libcoreGetFees.js b/src/commands/libcoreGetFees.js
index b08ec777..9aea3046 100644
--- a/src/commands/libcoreGetFees.js
+++ b/src/commands/libcoreGetFees.js
@@ -33,14 +33,20 @@ const cmd: Command = createCommand(
withLibcore(async core => {
const { walletName } = accountIdHelper.decode(accountId)
- const njsWallet = await core.getWallet(walletName)
+ const njsWallet = await core.getPoolInstance().getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(accountIndex)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
- const amount = core.createAmount(njsWalletCurrency, transaction.amount)
- const feesPerByte = core.createAmount(njsWalletCurrency, transaction.feePerByte)
+ const amount = new core.NJSAmount(njsWalletCurrency, transaction.amount).fromLong(
+ njsWalletCurrency,
+ transaction.amount,
+ )
+ const feesPerByte = new core.NJSAmount(njsWalletCurrency, transaction.feePerByte).fromLong(
+ njsWalletCurrency,
+ transaction.feePerByte,
+ )
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
if (!isValidAddress(core, njsWalletCurrency, transaction.recipient)) {
// FIXME this is a bug in libcore. later it will probably check this and we can remove this check
diff --git a/src/commands/libcoreHardReset.js b/src/commands/libcoreHardReset.js
index 7da0f3c6..a195d520 100644
--- a/src/commands/libcoreHardReset.js
+++ b/src/commands/libcoreHardReset.js
@@ -6,9 +6,9 @@ import withLibcore from 'helpers/withLibcore'
const cmd = createCommand('libcoreHardReset', () =>
Observable.create(o => {
- withLibcore(async (core, njsWalletPool) => {
+ withLibcore(async core => {
try {
- njsWalletPool.eraseDataSince(new Date(0))
+ core.getPoolInstance().eraseDataSince(new Date(0))
o.complete()
} catch (e) {
o.error(e)
diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js
index 489f3759..63740653 100644
--- a/src/commands/libcoreSignAndBroadcast.js
+++ b/src/commands/libcoreSignAndBroadcast.js
@@ -153,14 +153,20 @@ export async function doSignAndBroadcast({
onOperationBroadcasted: (optimisticOp: $Exact) => void,
}): Promise {
const { walletName } = accountIdHelper.decode(account.id)
- const njsWallet = await core.getWallet(walletName)
+ const njsWallet = await core.getPoolInstance().getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(account.index)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
- const amount = core.createAmount(njsWalletCurrency, transaction.amount)
- const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
+ const amount = new core.NJSAmount(njsWalletCurrency, transaction.amount).fromLong(
+ njsWalletCurrency,
+ transaction.amount,
+ )
+ const fees = new core.NJSAmount(njsWalletCurrency, transaction.feePerByte).fromLong(
+ njsWalletCurrency,
+ transaction.feePerByte,
+ )
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid
diff --git a/src/commands/libcoreSyncAccount.js b/src/commands/libcoreSyncAccount.js
index a6a38ec6..d2677b9e 100644
--- a/src/commands/libcoreSyncAccount.js
+++ b/src/commands/libcoreSyncAccount.js
@@ -14,9 +14,7 @@ type Input = {
type Result = AccountRaw
const cmd: Command = createCommand('libcoreSyncAccount', ({ rawAccount }) =>
- fromPromise(
- withLibcore((core, njsWalletPool) => syncAccount({ rawAccount, core, njsWalletPool })),
- ),
+ fromPromise(withLibcore(core => syncAccount({ rawAccount, core }))),
)
export default cmd
diff --git a/src/commands/libcoreValidAddress.js b/src/commands/libcoreValidAddress.js
index fc563d35..7693f092 100644
--- a/src/commands/libcoreValidAddress.js
+++ b/src/commands/libcoreValidAddress.js
@@ -15,7 +15,7 @@ const cmd: Command = createCommand(
({ currencyId, address }) =>
fromPromise(
withLibcore(async core => {
- const currency = await core.getCurrency(currencyId)
+ const currency = await core.getPoolInstance().getCurrency(currencyId)
return isValidAddress(core, currency, address)
}),
),
diff --git a/src/helpers/init-libcore.js b/src/helpers/init-libcore.js
new file mode 100644
index 00000000..f1df2ee0
--- /dev/null
+++ b/src/helpers/init-libcore.js
@@ -0,0 +1,193 @@
+// @flow
+
+import logger from 'logger'
+import invariant from 'invariant'
+import network from 'api/network'
+
+const lib = require('@ledgerhq/ledger-core')
+
+const crypto = require('crypto')
+const path = require('path')
+const fs = require('fs')
+
+const MAX_RANDOM = 2684869021
+
+const bytesArrayToString = (bytesArray = []) => Buffer.from(bytesArray).toString()
+
+const stringToBytesArray = str => Array.from(Buffer.from(str))
+
+const NJSExecutionContextImpl = {
+ execute: runnable => {
+ try {
+ runnable.run()
+ } catch (e) {
+ logger.log(e)
+ }
+ },
+ delay: (runnable, ms) => setTimeout(() => runnable.run(), ms),
+}
+
+const ThreadContexts = {}
+
+const getSerialExecutionContext = name => {
+ let currentContext = ThreadContexts[name]
+ if (!currentContext) {
+ currentContext = new lib.NJSExecutionContext(NJSExecutionContextImpl)
+ ThreadContexts[name] = currentContext
+ }
+ return currentContext
+}
+
+const NJSThreadDispatcher = new lib.NJSThreadDispatcher({
+ contexts: ThreadContexts,
+ getThreadPoolExecutionContext: name => getSerialExecutionContext(name),
+ getMainExecutionContext: () => getSerialExecutionContext('main'),
+ getSerialExecutionContext,
+ newLock: () => {
+ logger.warn('libcore NJSThreadDispatcher: newLock: Not implemented')
+ },
+})
+
+function createHttpConnection(res, err) {
+ if (!res) {
+ return null
+ }
+ const headersMap = new Map()
+ Object.keys(res.headers).forEach(key => {
+ if (typeof res.headers[key] === 'string') {
+ headersMap.set(key, res.headers[key])
+ }
+ })
+ const NJSHttpUrlConnectionImpl = {
+ getStatusCode: () => Number(res.status),
+ getStatusText: () => res.statusText,
+ getHeaders: () => headersMap,
+ readBody: () => ({
+ error: err ? { code: 0, message: 'something went wrong' } : null,
+ data: stringToBytesArray(JSON.stringify(res.data)),
+ }),
+ }
+ return new lib.NJSHttpUrlConnection(NJSHttpUrlConnectionImpl)
+}
+
+const NJSHttpClient = new lib.NJSHttpClient({
+ execute: async r => {
+ const method = r.getMethod()
+ const headersMap = r.getHeaders()
+ let data = r.getBody()
+ if (Array.isArray(data)) {
+ const dataStr = bytesArrayToString(data)
+ try {
+ data = JSON.parse(dataStr)
+ } catch (e) {
+ // not a json !?
+ }
+ }
+ const url = r.getUrl()
+ const headers = {}
+ headersMap.forEach((v, k) => {
+ headers[k] = v
+ })
+ let res
+ try {
+ // $FlowFixMe
+ res = await network({ method: lib.METHODS[method], url, headers, data })
+ const urlConnection = createHttpConnection(res)
+ r.complete(urlConnection, null)
+ } catch (err) {
+ const urlConnection = createHttpConnection(res, err.message)
+ r.complete(urlConnection, { code: 0, message: err.message })
+ }
+ },
+})
+
+const NJSWebSocketClient = new lib.NJSWebSocketClient({
+ connect: (url, connection) => {
+ connection.OnConnect()
+ },
+ send: (connection, data) => {
+ connection.OnMessage(data)
+ },
+ disconnect: connection => {
+ connection.OnClose()
+ },
+})
+
+const NJSLogPrinter = new lib.NJSLogPrinter({
+ context: {},
+ printError: message => logger.libcore('Error', message),
+ printInfo: message => logger.libcore('Info', message),
+ printDebug: message => logger.libcore('Debug', message),
+ printWarning: message => logger.libcore('Warning', message),
+ printApdu: message => logger.libcore('Apdu', message),
+ printCriticalError: message => logger.libcore('CriticalError', message),
+ getContext: () => NJSThreadDispatcher.getMainExecutionContext(),
+})
+
+const NJSRandomNumberGenerator = new lib.NJSRandomNumberGenerator({
+ getRandomBytes: size => crypto.randomBytes(size),
+ getRandomInt: () => Math.random() * MAX_RANDOM,
+ getRandomLong: () => Math.random() * MAX_RANDOM * MAX_RANDOM,
+})
+
+const NJSDatabaseBackend = new lib.NJSDatabaseBackend()
+const NJSDynamicObject = new lib.NJSDynamicObject()
+
+let walletPoolInstance = null
+
+const instanciateWalletPool = ({ dbPath }) => {
+ try {
+ fs.mkdirSync(dbPath)
+ } catch (err) {
+ if (err.code !== 'EEXIST') {
+ throw err
+ }
+ }
+
+ const NJSPathResolver = new lib.NJSPathResolver({
+ resolveLogFilePath: pathToResolve => {
+ const hash = pathToResolve.replace(/\//g, '__')
+ return path.resolve(dbPath, `./log_file_${hash}`)
+ },
+ resolvePreferencesPath: pathToResolve => {
+ const hash = pathToResolve.replace(/\//g, '__')
+ return path.resolve(dbPath, `./preferences_${hash}`)
+ },
+ resolveDatabasePath: pathToResolve => {
+ const hash = pathToResolve.replace(/\//g, '__')
+ return path.resolve(dbPath, `./database_${hash}`)
+ },
+ })
+
+ walletPoolInstance = new lib.NJSWalletPool(
+ 'ledger_live_desktop',
+ '',
+ NJSHttpClient,
+ NJSWebSocketClient,
+ NJSPathResolver,
+ NJSLogPrinter,
+ NJSThreadDispatcher,
+ NJSRandomNumberGenerator,
+ NJSDatabaseBackend,
+ NJSDynamicObject,
+ )
+
+ return walletPoolInstance
+}
+
+const getPoolInstance = () => {
+ if (!walletPoolInstance) {
+ instanciateWalletPool({
+ // sqlite files will be located in the app local data folder
+ dbPath: process.env.LEDGER_LIVE_SQLITE_PATH,
+ })
+ }
+ invariant(walletPoolInstance, "can't initialize walletPoolInstance")
+ return walletPoolInstance
+}
+
+export default {
+ ...lib,
+ getSerialExecutionContext,
+ getPoolInstance,
+}
diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js
index 5607fda8..b04049c3 100644
--- a/src/helpers/libcore.js
+++ b/src/helpers/libcore.js
@@ -123,6 +123,58 @@ async function scanAccountsOnDeviceBySegwit({
return accounts
}
+const hexToBytes = str => Array.from(Buffer.from(str, 'hex'))
+
+const createAccount = async (wallet, hwApp) => {
+ const accountCreationInfos = await wallet.getNextAccountCreationInfo()
+ await accountCreationInfos.derivations.reduce(
+ (promise, derivation) =>
+ promise.then(async () => {
+ const { publicKey, chainCode } = await hwApp.getWalletPublicKey(derivation)
+ accountCreationInfos.publicKeys.push(hexToBytes(publicKey))
+ accountCreationInfos.chainCodes.push(hexToBytes(chainCode))
+ }),
+ Promise.resolve(),
+ )
+ return wallet.newAccountWithInfo(accountCreationInfos)
+}
+
+function createEventReceiver(core, cb) {
+ return new core.NJSEventReceiver({
+ onEvent: event => cb(event),
+ })
+}
+
+function subscribeToEventBus(core, eventBus, receiver) {
+ eventBus.subscribe(core.getSerialExecutionContext('main'), receiver)
+}
+
+const coreSyncAccount = (core, account) =>
+ new Promise((resolve, reject) => {
+ const eventReceiver = createEventReceiver(core, e => {
+ const code = e.getCode()
+ if (code === core.EVENT_CODE.UNDEFINED || code === core.EVENT_CODE.SYNCHRONIZATION_FAILED) {
+ const payload = e.getPayload()
+ const message = (
+ (payload && payload.getString('EV_SYNC_ERROR_MESSAGE')) ||
+ 'Sync failed'
+ ).replace(' (EC_PRIV_KEY_INVALID_FORMAT)', '')
+ reject(new Error(message))
+ return
+ }
+ if (
+ code === core.EVENT_CODE.SYNCHRONIZATION_SUCCEED ||
+ code === core.EVENT_CODE.SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT
+ ) {
+ resolve(() => {
+ eventBus.unsubscribe(eventReceiver)
+ })
+ }
+ })
+ const eventBus = account.synchronize()
+ subscribeToEventBus(core, eventBus, eventReceiver)
+ })
+
async function scanNextAccount(props: {
// $FlowFixMe
wallet: NJSWallet,
@@ -155,11 +207,11 @@ async function scanNextAccount(props: {
const njsAccount = hasBeenScanned
? await wallet.getAccount(accountIndex)
- : await core.createAccount(wallet, hwApp)
+ : await createAccount(wallet, hwApp)
const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize.
if (shouldSyncAccount) {
- await core.syncAccount(njsAccount)
+ await coreSyncAccount(core, njsAccount)
}
const query = njsAccount.queryOperations()
@@ -190,25 +242,38 @@ async function scanNextAccount(props: {
return scanNextAccount({ ...props, accountIndex: accountIndex + 1 })
}
+const createWalletConfig = (core, configMap = {}) => {
+ const config = new core.NJSDynamicObject()
+ for (const i in configMap) {
+ if (configMap.hasOwnProperty(i)) {
+ config.putString(i, configMap[i])
+ }
+ }
+ return config
+}
+
async function getOrCreateWallet(
core: *,
WALLET_IDENTIFIER: string,
currencyId: string,
isSegwit: boolean,
): NJSWallet {
+ const pool = core.getPoolInstance()
try {
- const wallet = await core.getWallet(WALLET_IDENTIFIER)
+ const wallet = await pool.getWallet(WALLET_IDENTIFIER)
return wallet
} catch (err) {
- const currency = await core.getCurrency(currencyId)
+ const currency = await pool.getCurrency(currencyId)
const walletConfig = isSegwit
? {
KEYCHAIN_ENGINE: 'BIP49_P2SH',
KEYCHAIN_DERIVATION_SCHEME: "49'/'/'//",
}
: undefined
- const njsWalletConfig = core.createWalletConfig(walletConfig)
- const wallet = await core.createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig)
+ const njsWalletConfig = createWalletConfig(core, walletConfig)
+ const wallet = await core
+ .getPoolInstance()
+ .createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig)
return wallet
}
}
@@ -342,33 +407,12 @@ function buildOperationRaw({
}
}
-export async function getNJSAccount({
- accountRaw,
- njsWalletPool,
-}: {
- accountRaw: AccountRaw,
- njsWalletPool: *,
-}) {
- const decodedAccountId = accountIdHelper.decode(accountRaw.id)
- const njsWallet = await njsWalletPool.getWallet(decodedAccountId.walletName)
- const njsAccount = await njsWallet.getAccount(accountRaw.index)
- return njsAccount
-}
-
-export async function syncAccount({
- rawAccount,
- core,
- njsWalletPool,
-}: {
- core: *,
- rawAccount: AccountRaw,
- njsWalletPool: *,
-}) {
+export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: AccountRaw }) {
const decodedAccountId = accountIdHelper.decode(rawAccount.id)
- const njsWallet = await njsWalletPool.getWallet(decodedAccountId.walletName)
+ const njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName)
const njsAccount = await njsWallet.getAccount(rawAccount.index)
- const unsub = await core.syncAccount(njsAccount)
+ const unsub = await coreSyncAccount(core, njsAccount)
unsub()
const query = njsAccount.queryOperations()
diff --git a/src/helpers/withLibcore.js b/src/helpers/withLibcore.js
index 9a8ea130..e2d42054 100644
--- a/src/helpers/withLibcore.js
+++ b/src/helpers/withLibcore.js
@@ -1,26 +1,10 @@
// @flow
-import invariant from 'invariant'
-import network from 'api/network'
-
-const core = require('@ledgerhq/ledger-core')
-
-core.setHttpQueryImplementation(network)
-
-let walletPoolInstance: ?Object = null
-
-// TODO: `core` and `NJSWalletPool` should be typed
-type Job = (Object, Object) => Promise
+// TODO: `core` should be typed
+type Job = Object => Promise
export default function withLibcore(job: Job): Promise {
- if (!walletPoolInstance) {
- walletPoolInstance = core.instanciateWalletPool({
- // sqlite files will be located in the app local data folder
- dbPath: process.env.LEDGER_LIVE_SQLITE_PATH,
- })
- }
- const walletPool = walletPoolInstance
- invariant(walletPool, 'core.instanciateWalletPool returned null !!')
-
- return job(core, walletPool)
+ const core = require('./init-libcore').default
+ core.getPoolInstance()
+ return job(core)
}
diff --git a/src/logger.js b/src/logger.js
index c48d8f40..e9f47e1a 100644
--- a/src/logger.js
+++ b/src/logger.js
@@ -51,6 +51,7 @@ const logCmds = !__DEV__ || process.env.DEBUG_COMMANDS
const logDb = !__DEV__ || process.env.DEBUG_DB
const logRedux = !__DEV__ || process.env.DEBUG_ACTION
const logTabkey = !__DEV__ || process.env.DEBUG_TAB_KEY
+const logLibcore = !__DEV__ || process.env.DEBUG_LIBCORE
export default {
onCmd: (type: string, id: string, spentTime: number, data?: any) => {
@@ -103,6 +104,13 @@ export default {
addLog('keydown', msg)
},
+ libcore: (level: string, msg: string) => {
+ if (logLibcore) {
+ console.log(`🛠${level}: ${msg}`)
+ }
+ addLog('action', `🛠${level}: ${msg}`)
+ },
+
// General functions in case the hooks don't apply
log: (...args: any) => {
diff --git a/yarn.lock b/yarn.lock
index 98e3bbef..93960899 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1502,9 +1502,9 @@
dependencies:
events "^2.0.0"
-"@ledgerhq/ledger-core@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.9.0.tgz#3240857e76f3c2b17ba02d120b96fd1b13b50789"
+"@ledgerhq/ledger-core@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.1.tgz#0b31f7d2c693b9c11d4093dbb0896f13c33bf141"
dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6"