@ -2,9 +2,8 @@
import type { AccountRaw , OperationRaw } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc'
import { fromPromis e } from 'rxjs/observable/fromPromise '
import { Observabl e } from 'rxjs'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies'
import type Transport from '@ledgerhq/hw-transport'
import withLibcore from 'helpers/withLibcore'
import { createCommand , Command } from 'helpers/ipc'
@ -23,24 +22,37 @@ type Input = {
deviceId : string ,
}
type Result = $Exact < OperationRaw >
type Result = { type : 'signed' } | { type : 'broadcasted' , operation : OperationRaw }
const cmd : Command < Input , Result > = createCommand (
'libcoreSignAndBroadcast' ,
( { account , transaction , deviceId } ) =>
fromPromise (
withDevice ( deviceId ) ( transport =>
withLibcore ( core =>
doSignAndBroadcast ( {
account ,
transaction ,
deviceId ,
core ,
transport ,
} ) ,
) ,
) ,
) ,
Observable . create ( o => {
let unsubscribed = false
const isCancelled = ( ) => unsubscribed
withLibcore ( core =>
doSignAndBroadcast ( {
account ,
transaction ,
deviceId ,
core ,
isCancelled ,
onSigned : ( ) => {
o . next ( { type : 'signed' } )
} ,
onOperationBroadcasted : operation => {
o . next ( {
type : 'broadcasted' ,
operation ,
} )
} ,
} ) ,
) . then ( ( ) => o . complete ( ) , e => o . error ( e ) )
return ( ) => {
unsubscribed = true
}
} ) ,
)
export async function doSignAndBroadcast ( {
@ -48,60 +60,74 @@ export async function doSignAndBroadcast({
transaction ,
deviceId ,
core ,
transport ,
isCancelled ,
onSigned ,
onOperationBroadcasted ,
} : {
account : AccountRaw ,
transaction : BitcoinLikeTransaction ,
deviceId : string ,
core : * ,
transport : Transport < * > ,
} ) {
const hwApp = new Btc ( transport )
const WALLET_IDENTIFIER = await getWalletIdentifier ( {
hwApp ,
isSegwit : ! ! account . isSegwit ,
currencyId : account . currencyId ,
devicePath : deviceId ,
isCancelled : ( ) => boolean ,
onSigned : ( ) => void ,
onOperationBroadcasted : ( optimisticOp : $Exact < OperationRaw > ) => void ,
} ) : Promise < void > {
let njsAccount
const signedTransaction : ? string = 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 )
if ( isCancelled ( ) ) return null
njsAccount = await njsWallet . getAccount ( account . index )
if ( isCancelled ( ) ) return null
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 ( )
if ( isCancelled ( ) ) return null
const sigHashType = core . helpers . bytesToHex (
njsWalletCurrency . bitcoinLikeNetworkParameters . SigHash ,
)
const hasTimestamp = njsWalletCurrency . bitcoinLikeNetworkParameters . UsesTimestampedTransaction
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const currency = getCryptoCurrencyById ( account . currencyId )
return core . signTransaction ( {
hwApp ,
transaction : builded ,
sigHashType : parseInt ( sigHashType , 16 ) . toString ( ) ,
supportsSegwit : ! ! currency . supportsSegwit ,
isSegwit : account . isSegwit ,
hasTimestamp ,
} )
} )
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 hasTimestamp = njsWalletCurrency . bitcoinLikeNetworkParameters . UsesTimestampedTransaction
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const currency = getCryptoCurrencyById ( account . currencyId )
const signedTransaction = await core . signTransaction ( {
hwApp ,
transaction : builded ,
sigHashType : parseInt ( sigHashType , 16 ) . toString ( ) ,
supportsSegwit : ! ! currency . supportsSegwit ,
isSegwit : account . isSegwit ,
hasTimestamp ,
} )
if ( ! signedTransaction || isCancelled ( ) || ! njsAccount ) return
onSigned ( )
const txHash = await njsAccount . asBitcoinLikeAccount ( ) . broadcastRawTransaction ( signedTransaction )
// optimistic operation
return {
// NB we don't check isCancelled() because the broadcast is not cancellable now!
onOperationBroadcasted ( {
id : txHash ,
hash : txHash ,
type : 'OUT' ,
@ -113,7 +139,7 @@ export async function doSignAndBroadcast({
recipients : [ transaction . recipient ] ,
accountId : account . id ,
date : new Date ( ) . toISOString ( ) ,
}
} )
}
export default cmd