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