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