Meriadec Pillet
7 years ago
committed by
GitHub
21 changed files with 484 additions and 173 deletions
@ -0,0 +1,131 @@ |
|||
require('babel-polyfill') |
|||
require('babel-register') |
|||
|
|||
const chalk = require('chalk') |
|||
const inquirer = require('inquirer') |
|||
const path = require('path') |
|||
const TransportNodeHid = require('@ledgerhq/hw-transport-node-hid').default |
|||
|
|||
const { serializeAccounts, encodeAccount, decodeAccount } = require('../src/reducers/accounts') |
|||
const { doSignAndBroadcast } = require('../src/commands/libcoreSignAndBroadcast') |
|||
|
|||
const coreHelper = require('../src/helpers/libcore') |
|||
const withLibcore = require('../src/helpers/withLibcore').default |
|||
|
|||
if (!process.env.LEDGER_LIVE_SQLITE_PATH) { |
|||
throw new Error('you must define process.env.LEDGER_LIVE_SQLITE_PATH first') |
|||
} |
|||
|
|||
const LOCAL_DIRECTORY_PATH = path.resolve(process.env.LEDGER_LIVE_SQLITE_PATH, '../') |
|||
|
|||
gimmeDeviceAndLibCore(async ({ device, core, njsWalletPool }) => { |
|||
const raw = require(path.join(LOCAL_DIRECTORY_PATH, 'accounts.json')) // eslint-disable-line import/no-dynamic-require
|
|||
const accounts = serializeAccounts(raw.data) |
|||
const accountToUse = await chooseAccount('Which account to use?', accounts) |
|||
await actionLoop({ account: accountToUse, accounts, core, njsWalletPool, device }) |
|||
process.exit(0) |
|||
}) |
|||
|
|||
async function actionLoop(props) { |
|||
try { |
|||
const { account, accounts, core, njsWalletPool, device } = props |
|||
const actionToDo = await chooseAction(`What do you want to do with [${account.name}] ?`) |
|||
if (actionToDo === 'send funds') { |
|||
const transport = await TransportNodeHid.open(device.path) |
|||
const accountToReceive = await chooseAccount('To which account?', accounts) |
|||
const receiveAddress = await getFreshAddress({ |
|||
account: accountToReceive, |
|||
core, |
|||
njsWalletPool, |
|||
}) |
|||
console.log(`the receive address is ${receiveAddress}`) |
|||
const rawAccount = encodeAccount(account) |
|||
console.log(`trying to sign and broadcast...`) |
|||
const rawOp = await doSignAndBroadcast({ |
|||
account: rawAccount, |
|||
transaction: { |
|||
amount: 4200000, |
|||
recipient: receiveAddress, |
|||
feePerByte: 16, |
|||
isRBF: false, |
|||
}, |
|||
deviceId: device.path, |
|||
core, |
|||
transport, |
|||
}) |
|||
console.log(rawOp) |
|||
} else if (actionToDo === 'sync') { |
|||
console.log(`\nLaunch sync...\n`) |
|||
const rawAccount = encodeAccount(account) |
|||
const syncedAccount = await coreHelper.syncAccount({ rawAccount, core, njsWalletPool }) |
|||
console.log(`\nEnd sync...\n`) |
|||
console.log(`updated account: `, displayAccount(syncedAccount, 'red')) |
|||
} else if (actionToDo === 'quit') { |
|||
return true |
|||
} |
|||
} catch (err) { |
|||
console.log(`x Something went wrong`) |
|||
console.log(err) |
|||
process.exit(1) |
|||
} |
|||
return actionLoop(props) |
|||
} |
|||
|
|||
async function chooseInList(msg, list, formatItem = i => i) { |
|||
const choices = list.map(formatItem) |
|||
const { choice } = await inquirer.prompt([ |
|||
{ |
|||
type: 'list', |
|||
name: 'choice', |
|||
message: msg, |
|||
choices, |
|||
}, |
|||
]) |
|||
const index = choices.indexOf(choice) |
|||
return list[index] |
|||
} |
|||
|
|||
async function chooseAction(msg) { |
|||
return chooseInList(msg, ['sync', 'send funds', 'quit']) |
|||
} |
|||
|
|||
function chooseAccount(msg, accounts) { |
|||
return chooseInList(msg, accounts, acc => displayAccount(acc)) |
|||
} |
|||
|
|||
async function gimmeDeviceAndLibCore(cb) { |
|||
withLibcore((core, njsWalletPool) => { |
|||
TransportNodeHid.listen({ |
|||
error: () => {}, |
|||
complete: () => {}, |
|||
next: async e => { |
|||
if (!e.device) { |
|||
return |
|||
} |
|||
if (e.type === 'add') { |
|||
const { device } = e |
|||
cb({ device, core, njsWalletPool }) |
|||
} |
|||
}, |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
function displayAccount(acc, color = null) { |
|||
const isRawAccount = typeof acc.lastSyncDate === 'string' |
|||
if (isRawAccount) { |
|||
acc = decodeAccount(acc) |
|||
} |
|||
const str = `[${acc.name}] ${acc.isSegwit ? '' : '(legacy) '}${acc.unit.code} ${acc.balance} - ${ |
|||
acc.operations.length |
|||
} txs` |
|||
return color ? chalk[color](str) : str |
|||
} |
|||
|
|||
async function getFreshAddress({ account, core, njsWalletPool }) { |
|||
const njsAccount = await coreHelper.getNJSAccount({ account, njsWalletPool }) |
|||
const unsub = await core.syncAccount(njsAccount) |
|||
unsub() |
|||
const rawAddresses = await njsAccount.getFreshPublicAddresses() |
|||
return rawAddresses[0] |
|||
} |
@ -0,0 +1,22 @@ |
|||
// @flow
|
|||
|
|||
import type { AccountRaw } from '@ledgerhq/live-common/lib/types' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { syncAccount } from 'helpers/libcore' |
|||
import withLibcore from 'helpers/withLibcore' |
|||
|
|||
type Input = { |
|||
rawAccount: AccountRaw, |
|||
} |
|||
|
|||
type Result = AccountRaw |
|||
|
|||
const cmd: Command<Input, Result> = createCommand('libcoreSyncAccount', ({ rawAccount }) => |
|||
fromPromise( |
|||
withLibcore((core, njsWalletPool) => syncAccount({ rawAccount, core, njsWalletPool })), |
|||
), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,22 @@ |
|||
// @flow
|
|||
|
|||
import invariant from 'invariant' |
|||
|
|||
type Params = { |
|||
type: string, |
|||
version: string, |
|||
xpub: string, |
|||
walletName: string, |
|||
} |
|||
|
|||
export function encode({ type, version, xpub, walletName }: Params) { |
|||
return `${type}:${version}:${xpub}:${walletName}` |
|||
} |
|||
|
|||
export function decode(accountId: string): Params { |
|||
invariant(typeof accountId === 'string', 'accountId is not a string') |
|||
const splitted = accountId.split(':') |
|||
invariant(splitted.length === 4, 'invalid size for accountId') |
|||
const [type, version, xpub, walletName] = splitted |
|||
return { type, version, xpub, walletName } |
|||
} |
@ -0,0 +1,30 @@ |
|||
// @flow
|
|||
|
|||
import invariant from 'invariant' |
|||
|
|||
const core = require('@ledgerhq/ledger-core') |
|||
|
|||
let walletPoolInstance: ?Object = null |
|||
let queue = Promise.resolve() |
|||
|
|||
// TODO: `core` and `NJSWalletPool` should be typed
|
|||
type Job<A> = (Object, Object) => Promise<A> |
|||
|
|||
export default function withLibcore<A>(job: Job<A>): Promise<A> { |
|||
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 !!') |
|||
|
|||
const p = queue.then(() => job(core, walletPool)) |
|||
|
|||
queue = p.catch(e => { |
|||
console.warn(`withLibCore: Error in job`, e) |
|||
}) |
|||
|
|||
return p |
|||
} |
@ -1,20 +0,0 @@ |
|||
// Yep. That's a singleton.
|
|||
//
|
|||
// Electron needs to tell lib ledger core where to store the sqlite files, when
|
|||
// instanciating wallet pool, but we don't need to do each everytime we
|
|||
// require ledger-core, only the first time, so, eh.
|
|||
|
|||
const core = require('@ledgerhq/ledger-core') |
|||
|
|||
let instanciated = false |
|||
|
|||
module.exports = () => { |
|||
if (!instanciated) { |
|||
core.instanciateWalletPool({ |
|||
// sqlite files will be located in the app local data folder
|
|||
dbPath: process.env.LEDGER_LIVE_SQLITE_PATH, |
|||
}) |
|||
instanciated = true |
|||
} |
|||
return core |
|||
} |
Loading…
Reference in new issue