committed by
GitHub
35 changed files with 443 additions and 409 deletions
@ -0,0 +1,44 @@ |
|||
// @flow
|
|||
|
|||
import type { AccountRaw } from '@ledgerhq/live-common/lib/types' |
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { Observable } from 'rxjs' |
|||
import { scanAccountsOnDevice } from 'helpers/libcore' |
|||
|
|||
type Input = { |
|||
devicePath: string, |
|||
currencyId: string, |
|||
} |
|||
|
|||
type Result = AccountRaw |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'devices', |
|||
'libcoreScanAccounts', |
|||
({ devicePath, currencyId }) => |
|||
Observable.create(o => { |
|||
// TODO scanAccountsOnDevice should directly return a Observable so we just have to pass-in
|
|||
scanAccountsOnDevice({ |
|||
devicePath, |
|||
currencyId, |
|||
onAccountScanned: account => { |
|||
o.next(account) |
|||
}, |
|||
}).then( |
|||
() => { |
|||
o.complete() |
|||
}, |
|||
e => { |
|||
o.error(e) |
|||
}, |
|||
) |
|||
|
|||
function unsubscribe() { |
|||
// FIXME not implemented
|
|||
} |
|||
|
|||
return unsubscribe |
|||
}), |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,94 @@ |
|||
// @flow
|
|||
|
|||
import type { AccountRaw, OperationRaw } from '@ledgerhq/live-common/lib/types' |
|||
import Btc from '@ledgerhq/hw-app-btc' |
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import { getWalletIdentifier } from 'helpers/libcore' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' |
|||
|
|||
type BitcoinLikeTransaction = { |
|||
amount: number, |
|||
feePerByte: number, |
|||
recipient: string, |
|||
} |
|||
|
|||
type Input = { |
|||
account: AccountRaw, |
|||
transaction: BitcoinLikeTransaction, |
|||
deviceId: string, |
|||
} |
|||
|
|||
type Result = $Exact<OperationRaw> |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'devices', |
|||
'libcoreSignAndBroadcast', |
|||
({ account, transaction, deviceId }) => { |
|||
// TODO: investigate why importing it on file scope causes trouble
|
|||
const core = require('init-ledger-core')() |
|||
|
|||
return fromPromise( |
|||
withDevice(deviceId)(async transport => { |
|||
const hwApp = new Btc(transport) |
|||
|
|||
const WALLET_IDENTIFIER = await getWalletIdentifier({ |
|||
hwApp, |
|||
isSegwit: !!account.isSegwit, |
|||
currencyId: account.currencyId, |
|||
devicePath: deviceId, |
|||
}) |
|||
|
|||
const njsWallet = await core.getWallet(WALLET_IDENTIFIER) |
|||
const njsAccount = await njsWallet.getAccount(account.index) |
|||
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount() |
|||
const njsWalletCurrency = njsWallet.getCurrency() |
|||
const amount = core.createAmount(njsWalletCurrency, transaction.amount) |
|||
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte) |
|||
const transactionBuilder = bitcoinLikeAccount.buildTransaction() |
|||
|
|||
// TODO: check if is valid address. if not, it will fail silently on invalid
|
|||
|
|||
transactionBuilder.sendToAddress(amount, transaction.recipient) |
|||
// TODO: don't use hardcoded value for sequence (and first also maybe)
|
|||
transactionBuilder.pickInputs(0, 0xffffff) |
|||
transactionBuilder.setFeesPerByte(fees) |
|||
|
|||
const builded = await transactionBuilder.build() |
|||
const sigHashType = core.helpers.bytesToHex( |
|||
njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash, |
|||
) |
|||
|
|||
const currency = getCryptoCurrencyById(account.currencyId) |
|||
const signedTransaction = await core.signTransaction({ |
|||
hwApp, |
|||
transaction: builded, |
|||
sigHashType, |
|||
supportsSegwit: currency.supportsSegwit, |
|||
isSegwit: account.isSegwit, |
|||
}) |
|||
|
|||
const txHash = await njsAccount |
|||
.asBitcoinLikeAccount() |
|||
.broadcastRawTransaction(signedTransaction) |
|||
|
|||
// optimistic operation
|
|||
return { |
|||
id: txHash, |
|||
hash: txHash, |
|||
type: 'OUT', |
|||
value: amount, |
|||
blockHash: null, |
|||
blockHeight: null, |
|||
senders: [account.freshAddress], |
|||
recipients: [transaction.recipient], |
|||
accountId: account.id, |
|||
date: new Date().toISOString(), |
|||
} |
|||
}), |
|||
) |
|||
}, |
|||
) |
|||
|
|||
export default cmd |
@ -0,0 +1,16 @@ |
|||
// @flow
|
|||
|
|||
import React from 'react' |
|||
|
|||
const path = ( |
|||
<path |
|||
fill="currentColor" |
|||
d="M15.65 7.985c.008 4.27-3.475 7.762-7.745 7.765a7.722 7.722 0 0 1-5.2-1.998.375.375 0 0 1-.013-.544l.53-.53a.376.376 0 0 1 .517-.012A6.228 6.228 0 0 0 7.9 14.25 6.247 6.247 0 0 0 14.15 8 6.247 6.247 0 0 0 7.9 1.75a6.23 6.23 0 0 0-4.434 1.845L5 5.108a.375.375 0 0 1-.264.642H.725a.375.375 0 0 1-.375-.375V1.42c0-.333.402-.5.638-.267l1.41 1.39A7.75 7.75 0 0 1 15.65 7.985zm-5.22 2.818l.44-.606a.375.375 0 0 0-.082-.524L8.65 8.118V3.625a.375.375 0 0 0-.375-.375h-.75a.375.375 0 0 0-.375.375v5.257l2.755 2.004a.375.375 0 0 0 .524-.083z" |
|||
/> |
|||
) |
|||
|
|||
export default ({ size, ...p }: { size: number }) => ( |
|||
<svg viewBox="0 0 16 16" height={size} width={size} {...p}> |
|||
{path} |
|||
</svg> |
|||
) |
@ -1,48 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
|
|||
import scanAccountsOnDevice from './scanAccountsOnDevice' |
|||
import signAndBroadcastTransactionBTCLike from './signAndBroadcastTransaction/btc' |
|||
|
|||
import sync from './sync' |
|||
|
|||
export default { |
|||
sync, |
|||
signAndBroadcastTransactionBTCLike, |
|||
scan: async ( |
|||
send: IPCSend, |
|||
{ |
|||
devicePath, |
|||
currencyId, |
|||
}: { |
|||
devicePath: string, |
|||
currencyId: string, |
|||
}, |
|||
) => { |
|||
try { |
|||
send('accounts.scanAccountsOnDevice.start', { pid: process.pid }, { kill: false }) |
|||
const accounts = await scanAccountsOnDevice({ |
|||
devicePath, |
|||
currencyId, |
|||
onAccountScanned: account => { |
|||
send('accounts.scanAccountsOnDevice.accountScanned', account, { kill: false }) |
|||
}, |
|||
}) |
|||
send('accounts.scanAccountsOnDevice.success', accounts) |
|||
} catch (err) { |
|||
send('accounts.scanAccountsOnDevice.fail', formatErr(err)) |
|||
} |
|||
}, |
|||
} |
|||
|
|||
// TODO: move this to a helper
|
|||
function formatErr(err) { |
|||
if (err instanceof Error) { |
|||
return err.message || err.code |
|||
} |
|||
if (typeof err === 'string') { |
|||
return err |
|||
} |
|||
return 'unknown error' |
|||
} |
@ -1,71 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import Btc from '@ledgerhq/hw-app-btc' |
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import type { AccountRaw } from '@ledgerhq/live-common/lib/types' |
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
import { getWalletIdentifier } from '../scanAccountsOnDevice' |
|||
|
|||
type BitcoinLikeTransaction = { |
|||
amount: number, |
|||
feePerByte: number, |
|||
recipient: string, |
|||
} |
|||
|
|||
export default async function signAndBroadcastTransactionBTCLike( |
|||
send: IPCSend, |
|||
{ |
|||
account, |
|||
transaction, |
|||
deviceId, // which is in fact `devicePath`
|
|||
}: { |
|||
account: AccountRaw, |
|||
transaction: BitcoinLikeTransaction, |
|||
deviceId: string, |
|||
}, |
|||
) { |
|||
try { |
|||
// TODO: investigate why importing it on file scope causes trouble
|
|||
const core = require('init-ledger-core')() |
|||
|
|||
const txHash = await withDevice(deviceId)(async transport => { |
|||
const hwApp = new Btc(transport) |
|||
|
|||
const WALLET_IDENTIFIER = await getWalletIdentifier({ |
|||
hwApp, |
|||
isSegwit: !!account.isSegwit, |
|||
currencyId: account.currencyId, |
|||
devicePath: deviceId, |
|||
}) |
|||
|
|||
const njsWallet = await core.getWallet(WALLET_IDENTIFIER) |
|||
const njsAccount = await njsWallet.getAccount(account.index) |
|||
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount() |
|||
const njsWalletCurrency = njsWallet.getCurrency() |
|||
const amount = core.createAmount(njsWalletCurrency, transaction.amount) |
|||
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte) |
|||
const transactionBuilder = bitcoinLikeAccount.buildTransaction() |
|||
|
|||
// TODO: check if is valid address. if not, it will fail silently on invalid
|
|||
|
|||
transactionBuilder.sendToAddress(amount, transaction.recipient) |
|||
// TODO: don't use hardcoded value for sequence (and first also maybe)
|
|||
transactionBuilder.pickInputs(0, 0xffffff) |
|||
transactionBuilder.setFeesPerByte(fees) |
|||
|
|||
const builded = await transactionBuilder.build() |
|||
const signedTransaction = await core.signTransaction(hwApp, builded) |
|||
|
|||
const txHash = await njsAccount |
|||
.asBitcoinLikeAccount() |
|||
.broadcastRawTransaction(signedTransaction) |
|||
|
|||
return txHash |
|||
}) |
|||
|
|||
send('accounts.signAndBroadcastTransactionBTCLike.success', txHash) |
|||
} catch (err) { |
|||
send('accounts.signAndBroadcastTransactionBTCLike.fail', err) |
|||
} |
|||
} |
@ -1,7 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import type { IPCSend } from 'types/electron' |
|||
|
|||
export default (send: IPCSend) => { |
|||
setTimeout(() => send('accounts.sync.success'), 5e3) |
|||
} |
Loading…
Reference in new issue