From df75ea4432618c0b9f304dd79f94de2b2c4dbf37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 08:53:27 +0200 Subject: [PATCH 1/7] Fix LibcoreBridge to not validate if amount=0 --- src/bridge/LibcoreBridge.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 45321d7e..b3ee7ff9 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -48,7 +48,7 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps) => ( const recipientValidLRU = LRU({ max: 100 }) -const isRecipientValid = (currency, recipient): Promise => { +const isRecipientValid = (currency, recipient) => { const key = `${currency.id}_${recipient}` let promise = recipientValidLRU.get(key) if (promise) return promise @@ -172,14 +172,18 @@ const LibcoreBridge: WalletBridge = { isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false, canBeSpent: (a, t) => - getFees(a, t) - .then(fees => fees !== null) - .catch(() => false), + !t.amount + ? Promise.resolve(true) + : getFees(a, t) + .then(fees => fees !== null) + .catch(() => false), getTotalSpent: (a, t) => - getFees(a, t) - .then(totalFees => t.amount + (totalFees || 0)) - .catch(() => 0), + !t.amount + ? Promise.resolve(0) + : getFees(a, t) + .then(totalFees => t.amount + (totalFees || 0)) + .catch(() => 0), getMaxAmount: (a, t) => getFees(a, t) From ff0cb8d9b3c352da7d5c0011da0c4abb4e6ba579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 09:00:50 +0200 Subject: [PATCH 2/7] Prevent negative values and Infinity to be inputable --- src/components/base/InputCurrency/index.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index 205d9067..97623746 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -14,10 +14,6 @@ import Select from 'components/base/LegacySelect' import type { Unit } from '@ledgerhq/live-common/lib/types' -function parseValue(value) { - return value.toString().replace(/,/g, '.') -} - function format(unit: Unit, value: number, { isFocused, showAllDigits, subMagnitude }) { // FIXME do we need locale for the input too ? return formatCurrencyUnit(unit, value, { @@ -104,7 +100,9 @@ class InputCurrency extends PureComponent { } handleChange = (v: string) => { - v = parseValue(v) + // FIXME this is to refactor. this is hacky and don't cover everything.. + + v = v.toString().replace(/,/g, '.') // allow to type directly `.` in input to have `0.` if (v.startsWith('.')) { @@ -120,7 +118,8 @@ class InputCurrency extends PureComponent { } // Check if value is valid Number - if (isNaN(Number(v))) { + const asNumber = parseFloat(v) + if (isNaN(asNumber) || !isFinite(asNumber) || asNumber < 0) { return } From 2d77bf0da98a115e81b9480206667a312300796c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 10:09:39 +0200 Subject: [PATCH 3/7] Improve InputCurrency - you have more control on what you type, no more weird "empty input" cases - it prevents to type more digits than the currency allows of magnitude - it do everything to prevent you from typing weird things --- src/components/base/InputCurrency/index.js | 85 ++++++++++++---------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index 97623746..da1963b0 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -6,7 +6,6 @@ import styled from 'styled-components' import { formatCurrencyUnit } from '@ledgerhq/live-common/lib/helpers/currencies' import noop from 'lodash/noop' -import isNaN from 'lodash/isNaN' import Box from 'components/base/Box' import Input from 'components/base/Input' @@ -14,6 +13,43 @@ import Select from 'components/base/LegacySelect' import type { Unit } from '@ledgerhq/live-common/lib/types' +// TODO move this back to live common +const numbers = '0123456789' +const sanitizeValueString = ( + unit: Unit, + valueString: string, +): { + display: string, + value: string, +} => { + let display = '' + let value = '' + let decimals = -1 + for (let i = 0; i < valueString.length; i++) { + const c = valueString[i] + if (numbers.indexOf(c) !== -1) { + if (decimals >= 0) { + decimals++ + if (decimals > unit.magnitude) break + value += c + display += c + } else if (value !== '0') { + value += c + display += c + } + } else if (decimals === -1 && (c === ',' || c === '.')) { + if (i === 0) display = '0' + decimals = 0 + display += '.' + } + } + for (let i = decimals; i < unit.magnitude; ++i) { + value += '0' + } + if (!value) value = '0' + return { display, value } +} + function format(unit: Unit, value: number, { isFocused, showAllDigits, subMagnitude }) { // FIXME do we need locale for the input too ? return formatCurrencyUnit(unit, value, { @@ -81,9 +117,10 @@ class InputCurrency extends PureComponent { componentWillReceiveProps(nextProps: Props) { const { value, showAllDigits, unit } = this.props const needsToBeReformatted = - value !== nextProps.value || - showAllDigits !== nextProps.showAllDigits || - unit !== nextProps.unit + !this.state.isFocused && + (value !== nextProps.value || + showAllDigits !== nextProps.showAllDigits || + unit !== nextProps.unit) if (needsToBeReformatted) { const { isFocused } = this.state this.setState({ @@ -100,31 +137,13 @@ class InputCurrency extends PureComponent { } handleChange = (v: string) => { - // FIXME this is to refactor. this is hacky and don't cover everything.. - - v = v.toString().replace(/,/g, '.') - - // allow to type directly `.` in input to have `0.` - if (v.startsWith('.')) { - v = `0${v}` - } - - // forbid multiple 0 at start - if (v === '' || v.startsWith('00')) { - const { onChange, unit } = this.props - onChange(0, unit) - this.setState({ displayValue: '' }) - return - } - - // Check if value is valid Number - const asNumber = parseFloat(v) - if (isNaN(asNumber) || !isFinite(asNumber) || asNumber < 0) { - return + const { onChange, unit, value } = this.props + const r = sanitizeValueString(unit, v) + const satoshiValue = parseInt(r.value, 10) + if (value !== satoshiValue) { + onChange(satoshiValue, unit) } - - this.emitOnChange(v) - this.setState({ displayValue: v || '' }) + this.setState({ displayValue: r.display }) } handleBlur = () => { @@ -148,16 +167,6 @@ class InputCurrency extends PureComponent { }) } - emitOnChange = (v: string) => { - const { onChange, unit } = this.props - const { displayValue } = this.state - - if (displayValue.toString() !== v.toString()) { - const satoshiValue = Number(v) * 10 ** unit.magnitude - onChange(satoshiValue, unit) - } - } - renderItem = item => item.code renderSelected = item => {item.code} From 494dbdcc57bda561bb4bcc4e506c98d8ad050832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 11:09:09 +0200 Subject: [PATCH 4/7] fix false positive on not enough balance --- src/bridge/LibcoreBridge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index b3ee7ff9..67f71138 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -175,7 +175,7 @@ const LibcoreBridge: WalletBridge = { !t.amount ? Promise.resolve(true) : getFees(a, t) - .then(fees => fees !== null) + .then(() => true) .catch(() => false), getTotalSpent: (a, t) => From 3373ae69e5aabaeab146fd78972f00d647f3e4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 11:13:23 +0200 Subject: [PATCH 5/7] Fix refuse on device to go on step 4 --- src/components/DeviceConfirm/index.js | 4 ++-- .../modals/Send/03-step-verification.js | 5 ++--- src/components/modals/Send/index.js | 16 ++++++++-------- src/helpers/createCustomErrorClass.js | 3 ++- static/i18n/en/errors.yml | 1 + 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/DeviceConfirm/index.js b/src/components/DeviceConfirm/index.js index a9c88790..6e9a4e8b 100644 --- a/src/components/DeviceConfirm/index.js +++ b/src/components/DeviceConfirm/index.js @@ -41,7 +41,7 @@ const WrapperIcon = styled(Box)` } ` -const Check = ({ error }: { error: * }) => ( +const Check = ({ error }: { error?: boolean }) => ( {error ? : } @@ -74,7 +74,7 @@ const PushButton = styled(Box)` ` type Props = { - error: *, + error?: boolean, } const SVG = ( diff --git a/src/components/modals/Send/03-step-verification.js b/src/components/modals/Send/03-step-verification.js index a6df0e6d..1148b617 100644 --- a/src/components/modals/Send/03-step-verification.js +++ b/src/components/modals/Send/03-step-verification.js @@ -27,14 +27,13 @@ const Info = styled(Box).attrs({ ` type Props = { - hasError: boolean, t: T, } -export default ({ t, hasError }: Props) => ( +export default ({ t }: Props) => ( {multiline(t('app:send.steps.verification.warning'))} {t('app:send.steps.verification.body')} - + ) diff --git a/src/components/modals/Send/index.js b/src/components/modals/Send/index.js index 62fdf864..57106659 100644 --- a/src/components/modals/Send/index.js +++ b/src/components/modals/Send/index.js @@ -15,6 +15,7 @@ import { getBridgeForCurrency } from 'bridge' import { accountsSelector } from 'reducers/accounts' import { updateAccountWithUpdater } from 'actions/accounts' +import createCustomErrorClass from 'helpers/createCustomErrorClass' import { MODAL_SEND } from 'config/constants' import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal' @@ -32,6 +33,8 @@ import StepAmount from './01-step-amount' import StepVerification from './03-step-verification' import StepConfirmation from './04-step-confirmation' +export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') + type Props = { updateAccountWithUpdater: (string, (Account) => Account) => void, accounts: Account[], @@ -226,14 +229,11 @@ class SendModal extends Component> { }) } - onOperationError = (error: Error) => { - // $FlowFixMe - if (error.statusCode === 0x6985) { - // User denied on device - this.setState({ error }) - } else { - this.setState({ error, stepIndex: 3 }) - } + onOperationError = (error: *) => { + this.setState({ + error: error.statusCode === 0x6985 ? new UserRefusedOnDevice() : error, + stepIndex: 3, + }) } onChangeAccount = account => { diff --git a/src/helpers/createCustomErrorClass.js b/src/helpers/createCustomErrorClass.js index 78738ef5..698dc61e 100644 --- a/src/helpers/createCustomErrorClass.js +++ b/src/helpers/createCustomErrorClass.js @@ -1,6 +1,6 @@ // @flow -export default (name: string) => { +export default (name: string): Class => { const C = function CustomError(message?: string, fields?: Object) { this.name = name this.message = message || name @@ -9,5 +9,6 @@ export default (name: string) => { } // $FlowFixMe C.prototype = new Error() + // $FlowFixMe we can't easily type a subset of Error for now... return C } diff --git a/static/i18n/en/errors.yml b/static/i18n/en/errors.yml index 34d4f713..7b6596f8 100644 --- a/static/i18n/en/errors.yml +++ b/static/i18n/en/errors.yml @@ -11,3 +11,4 @@ LedgerAPINotAvailable: 'Ledger API is not available for currency {{currencyName} LedgerAPIError: 'A problem occurred with Ledger API. Please try again later. (HTTP {{status}})' NetworkDown: 'Your internet connection seems down. Please try again later.' NoAddressesFound: 'No accounts found' +UserRefusedOnDevice: Transaction have been aborted From e684879b6f03c7ae92d890eab0a7c99368044bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 11:16:41 +0200 Subject: [PATCH 6/7] fix calculation --- src/components/base/InputCurrency/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index da1963b0..c32377fc 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -43,7 +43,7 @@ const sanitizeValueString = ( display += '.' } } - for (let i = decimals; i < unit.magnitude; ++i) { + for (let i = Math.max(0, decimals); i < unit.magnitude; ++i) { value += '0' } if (!value) value = '0' From 36e40948a8999893f650b451f1cc283d833cf6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 15 Jun 2018 11:25:18 +0200 Subject: [PATCH 7/7] In the OUT case we need to add the fees --- src/helpers/libcore.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 0cc0232d..5607fda8 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -312,7 +312,7 @@ function buildOperationRaw({ const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction() const hash = bitcoinLikeTransaction.getHash() const operationType = op.getOperationType() - const value = op.getAmount().toLong() + let value = op.getAmount().toLong() const fee = op.getFees().toLong() const OperationTypeMap: { [_: $Keys]: OperationType } = { @@ -323,6 +323,10 @@ function buildOperationRaw({ // if transaction is a send, amount becomes negative const type = OperationTypeMap[operationType] + if (type === 'OUT') { + value += fee + } + return { id, hash,