Browse Source

Add fail-safe for Fees to block the UI until loaded

gre-patch-1
Gaëtan Renaudeau 6 years ago
parent
commit
7be88e4b35
No known key found for this signature in database GPG Key ID: 7B66B85F042E5451
  1. 30
      src/bridge/EthereumJSBridge.js
  2. 48
      src/bridge/LibcoreBridge.js
  3. 19
      src/bridge/RippleJSBridge.js
  4. 17
      src/components/FeesField/BitcoinKind.js
  5. 9
      src/components/FeesField/EthereumKind.js
  6. 9
      src/components/FeesField/RippleKind.js
  7. 23
      src/components/base/Input/index.js
  8. 23
      src/components/base/InputCurrency/index.js
  9. 9
      src/components/modals/Send/fields/AmountField.js
  10. 1
      src/config/errors.js
  11. 3
      static/i18n/en/errors.json

30
src/bridge/EthereumJSBridge.js

@ -16,20 +16,20 @@ import { getDerivations } from 'helpers/derivations'
import getAddressCommand from 'commands/getAddress' import getAddressCommand from 'commands/getAddress'
import signTransactionCommand from 'commands/signTransaction' import signTransactionCommand from 'commands/signTransaction'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName'
import { NotEnoughBalance, ETHAddressNonEIP } from 'config/errors' import { NotEnoughBalance, FeeNotLoaded, ETHAddressNonEIP } from 'config/errors'
import type { EditProps, WalletBridge } from './types' import type { EditProps, WalletBridge } from './types'
type Transaction = { type Transaction = {
recipient: string, recipient: string,
amount: BigNumber, amount: BigNumber,
gasPrice: BigNumber, gasPrice: ?BigNumber,
gasLimit: BigNumber, gasLimit: BigNumber,
} }
const serializeTransaction = t => ({ const serializeTransaction = t => ({
recipient: t.recipient, recipient: t.recipient,
amount: `0x${BigNumber(t.amount).toString(16)}`, amount: `0x${BigNumber(t.amount).toString(16)}`,
gasPrice: `0x${BigNumber(t.gasPrice).toString(16)}`, gasPrice: !t.gasPrice ? '0x00' : `0x${BigNumber(t.gasPrice).toString(16)}`,
gasLimit: `0x${BigNumber(t.gasLimit).toString(16)}`, gasLimit: `0x${BigNumber(t.gasLimit).toString(16)}`,
}) })
@ -140,6 +140,8 @@ const signAndBroadcast = async ({
onSigned, onSigned,
onOperationBroadcasted, onOperationBroadcasted,
}) => { }) => {
const { gasPrice, amount, gasLimit } = t
if (!gasPrice) throw new FeeNotLoaded()
const api = apiForCurrency(a.currency) const api = apiForCurrency(a.currency)
const nonce = await api.getAccountNonce(a.freshAddress) const nonce = await api.getAccountNonce(a.freshAddress)
@ -162,8 +164,8 @@ const signAndBroadcast = async ({
id: `${a.id}-${hash}-OUT`, id: `${a.id}-${hash}-OUT`,
hash, hash,
type: 'OUT', type: 'OUT',
value: t.amount, value: amount,
fee: t.gasPrice.times(t.gasLimit), fee: gasPrice.times(gasLimit),
blockHeight: null, blockHeight: null,
blockHash: null, blockHash: null,
accountId: a.id, accountId: a.id,
@ -402,7 +404,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
createTransaction: () => ({ createTransaction: () => ({
amount: BigNumber(0), amount: BigNumber(0),
recipient: '', recipient: '',
gasPrice: BigNumber(0), gasPrice: null,
gasLimit: BigNumber(0x5208), gasLimit: BigNumber(0x5208),
}), }),
@ -425,16 +427,22 @@ const EthereumBridge: WalletBridge<Transaction> = {
EditAdvancedOptions, EditAdvancedOptions,
checkValidTransaction: (a, t) => checkValidTransaction: (a, t) =>
t.amount.isLessThanOrEqualTo(a.balance) !t.gasPrice
? Promise.resolve(true) ? Promise.reject(new FeeNotLoaded())
: Promise.reject(new NotEnoughBalance()), : t.amount.isLessThanOrEqualTo(a.balance)
? Promise.resolve(true)
: Promise.reject(new NotEnoughBalance()),
getTotalSpent: (a, t) => getTotalSpent: (a, t) =>
t.amount.isGreaterThan(0) && t.gasPrice.isGreaterThan(0) && t.gasLimit.isGreaterThan(0) t.amount.isGreaterThan(0) &&
t.gasPrice &&
t.gasPrice.isGreaterThan(0) &&
t.gasLimit.isGreaterThan(0)
? Promise.resolve(t.amount.plus(t.gasPrice.times(t.gasLimit))) ? Promise.resolve(t.amount.plus(t.gasPrice.times(t.gasLimit)))
: Promise.resolve(BigNumber(0)), : Promise.resolve(BigNumber(0)),
getMaxAmount: (a, t) => Promise.resolve(a.balance.minus(t.gasPrice.times(t.gasLimit))), getMaxAmount: (a, t) =>
Promise.resolve(a.balance.minus((t.gasPrice || BigNumber(0)).times(t.gasLimit))),
signAndBroadcast: (a, t, deviceId) => signAndBroadcast: (a, t, deviceId) =>
Observable.create(o => { Observable.create(o => {

48
src/bridge/LibcoreBridge.js

@ -11,7 +11,7 @@ import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast' import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreGetFees, { extractGetFeesInputFromAccount } from 'commands/libcoreGetFees' import libcoreGetFees, { extractGetFeesInputFromAccount } from 'commands/libcoreGetFees'
import libcoreValidAddress from 'commands/libcoreValidAddress' import libcoreValidAddress from 'commands/libcoreValidAddress'
import { NotEnoughBalance } from 'config/errors' import { NotEnoughBalance, FeeNotLoaded } from 'config/errors'
import type { WalletBridge, EditProps } from './types' import type { WalletBridge, EditProps } from './types'
const NOT_ENOUGH_FUNDS = 52 const NOT_ENOUGH_FUNDS = 52
@ -20,15 +20,18 @@ const notImplemented = new Error('LibcoreBridge: not implemented')
type Transaction = { type Transaction = {
amount: BigNumber, amount: BigNumber,
feePerByte: BigNumber, feePerByte: ?BigNumber,
recipient: string, recipient: string,
} }
const serializeTransaction = t => ({ const serializeTransaction = t => {
recipient: t.recipient, const { feePerByte } = t
amount: t.amount.toString(), return {
feePerByte: t.feePerByte.toString(), recipient: t.recipient,
}) amount: t.amount.toString(),
feePerByte: (feePerByte && feePerByte.toString()) || '0',
}
}
const decodeOperation = (encodedAccount, rawOp) => const decodeOperation = (encodedAccount, rawOp) =>
decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations[0] decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations[0]
@ -75,7 +78,9 @@ const isRecipientValid = (currency, recipient) => {
const feesLRU = LRU({ max: 100 }) const feesLRU = LRU({ max: 100 })
const getFeesKey = (a, t) => const getFeesKey = (a, t) =>
`${a.id}_${a.blockHeight || 0}_${t.amount.toString()}_${t.recipient}_${t.feePerByte.toString()}` `${a.id}_${a.blockHeight || 0}_${t.amount.toString()}_${t.recipient}_${
t.feePerByte ? t.feePerByte.toString() : ''
}`
const getFees = async (a, transaction) => { const getFees = async (a, transaction) => {
const isValid = await isRecipientValid(a.currency, transaction.recipient) const isValid = await isRecipientValid(a.currency, transaction.recipient)
@ -83,6 +88,7 @@ const getFees = async (a, transaction) => {
const key = getFeesKey(a, transaction) const key = getFeesKey(a, transaction)
let promise = feesLRU.get(key) let promise = feesLRU.get(key)
if (promise) return promise if (promise) return promise
promise = libcoreGetFees promise = libcoreGetFees
.send({ .send({
...extractGetFeesInputFromAccount(a), ...extractGetFeesInputFromAccount(a),
@ -95,17 +101,19 @@ const getFees = async (a, transaction) => {
} }
const checkValidTransaction = (a, t) => const checkValidTransaction = (a, t) =>
!t.amount !t.feePerByte
? Promise.resolve(true) ? Promise.reject(new FeeNotLoaded())
: getFees(a, t) : !t.amount
.then(() => true) ? Promise.resolve(true)
.catch(e => { : getFees(a, t)
if (e.code === NOT_ENOUGH_FUNDS) { .then(() => true)
throw new NotEnoughBalance() .catch(e => {
} if (e.code === NOT_ENOUGH_FUNDS) {
feesLRU.del(getFeesKey(a, t)) throw new NotEnoughBalance()
throw e }
}) feesLRU.del(getFeesKey(a, t))
throw e
})
const LibcoreBridge: WalletBridge<Transaction> = { const LibcoreBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, devicePath) { scanAccountsOnDevice(currency, devicePath) {
@ -169,7 +177,7 @@ const LibcoreBridge: WalletBridge<Transaction> = {
createTransaction: () => ({ createTransaction: () => ({
amount: BigNumber(0), amount: BigNumber(0),
recipient: '', recipient: '',
feePerByte: BigNumber(0), feePerByte: null,
isRBF: false, isRBF: false,
}), }),

19
src/bridge/RippleJSBridge.js

@ -20,13 +20,13 @@ import {
import FeesRippleKind from 'components/FeesField/RippleKind' import FeesRippleKind from 'components/FeesField/RippleKind'
import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind' import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName'
import { NotEnoughBalance } from 'config/errors' import { NotEnoughBalance, FeeNotLoaded } from 'config/errors'
import type { WalletBridge, EditProps } from './types' import type { WalletBridge, EditProps } from './types'
type Transaction = { type Transaction = {
amount: BigNumber, amount: BigNumber,
recipient: string, recipient: string,
fee: BigNumber, fee: ?BigNumber,
tag: ?number, tag: ?number,
} }
@ -51,6 +51,8 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOperationBroadcasted }) { async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOperationBroadcasted }) {
const api = apiForEndpointConfig(a.endpointConfig) const api = apiForEndpointConfig(a.endpointConfig)
const { fee } = t
if (!fee) throw new FeeNotLoaded()
try { try {
await api.connect() await api.connect()
const amount = formatAPICurrencyXRP(t.amount) const amount = formatAPICurrencyXRP(t.amount)
@ -66,7 +68,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
}, },
} }
const instruction = { const instruction = {
fee: formatAPICurrencyXRP(t.fee).value, fee: formatAPICurrencyXRP(fee).value,
maxLedgerVersionOffset: 12, maxLedgerVersionOffset: 12,
} }
@ -97,7 +99,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
accountId: a.id, accountId: a.id,
type: 'OUT', type: 'OUT',
value: t.amount, value: t.amount,
fee: t.fee, fee,
blockHash: null, blockHash: null,
blockHeight: null, blockHeight: null,
senders: [a.freshAddress], senders: [a.freshAddress],
@ -452,7 +454,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
createTransaction: () => ({ createTransaction: () => ({
amount: BigNumber(0), amount: BigNumber(0),
recipient: '', recipient: '',
fee: BigNumber(0), fee: null,
tag: undefined, tag: undefined,
}), }),
@ -495,10 +497,11 @@ const RippleJSBridge: WalletBridge<Transaction> = {
getTransactionRecipient: (a, t) => t.recipient, getTransactionRecipient: (a, t) => t.recipient,
checkValidTransaction: async (a, t) => { checkValidTransaction: async (a, t) => {
if (!t.fee) throw new FeeNotLoaded()
const r = await getServerInfo(a.endpointConfig) const r = await getServerInfo(a.endpointConfig)
if ( if (
t.amount t.amount
.plus(t.fee) .plus(t.fee || 0)
.plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) .plus(parseAPIValue(r.validatedLedger.reserveBaseXRP))
.isLessThanOrEqualTo(a.balance) .isLessThanOrEqualTo(a.balance)
) { ) {
@ -507,9 +510,9 @@ const RippleJSBridge: WalletBridge<Transaction> = {
throw new NotEnoughBalance() throw new NotEnoughBalance()
}, },
getTotalSpent: (a, t) => Promise.resolve(t.amount.plus(t.fee)), getTotalSpent: (a, t) => Promise.resolve(t.amount.plus(t.fee || 0)),
getMaxAmount: (a, t) => Promise.resolve(a.balance.minus(t.fee)), getMaxAmount: (a, t) => Promise.resolve(a.balance.minus(t.fee || 0)),
signAndBroadcast: (a, t, deviceId) => signAndBroadcast: (a, t, deviceId) =>
Observable.create(o => { Observable.create(o => {

17
src/components/FeesField/BitcoinKind.js

@ -8,6 +8,7 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common' import type { T } from 'types/common'
import { FeeNotLoaded } from 'config/errors'
import InputCurrency from 'components/base/InputCurrency' import InputCurrency from 'components/base/InputCurrency'
import Select from 'components/base/Select' import Select from 'components/base/Select'
import type { Fees } from 'api/Fees' import type { Fees } from 'api/Fees'
@ -17,7 +18,7 @@ import Box from '../base/Box'
type Props = { type Props = {
account: Account, account: Account,
feePerByte: BigNumber, feePerByte: ?BigNumber,
onChange: BigNumber => void, onChange: BigNumber => void,
t: T, t: T,
} }
@ -81,16 +82,18 @@ class FeesField extends Component<OwnProps, State> {
items = items.sort((a, b) => a.blockCount - b.blockCount) items = items.sort((a, b) => a.blockCount - b.blockCount)
} }
items.push(customItem) items.push(customItem)
const selectedItem = prevState.selectedItem.feePerByte.eq(feePerByte) const selectedItem = !feePerByte
? prevState.selectedItem ? customItem
: items.find(f => f.feePerByte.eq(feePerByte)) || items[items.length - 1] : prevState.selectedItem.feePerByte.eq(feePerByte)
? prevState.selectedItem
: items.find(f => f.feePerByte.eq(feePerByte)) || items[items.length - 1]
return { items, selectedItem } return { items, selectedItem }
} }
componentDidUpdate({ fees: prevFees }: OwnProps) { componentDidUpdate({ fees: prevFees }: OwnProps) {
const { feePerByte, fees, onChange } = this.props const { feePerByte, fees, onChange } = this.props
const { items, isFocused } = this.state const { items, isFocused } = this.state
if (fees && fees !== prevFees && feePerByte.isZero() && !isFocused) { if (fees && fees !== prevFees && !feePerByte && !isFocused) {
// initialize with the median // initialize with the median
const feePerByte = (items.find(item => item.blockCount === defaultBlockCount) || items[0]) const feePerByte = (items.find(item => item.blockCount === defaultBlockCount) || items[0])
.feePerByte .feePerByte
@ -127,7 +130,7 @@ class FeesField extends Component<OwnProps, State> {
const satoshi = units[units.length - 1] const satoshi = units[units.length - 1]
return ( return (
<GenericContainer error={error}> <GenericContainer>
<Select width={156} options={items} value={selectedItem} onChange={this.onSelectChange} /> <Select width={156} options={items} value={selectedItem} onChange={this.onSelectChange} />
<InputCurrency <InputCurrency
ref={this.input} ref={this.input}
@ -137,6 +140,8 @@ class FeesField extends Component<OwnProps, State> {
value={feePerByte} value={feePerByte}
onChange={onChange} onChange={onChange}
onChangeFocus={this.onChangeFocus} onChangeFocus={this.onChangeFocus}
loading={!feePerByte && !error}
error={!feePerByte && error ? new FeeNotLoaded() : null}
renderRight={ renderRight={
<InputRight> <InputRight>
{t('app:send.steps.amount.unitPerByte', { unit: satoshi.code })} {t('app:send.steps.amount.unitPerByte', { unit: satoshi.code })}

9
src/components/FeesField/EthereumKind.js

@ -4,6 +4,7 @@ import React, { Component } from 'react'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import { FeeNotLoaded } from 'config/errors'
import InputCurrency from 'components/base/InputCurrency' import InputCurrency from 'components/base/InputCurrency'
import type { Fees } from 'api/Fees' import type { Fees } from 'api/Fees'
import WithFeesAPI from '../WithFeesAPI' import WithFeesAPI from '../WithFeesAPI'
@ -11,7 +12,7 @@ import GenericContainer from './GenericContainer'
type Props = { type Props = {
account: Account, account: Account,
gasPrice: BigNumber, gasPrice: ?BigNumber,
onChange: BigNumber => void, onChange: BigNumber => void,
} }
@ -22,7 +23,7 @@ class FeesField extends Component<Props & { fees?: Fees, error?: Error }, *> {
componentDidUpdate() { componentDidUpdate() {
const { gasPrice, fees, onChange } = this.props const { gasPrice, fees, onChange } = this.props
const { isFocused } = this.state const { isFocused } = this.state
if (gasPrice.isZero() && fees && fees.gas_price && !isFocused) { if (!gasPrice && fees && fees.gas_price && !isFocused) {
onChange(BigNumber(fees.gas_price)) // we want to set the default to gas_price onChange(BigNumber(fees.gas_price)) // we want to set the default to gas_price
} }
} }
@ -33,12 +34,14 @@ class FeesField extends Component<Props & { fees?: Fees, error?: Error }, *> {
const { account, gasPrice, error, onChange } = this.props const { account, gasPrice, error, onChange } = this.props
const { units } = account.currency const { units } = account.currency
return ( return (
<GenericContainer error={error}> <GenericContainer>
<InputCurrency <InputCurrency
defaultUnit={units.length > 1 ? units[1] : units[0]} defaultUnit={units.length > 1 ? units[1] : units[0]}
units={units} units={units}
containerProps={{ grow: true }} containerProps={{ grow: true }}
value={gasPrice} value={gasPrice}
loading={!error && !gasPrice}
error={!gasPrice && error ? new FeeNotLoaded() : null}
onChange={onChange} onChange={onChange}
onChangeFocus={this.onChangeFocus} onChangeFocus={this.onChangeFocus}
/> />

9
src/components/FeesField/RippleKind.js

@ -4,12 +4,13 @@ import React, { Component } from 'react'
import type { BigNumber } from 'bignumber.js' import type { BigNumber } from 'bignumber.js'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import { apiForEndpointConfig, parseAPIValue } from 'api/Ripple' import { apiForEndpointConfig, parseAPIValue } from 'api/Ripple'
import { FeeNotLoaded } from 'config/errors'
import InputCurrency from 'components/base/InputCurrency' import InputCurrency from 'components/base/InputCurrency'
import GenericContainer from './GenericContainer' import GenericContainer from './GenericContainer'
type Props = { type Props = {
account: Account, account: Account,
fee: BigNumber, fee: ?BigNumber,
onChange: BigNumber => void, onChange: BigNumber => void,
} }
@ -36,7 +37,7 @@ class FeesField extends Component<Props, State> {
const info = await api.getServerInfo() const info = await api.getServerInfo()
if (syncId !== this.syncId) return if (syncId !== this.syncId) return
const serverFee = parseAPIValue(info.validatedLedger.baseFeeXRP) const serverFee = parseAPIValue(info.validatedLedger.baseFeeXRP)
if (this.props.fee.isZero()) { if (!this.props.fee) {
this.props.onChange(serverFee) this.props.onChange(serverFee)
} }
} catch (error) { } catch (error) {
@ -50,11 +51,13 @@ class FeesField extends Component<Props, State> {
const { error } = this.state const { error } = this.state
const { units } = account.currency const { units } = account.currency
return ( return (
<GenericContainer error={error}> <GenericContainer>
<InputCurrency <InputCurrency
defaultUnit={units[0]} defaultUnit={units[0]}
units={units} units={units}
containerProps={{ grow: true }} containerProps={{ grow: true }}
loading={!error && !fee}
error={!fee && error ? new FeeNotLoaded() : null}
value={fee} value={fee}
onChange={onChange} onChange={onChange}
/> />

23
src/components/base/Input/index.js

@ -4,9 +4,8 @@ import React, { PureComponent } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { fontSize, space } from 'styled-system' import { fontSize, space } from 'styled-system'
import noop from 'lodash/noop' import noop from 'lodash/noop'
import fontFamily from 'styles/styled/fontFamily' import fontFamily from 'styles/styled/fontFamily'
import Spinner from 'components/base/Spinner'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import TranslatedError from 'components/TranslatedError' import TranslatedError from 'components/TranslatedError'
@ -44,6 +43,19 @@ const ErrorDisplay = styled(Box)`
color: ${p => p.theme.colors.pearl}; color: ${p => p.theme.colors.pearl};
` `
const LoadingDisplay = styled(Box)`
position: absolute;
left: 0px;
top: 0px;
bottom: 0px;
background: white;
pointer-events: none;
flex-direction: row;
align-items: center;
padding: 0 15px;
border-radius: 4px;
`
const WarningDisplay = styled(ErrorDisplay)` const WarningDisplay = styled(ErrorDisplay)`
color: ${p => p.theme.colors.warning}; color: ${p => p.theme.colors.warning};
` `
@ -98,6 +110,7 @@ type Props = {
renderLeft?: any, renderLeft?: any,
renderRight?: any, renderRight?: any,
containerProps?: Object, containerProps?: Object,
loading?: boolean,
error?: ?Error | boolean, error?: ?Error | boolean,
warning?: ?Error | boolean, warning?: ?Error | boolean,
small?: boolean, small?: boolean,
@ -182,6 +195,7 @@ class Input extends PureComponent<Props, State> {
editInPlace, editInPlace,
small, small,
error, error,
loading,
warning, warning,
...props ...props
} = this.props } = this.props
@ -217,6 +231,11 @@ class Input extends PureComponent<Props, State> {
<TranslatedError error={warning} /> <TranslatedError error={warning} />
</WarningDisplay> </WarningDisplay>
) : null} ) : null}
{loading && !isFocus ? (
<LoadingDisplay>
<Spinner size={16} />
</LoadingDisplay>
) : null}
</Box> </Box>
{renderRight} {renderRight}
</Container> </Container>

23
src/components/base/InputCurrency/index.js

@ -81,7 +81,7 @@ type Props = {
renderRight: any, renderRight: any,
unit: Unit, unit: Unit,
units: Unit[], units: Unit[],
value: BigNumber, value: ?BigNumber,
showAllDigits?: boolean, showAllDigits?: boolean,
subMagnitude: number, subMagnitude: number,
allowZero: boolean, allowZero: boolean,
@ -98,7 +98,7 @@ class InputCurrency extends PureComponent<Props, State> {
onChange: noop, onChange: noop,
renderRight: null, renderRight: null,
units: [], units: [],
value: BigNumber(0), value: null,
showAllDigits: false, showAllDigits: false,
subMagnitude: 0, subMagnitude: 0,
allowZero: false, allowZero: false,
@ -123,13 +123,14 @@ class InputCurrency extends PureComponent<Props, State> {
if (needsToBeReformatted) { if (needsToBeReformatted) {
const { isFocused } = this.state const { isFocused } = this.state
this.setState({ this.setState({
displayValue: nextProps.value.isZero() displayValue:
? '' !nextProps.value || nextProps.value.isZero()
: format(nextProps.unit, nextProps.value, { ? ''
isFocused, : format(nextProps.unit, nextProps.value, {
showAllDigits: nextProps.showAllDigits, isFocused,
subMagnitude: nextProps.subMagnitude, showAllDigits: nextProps.showAllDigits,
}), subMagnitude: nextProps.subMagnitude,
}),
}) })
} }
} }
@ -138,7 +139,7 @@ class InputCurrency extends PureComponent<Props, State> {
const { onChange, unit, value } = this.props const { onChange, unit, value } = this.props
const r = sanitizeValueString(unit, v) const r = sanitizeValueString(unit, v)
const satoshiValue = BigNumber(r.value) const satoshiValue = BigNumber(r.value)
if (!value.isEqualTo(satoshiValue)) { if (!value || !value.isEqualTo(satoshiValue)) {
onChange(satoshiValue, unit) onChange(satoshiValue, unit)
} }
this.setState({ displayValue: r.display }) this.setState({ displayValue: r.display })
@ -159,7 +160,7 @@ class InputCurrency extends PureComponent<Props, State> {
this.setState({ this.setState({
isFocused, isFocused,
displayValue: displayValue:
(!value || value.isZero()) && !allowZero !value || (value.isZero() && !allowZero)
? '' ? ''
: format(unit, value, { isFocused, showAllDigits, subMagnitude }), : format(unit, value, { isFocused, showAllDigits, subMagnitude }),
}) })

9
src/components/modals/Send/fields/AmountField.js

@ -4,6 +4,9 @@ import Box from 'components/base/Box'
import Label from 'components/base/Label' import Label from 'components/base/Label'
import RequestAmount from 'components/RequestAmount' import RequestAmount from 'components/RequestAmount'
// list of errors that are handled somewhere else on UI, otherwise the field will catch every other errors.
const blacklistErrorName = ['FeeNotLoaded', 'InvalidAddress']
class AmountField extends Component<*, { validTransactionError: ?Error }> { class AmountField extends Component<*, { validTransactionError: ?Error }> {
state = { state = {
validTransactionError: null, validTransactionError: null,
@ -49,7 +52,11 @@ class AmountField extends Component<*, { validTransactionError: ?Error }> {
<RequestAmount <RequestAmount
withMax={false} withMax={false}
account={account} account={account}
validTransactionError={validTransactionError} validTransactionError={
validTransactionError && blacklistErrorName.includes(validTransactionError.name)
? null
: validTransactionError
}
onChange={this.onChange} onChange={this.onChange}
value={bridge.getTransactionAmount(account, transaction)} value={bridge.getTransactionAmount(account, transaction)}
/> />

1
src/config/errors.js

@ -42,6 +42,7 @@ export const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnec
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount') export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
export const ETHAddressNonEIP = createCustomErrorClass('ETHAddressNonEIP') export const ETHAddressNonEIP = createCustomErrorClass('ETHAddressNonEIP')
export const CantScanQRCode = createCustomErrorClass('CantScanQRCode') export const CantScanQRCode = createCustomErrorClass('CantScanQRCode')
export const FeeNotLoaded = createCustomErrorClass('FeeNotLoaded')
// db stuff, no need to translate // db stuff, no need to translate
export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven') export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven')

3
static/i18n/en/errors.json

@ -172,5 +172,8 @@
}, },
"CantScanQRCode": { "CantScanQRCode": {
"title": "Couldn't scan this QR-code: auto-verification not supported by this address" "title": "Couldn't scan this QR-code: auto-verification not supported by this address"
},
"FeeNotLoaded": {
"title": "Couldn't load default fees. Please carefully set."
} }
} }

Loading…
Cancel
Save