Browse Source

move internals to commands/helpers

master
Gaëtan Renaudeau 7 years ago
parent
commit
931b33ebc9
  1. 145
      src/bridge/LibcoreBridge.js
  2. 2
      src/bridge/types.js
  3. 35
      src/commands/libcoreScanAccounts.js
  4. 82
      src/commands/libcoreSignAndBroadcast.js
  5. 1
      src/helpers/ipc.js
  6. 6
      src/helpers/libcore.js
  7. 48
      src/internals/accounts/index.js
  8. 71
      src/internals/accounts/signAndBroadcastTransaction/btc.js
  9. 7
      src/internals/accounts/sync.js
  10. 4
      src/internals/devices/index.js

145
src/bridge/LibcoreBridge.js

@ -1,21 +1,20 @@
// @flow
import React from 'react'
import { ipcRenderer } from 'electron'
import { map } from 'rxjs/operators'
import { decodeAccount, encodeAccount } from 'reducers/accounts'
import runJob from 'renderer/runJob'
import FeesBitcoinKind from 'components/FeesField/BitcoinKind'
import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
// import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind'
import type { WalletBridge, EditProps } from './types'
const notImplemented = new Error('LibcoreBridge: not implemented')
// TODO for ipcRenderer listeners we should have a concept of requestId because
// to be able to listen to events that only concerns you
// IMPORTANT: please read ./types.js that specify & document everything
type Transaction = *
type Transaction = {
amount: number,
feePerByte: number,
recipient: string,
}
const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
<FeesBitcoinKind
@ -27,6 +26,8 @@ const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
/>
)
const EditAdvancedOptions = undefined // Not implemented yet
/*
const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
<AdvancedOptionsBitcoinKind
isRBF={value.isRBF}
@ -35,93 +36,32 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
}}
/>
)
*/
const LibcoreBridge: WalletBridge<Transaction> = {
synchronize(initialAccount, { next, complete, error }) {
const unbind = () => ipcRenderer.removeListener('msg', handleAccountSync)
function handleAccountSync(e, msg) {
switch (msg.type) {
case 'account.sync.progress': {
next(a => a)
// FIXME TODO: use next(), to actually emit account updates.....
// - need to sync the balance
// - need to sync block height & block hash
// - need to sync operations.
// - once all that, need to set lastSyncDate to new Date()
// - when you implement addPendingOperation you also here need to:
// - if there were pendingOperations that are now in operations, remove them as well.
// - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically)
// then we probably should trash them out? it's a complex question for UI
break
}
case 'account.sync.fail': {
unbind()
error(new Error('failed')) // TODO more error detail
break
}
case 'account.sync.success': {
unbind()
complete()
break
}
default:
}
}
ipcRenderer.on('msg', handleAccountSync)
// TODO how to start the sync ?!
return {
unsubscribe() {
unbind()
console.warn('LibcoreBridge: interrupting synchronization is not supported')
},
}
},
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) {
const unbind = () => ipcRenderer.removeListener('msg', handleMsgEvent)
function handleMsgEvent(e, { data, type }) {
if (type === 'accounts.scanAccountsOnDevice.accountScanned') {
next({ ...decodeAccount(data), archived: true })
}
}
ipcRenderer.on('msg', handleMsgEvent)
let unsubscribed
runJob({
channel: 'accounts',
job: 'scan',
successResponse: 'accounts.scanAccountsOnDevice.success',
errorResponse: 'accounts.scanAccountsOnDevice.fail',
data: {
devicePath: deviceId,
scanAccountsOnDevice(currency, devicePath, observer) {
return libcoreScanAccounts
.send({
devicePath,
currencyId: currency.id,
},
}).then(
() => {
if (unsubscribed) return
unbind()
complete()
},
e => {
if (unsubscribed) return
unbind()
error(e)
},
)
})
.pipe(map(decodeAccount))
.subscribe(observer)
},
synchronize(_initialAccount, _observer) {
// FIXME TODO: use next(), to actually emit account updates.....
// - need to sync the balance
// - need to sync block height & block hash
// - need to sync operations.
// - once all that, need to set lastSyncDate to new Date()
// - when you implement addPendingOperation you also here need to:
// - if there were pendingOperations that are now in operations, remove them as well.
// - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically)
// then we probably should trash them out? it's a complex question for UI
return {
unsubscribe() {
unsubscribed = true
unbind()
console.warn('LibcoreBridge: interrupting scanAccounts is not implemented') // FIXME
console.warn('LibcoreBridge: sync not implemented')
},
}
},
@ -165,15 +105,20 @@ const LibcoreBridge: WalletBridge<Transaction> = {
getMaxAmount: (a, _t) => Promise.resolve(a.balance), // FIXME
signAndBroadcast: (account, transaction, deviceId) => {
const rawAccount = encodeAccount(account)
return runJob({
channel: 'accounts',
job: 'signAndBroadcastTransactionBTCLike',
successResponse: 'accounts.signAndBroadcastTransactionBTCLike.success',
errorResponse: 'accounts.signAndBroadcastTransactionBTCLike.fail',
data: { account: rawAccount, transaction, deviceId },
})
signAndBroadcast: async (account, transaction, deviceId) => {
const encodedAccount = encodeAccount(account)
const rawOp = await libcoreSignAndBroadcast
.send({
account: encodedAccount,
transaction,
deviceId,
})
.toPromise()
// quick HACK
const [op] = decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations
return op
},
}

2
src/bridge/types.js

@ -15,7 +15,7 @@ export type Observer<T> = {
}
export type Subscription = {
unsubscribe: () => void,
+unsubscribe: () => void,
}
export type EditProps<Transaction> = {

35
src/commands/libcoreScanAccounts.js

@ -0,0 +1,35 @@
// @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

82
src/commands/libcoreSignAndBroadcast.js

@ -0,0 +1,82 @@
// @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'
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 signedTransaction = await core.signTransaction(hwApp, builded)
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

1
src/helpers/ipc.js

@ -41,6 +41,7 @@ export class Command<In, A> {
})
},
error: error => {
console.log('exec error:', error)
send({
type: `ERROR_${requestId}`,
data: {

6
src/internals/accounts/scanAccountsOnDevice.js → src/helpers/libcore.js

@ -18,10 +18,10 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc
type Props = {
devicePath: string,
currencyId: string,
onAccountScanned: Function,
onAccountScanned: AccountRaw => *,
}
export default function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
const { devicePath, currencyId, onAccountScanned } = props
return withDevice(devicePath)(async transport => {
@ -269,7 +269,7 @@ async function buildAccountRaw({
freshAddressPath,
balance,
blockHeight,
archived: false,
archived: true,
index: accountIndex,
operations,
pendingOperations: [],

48
src/internals/accounts/index.js

@ -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'
}

71
src/internals/accounts/signAndBroadcastTransaction/btc.js

@ -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)
}
}

7
src/internals/accounts/sync.js

@ -1,7 +0,0 @@
// @flow
import type { IPCSend } from 'types/electron'
export default (send: IPCSend) => {
setTimeout(() => send('accounts.sync.success'), 5e3)
}

4
src/internals/devices/index.js

@ -1,6 +1,8 @@
// @flow
import type { Command } from 'helpers/ipc'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import getAddress from 'commands/getAddress'
import signTransaction from 'commands/signTransaction'
import getDeviceInfo from 'commands/getDeviceInfo'
@ -21,4 +23,6 @@ export const commands: Array<Command<any, any>> = [
getIsGenuine,
getLatestFirmwareForDevice,
installApp,
libcoreScanAccounts,
libcoreSignAndBroadcast,
]

Loading…
Cancel
Save