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

2
src/bridge/makeMockBridge.js

@ -146,7 +146,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
getMaxAmount, getMaxAmount,
signAndBroadcast: async (account, t) => { signAndBroadcast: async ({ account, transaction: t }) => {
const rng = new Prando() const rng = new Prando()
const op = genOperation(account, account.operations, account.currency, rng) const op = genOperation(account, account.operations, account.currency, rng)
op.amount = -t.amount 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... * 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). * 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 () => { sign = async () => {
const { device, account, transaction, bridge, onSuccess } = this.props const { device, account, transaction, bridge, onSuccess } = this.props
try { try {
const txid = await bridge.signAndBroadcast(account, transaction, device.path) const txid = await bridge.signAndBroadcast({ account, transaction, deviceId: device.path })
onSuccess(txid) onSuccess(txid)
} catch (error) { } catch (error) {
this.setState({ error }) this.setState({ error })

3
src/internals/accounts/index.js

@ -3,11 +3,13 @@
import type { IPCSend } from 'types/electron' import type { IPCSend } from 'types/electron'
import scanAccountsOnDevice from './scanAccountsOnDevice' import scanAccountsOnDevice from './scanAccountsOnDevice'
import signAndBroadcastTransactionBTCLike from './signAndBroadcastTransaction/btc'
import sync from './sync' import sync from './sync'
export default { export default {
sync, sync,
signAndBroadcastTransactionBTCLike,
scan: async ( scan: async (
send: IPCSend, send: IPCSend,
{ {
@ -29,7 +31,6 @@ export default {
}) })
send('accounts.scanAccountsOnDevice.success', accounts) send('accounts.scanAccountsOnDevice.success', accounts)
} catch (err) { } catch (err) {
console.log(err)
send('accounts.scanAccountsOnDevice.fail', formatErr(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 return accounts
} }
async function scanAccountsOnDeviceBySegwit({ export async function getWalletIdentifier({
hwApp, hwApp,
isSegwit,
currencyId, currencyId,
onAccountScanned,
devicePath, devicePath,
isSegwit, }: {
hwApp: Object,
isSegwit: boolean,
currencyId: string,
devicePath: string,
}) { }) {
// compute wallet identifier
const isVerify = false const isVerify = false
const deviceIdentifiers = await hwApp.getWalletPublicKey(devicePath, isVerify, isSegwit) const deviceIdentifiers = await hwApp.getWalletPublicKey(devicePath, isVerify, isSegwit)
const { publicKey } = deviceIdentifiers const { publicKey } = deviceIdentifiers
const WALLET_IDENTIFIER = `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}` 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 // retrieve or create the wallet
const wallet = await getOrCreateWallet(WALLET_IDENTIFIER, currencyId, isSegwit) 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 // Yeah. `any` should be `AccountRaw[]` but it can also be a map
// of wrapped accounts. And as flow is apparently incapable of doing // of wrapped accounts. And as flow is apparently incapable of doing
// such a simple thing, let's put any, right? I don't care. // 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, successResponse: string,
errorResponse: string, errorResponse: string,
data?: any, data?: any,
}): Promise<void> { }): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer.send(channel, { type: job, data }) ipcRenderer.send(channel, { type: job, data })
ipcRenderer.on('msg', handler) ipcRenderer.on('msg', handler)

Loading…
Cancel
Save