Browse Source

Connect BTC like transaction to LibcoreBridge

master
meriadec 7 years ago
parent
commit
ce7c843323
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 14
      src/bridge/LibcoreBridge.js
  2. 2
      src/bridge/makeMockBridge.js
  3. 6
      src/bridge/types.js
  4. 2
      src/components/DeviceSignTransaction.js
  5. 3
      src/internals/accounts/index.js
  6. 24
      src/internals/accounts/scanAccountsOnDevice.js
  7. 71
      src/internals/accounts/signAndBroadcastTransaction/btc.js
  8. 4
      src/reducers/accounts.js
  9. 2
      src/renderer/runJob.js

14
src/bridge/LibcoreBridge.js

@ -1,7 +1,8 @@
// @flow
import React from 'react'
import { ipcRenderer } from 'electron'
import { decodeAccount } from 'reducers/accounts'
import { decodeAccount, encodeAccount } from 'reducers/accounts'
import runJob from 'renderer/runJob'
import FeesBitcoinKind from 'components/FeesField/BitcoinKind'
import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind'
@ -153,7 +154,16 @@ const LibcoreBridge: WalletBridge<Transaction> = {
getMaxAmount: (a, t) => Promise.resolve(a.balance - t.feePerByte),
signAndBroadcast: () => Promise.reject(notImplemented),
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 },
})
},
}
export default LibcoreBridge

2
src/bridge/makeMockBridge.js

@ -146,7 +146,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
getMaxAmount,
signAndBroadcast: async (account, t) => {
signAndBroadcast: async ({ account, transaction: t }) => {
const rng = new Prando()
const op = genOperation(account, account.operations, account.currency, rng)
op.amount = -t.amount

6
src/bridge/types.js

@ -98,5 +98,9 @@ export interface WalletBridge<Transaction> {
* NOTE: in future, when transaction balance is close to account.balance, we could wipe it all at this level...
* to implement that, we might want to have special logic `account.balance-transaction.amount < dust` but not sure where this should leave (i would say on UI side because we need to inform user visually).
*/
signAndBroadcast(account: Account, transaction: Transaction, deviceId: DeviceId): Promise<string>;
signAndBroadcast({
account: Account,
transaction: Transaction,
deviceId: DeviceId,
}): Promise<string>;
}

2
src/components/DeviceSignTransaction.js

@ -34,7 +34,7 @@ class DeviceSignTransaction extends PureComponent<Props, State> {
sign = async () => {
const { device, account, transaction, bridge, onSuccess } = this.props
try {
const txid = await bridge.signAndBroadcast(account, transaction, device.path)
const txid = await bridge.signAndBroadcast({ account, transaction, deviceId: device.path })
onSuccess(txid)
} catch (error) {
this.setState({ error })

3
src/internals/accounts/index.js

@ -3,11 +3,13 @@
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,
{
@ -29,7 +31,6 @@ export default {
})
send('accounts.scanAccountsOnDevice.success', accounts)
} catch (err) {
console.log(err)
send('accounts.scanAccountsOnDevice.fail', formatErr(err))
}
},

24
src/internals/accounts/scanAccountsOnDevice.js

@ -46,19 +46,33 @@ export default async function scanAccountsOnDevice(props: Props): Promise<Accoun
return accounts
}
async function scanAccountsOnDeviceBySegwit({
export async function getWalletIdentifier({
hwApp,
isSegwit,
currencyId,
onAccountScanned,
devicePath,
isSegwit,
}: {
hwApp: Object,
isSegwit: boolean,
currencyId: string,
devicePath: string,
}) {
// compute wallet identifier
const isVerify = false
const deviceIdentifiers = await hwApp.getWalletPublicKey(devicePath, isVerify, isSegwit)
const { publicKey } = deviceIdentifiers
const WALLET_IDENTIFIER = `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}`
return WALLET_IDENTIFIER
}
async function scanAccountsOnDeviceBySegwit({
hwApp,
currencyId,
onAccountScanned,
devicePath,
isSegwit,
}) {
// compute wallet identifier
const WALLET_IDENTIFIER = await getWalletIdentifier({ hwApp, isSegwit, currencyId, devicePath })
// retrieve or create the wallet
const wallet = await getOrCreateWallet(WALLET_IDENTIFIER, currencyId, isSegwit)

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

@ -0,0 +1,71 @@
// @flow
import Btc from '@ledgerhq/hw-app-btc'
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import type { AccountRaw } from '@ledgerhq/live-common/lib/types'
import type Transport from '@ledgerhq/hw-transport'
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')()
// instanciate app on device
const transport: Transport<*> = await CommNodeHid.open(deviceId)
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)
send('accounts.signAndBroadcastTransactionBTCLike.success', txHash)
} catch (err) {
send('accounts.signAndBroadcastTransactionBTCLike.fail', err)
}
}

4
src/reducers/accounts.js

@ -119,6 +119,10 @@ export function decodeAccount(account: AccountRaw): Account {
})
}
export function encodeAccount(account: Account): AccountRaw {
return accountModel.encode(account).data
}
// Yeah. `any` should be `AccountRaw[]` but it can also be a map
// of wrapped accounts. And as flow is apparently incapable of doing
// such a simple thing, let's put any, right? I don't care.

2
src/renderer/runJob.js

@ -14,7 +14,7 @@ export default function runJob({
successResponse: string,
errorResponse: string,
data?: any,
}): Promise<void> {
}): Promise<any> {
return new Promise((resolve, reject) => {
ipcRenderer.send(channel, { type: job, data })
ipcRenderer.on('msg', handler)

Loading…
Cancel
Save