Browse Source

Implement libcore features for Send funds

- address validation
- total fees & Total spent calculation
- not enough balance detection
master
Gaëtan Renaudeau 7 years ago
parent
commit
b7d2b9a0fe
  1. 3
      package.json
  2. 54
      src/bridge/LibcoreBridge.js
  3. 4
      src/commands/index.js
  4. 63
      src/commands/libcoreGetFees.js
  5. 24
      src/commands/libcoreValidAddress.js
  6. 5
      src/helpers/libcore.js
  7. 6
      yarn.lock

3
package.json

@ -41,7 +41,7 @@
"@ledgerhq/hw-app-xrp": "^4.13.0",
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "1.7.0",
"@ledgerhq/ledger-core": "1.9.0",
"@ledgerhq/live-common": "2.30.0",
"async": "^2.6.1",
"axios": "^0.18.0",
@ -64,6 +64,7 @@
"i18next-node-fs-backend": "^1.0.0",
"invariant": "^2.2.4",
"lodash": "^4.17.5",
"lru-cache": "^4.1.3",
"moment": "^2.22.2",
"qrcode": "^1.2.0",
"qrcode-reader": "^1.0.4",

54
src/bridge/LibcoreBridge.js

@ -1,6 +1,7 @@
// @flow
import React from 'react'
import { Observable } from 'rxjs'
import LRU from 'lru-cache'
import { map } from 'rxjs/operators'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { decodeAccount, encodeAccount } from 'reducers/accounts'
@ -8,6 +9,8 @@ import FeesBitcoinKind from 'components/FeesField/BitcoinKind'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreValidAddress from 'commands/libcoreValidAddress'
import type { WalletBridge, EditProps } from './types'
const notImplemented = new Error('LibcoreBridge: not implemented')
@ -43,6 +46,38 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
)
*/
const recipientValidLRU = LRU({ max: 100 })
const isRecipientValid = (currency, recipient): Promise<boolean> => {
const key = `${currency.id}_${recipient}`
let promise = recipientValidLRU.get(key)
if (promise) return promise
promise = libcoreValidAddress
.send({
address: recipient,
currencyId: currency.id,
})
.toPromise()
recipientValidLRU.set(key, promise)
return promise
}
const feesLRU = LRU({ max: 100 })
const getFees = async (a, transaction) => {
const isValid = await isRecipientValid(a.currency, transaction.recipient)
if (!isValid) return null
const key = `${a.id}_${transaction.amount}_${transaction.recipient}_${transaction.feePerByte}`
let promise = feesLRU.get(key)
if (promise) return promise
promise = libcoreGetFees
.send({ accountId: a.id, accountIndex: a.index, transaction })
.toPromise()
.then(r => r.totalFees)
feesLRU.set(key, promise)
return promise
}
const LibcoreBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, devicePath, observer) {
return libcoreScanAccounts
@ -107,7 +142,7 @@ const LibcoreBridge: WalletBridge<Transaction> = {
pullMoreOperations: () => Promise.reject(notImplemented),
isRecipientValid: (currency, recipient) => Promise.resolve(recipient.length > 0),
isRecipientValid,
createTransaction: () => ({
amount: 0,
@ -136,14 +171,23 @@ const LibcoreBridge: WalletBridge<Transaction> = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent: (a, t) => Promise.resolve(t.amount <= a.balance), // FIXME
canBeSpent: (a, t) =>
getFees(a, t)
.then(fees => fees !== null)
.catch(() => false),
getTotalSpent: (a, t) => Promise.resolve(t.amount), // FIXME
getTotalSpent: (a, t) =>
getFees(a, t)
.then(totalFees => t.amount + (totalFees || 0))
.catch(() => 0),
getMaxAmount: (a, _t) => Promise.resolve(a.balance), // FIXME
getMaxAmount: (a, t) =>
getFees(a, t)
.catch(() => 0)
.then(totalFees => a.balance - (totalFees || 0)),
signAndBroadcast: (account, transaction, deviceId) => {
const encodedAccount = encodeAccount(account)
const encodedAccount = encodeAccount(account) // FIXME no need to send the whole account over the threads
return libcoreSignAndBroadcast
.send({
account: encodedAccount,

4
src/commands/index.js

@ -14,11 +14,13 @@ import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
import installOsuFirmware from 'commands/installOsuFirmware'
import isDashboardOpen from 'commands/isDashboardOpen'
import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreGetVersion from 'commands/libcoreGetVersion'
import libcoreHardReset from 'commands/libcoreHardReset'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreValidAddress from 'commands/libcoreValidAddress'
import listApps from 'commands/listApps'
import listenDevices from 'commands/listenDevices'
import signTransaction from 'commands/signTransaction'
@ -39,11 +41,13 @@ const all: Array<Command<any, any>> = [
installMcu,
installOsuFirmware,
isDashboardOpen,
libcoreGetFees,
libcoreGetVersion,
libcoreHardReset,
libcoreScanAccounts,
libcoreSignAndBroadcast,
libcoreSyncAccount,
libcoreValidAddress,
listApps,
listenDevices,
signTransaction,

63
src/commands/libcoreGetFees.js

@ -0,0 +1,63 @@
// @flow
import { Observable } from 'rxjs'
import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc'
import * as accountIdHelper from 'helpers/accountId'
import { isValidAddress } from 'helpers/libcore'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
const InvalidAddress = createCustomErrorClass('InvalidAddress')
type BitcoinLikeTransaction = {
// TODO we rename this Transaction concept into transactionInput
amount: number,
feePerByte: number,
recipient: string,
}
type Input = {
accountId: string,
accountIndex: number,
transaction: BitcoinLikeTransaction,
}
type Result = { totalFees: number }
const cmd: Command<Input, Result> = createCommand(
'libcoreGetFees',
({ accountId, accountIndex, transaction }) =>
Observable.create(o => {
let unsubscribed = false
const isCancelled = () => unsubscribed
withLibcore(async core => {
const { walletName } = accountIdHelper.decode(accountId)
const njsWallet = await core.getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(accountIndex)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const feesPerByte = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
if (!isValidAddress(core, njsWalletCurrency, transaction.recipient)) {
// FIXME this is a bug in libcore. later it will probably check this and we can remove this check
throw new InvalidAddress()
}
transactionBuilder.sendToAddress(amount, transaction.recipient)
transactionBuilder.pickInputs(0, 0xffffff)
transactionBuilder.setFeesPerByte(feesPerByte)
const builded = await transactionBuilder.build()
const totalFees = builded.getFees().toLong()
o.next({ totalFees })
}).then(() => o.complete(), e => o.error(e))
return () => {
unsubscribed = true
}
}),
)
export default cmd

24
src/commands/libcoreValidAddress.js

@ -0,0 +1,24 @@
// @flow
import { fromPromise } from 'rxjs/observable/fromPromise'
import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc'
import { isValidAddress } from 'helpers/libcore'
type Input = {
address: string,
currencyId: string,
}
const cmd: Command<Input, boolean> = createCommand(
'libcoreValidAddress',
({ currencyId, address }) =>
fromPromise(
withLibcore(async core => {
const currency = await core.getCurrency(currencyId)
return isValidAddress(core, currency, address)
}),
),
)
export default cmd

5
src/helpers/libcore.js

@ -16,6 +16,11 @@ import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accou
const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
export function isValidAddress(core: *, currency: *, address: string): boolean {
const addr = new core.NJSAddress(address, currency)
return addr.isValid(address, currency)
}
type Props = {
core: *,
devicePath: string,

6
yarn.lock

@ -1502,9 +1502,9 @@
dependencies:
events "^2.0.0"
"@ledgerhq/ledger-core@1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.7.0.tgz#ac3d738e1b6b2f0a4e18d645300259f8c02a4851"
"@ledgerhq/ledger-core@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.9.0.tgz#3240857e76f3c2b17ba02d120b96fd1b13b50789"
dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6"

Loading…
Cancel
Save