diff --git a/src/bridge/EthereumJSBridge.js b/src/bridge/EthereumJSBridge.js index 2a48e6cf..69be1675 100644 --- a/src/bridge/EthereumJSBridge.js +++ b/src/bridge/EthereumJSBridge.js @@ -16,20 +16,20 @@ import { getDerivations } from 'helpers/derivations' import getAddressCommand from 'commands/getAddress' import signTransactionCommand from 'commands/signTransaction' 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' type Transaction = { recipient: string, amount: BigNumber, - gasPrice: BigNumber, + gasPrice: ?BigNumber, gasLimit: BigNumber, } const serializeTransaction = t => ({ recipient: t.recipient, 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)}`, }) @@ -140,6 +140,8 @@ const signAndBroadcast = async ({ onSigned, onOperationBroadcasted, }) => { + const { gasPrice, amount, gasLimit } = t + if (!gasPrice) throw new FeeNotLoaded() const api = apiForCurrency(a.currency) const nonce = await api.getAccountNonce(a.freshAddress) @@ -162,8 +164,8 @@ const signAndBroadcast = async ({ id: `${a.id}-${hash}-OUT`, hash, type: 'OUT', - value: t.amount, - fee: t.gasPrice.times(t.gasLimit), + value: amount, + fee: gasPrice.times(gasLimit), blockHeight: null, blockHash: null, accountId: a.id, @@ -402,7 +404,7 @@ const EthereumBridge: WalletBridge = { createTransaction: () => ({ amount: BigNumber(0), recipient: '', - gasPrice: BigNumber(0), + gasPrice: null, gasLimit: BigNumber(0x5208), }), @@ -425,16 +427,22 @@ const EthereumBridge: WalletBridge = { EditAdvancedOptions, checkValidTransaction: (a, t) => - t.amount.isLessThanOrEqualTo(a.balance) - ? Promise.resolve(true) - : Promise.reject(new NotEnoughBalance()), + !t.gasPrice + ? Promise.reject(new FeeNotLoaded()) + : t.amount.isLessThanOrEqualTo(a.balance) + ? Promise.resolve(true) + : Promise.reject(new NotEnoughBalance()), 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(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) => Observable.create(o => { diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 355232e6..fa86c0eb 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -11,7 +11,7 @@ import libcoreSyncAccount from 'commands/libcoreSyncAccount' import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast' import libcoreGetFees, { extractGetFeesInputFromAccount } from 'commands/libcoreGetFees' import libcoreValidAddress from 'commands/libcoreValidAddress' -import { NotEnoughBalance } from 'config/errors' +import { NotEnoughBalance, FeeNotLoaded } from 'config/errors' import type { WalletBridge, EditProps } from './types' const NOT_ENOUGH_FUNDS = 52 @@ -20,15 +20,18 @@ const notImplemented = new Error('LibcoreBridge: not implemented') type Transaction = { amount: BigNumber, - feePerByte: BigNumber, + feePerByte: ?BigNumber, recipient: string, } -const serializeTransaction = t => ({ - recipient: t.recipient, - amount: t.amount.toString(), - feePerByte: t.feePerByte.toString(), -}) +const serializeTransaction = t => { + const { feePerByte } = t + return { + recipient: t.recipient, + amount: t.amount.toString(), + feePerByte: (feePerByte && feePerByte.toString()) || '0', + } +} const decodeOperation = (encodedAccount, rawOp) => decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations[0] @@ -75,7 +78,9 @@ const isRecipientValid = (currency, recipient) => { const feesLRU = LRU({ max: 100 }) 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 isValid = await isRecipientValid(a.currency, transaction.recipient) @@ -83,6 +88,7 @@ const getFees = async (a, transaction) => { const key = getFeesKey(a, transaction) let promise = feesLRU.get(key) if (promise) return promise + promise = libcoreGetFees .send({ ...extractGetFeesInputFromAccount(a), @@ -95,17 +101,19 @@ const getFees = async (a, transaction) => { } const checkValidTransaction = (a, t) => - !t.amount - ? Promise.resolve(true) - : getFees(a, t) - .then(() => true) - .catch(e => { - if (e.code === NOT_ENOUGH_FUNDS) { - throw new NotEnoughBalance() - } - feesLRU.del(getFeesKey(a, t)) - throw e - }) + !t.feePerByte + ? Promise.reject(new FeeNotLoaded()) + : !t.amount + ? Promise.resolve(true) + : getFees(a, t) + .then(() => true) + .catch(e => { + if (e.code === NOT_ENOUGH_FUNDS) { + throw new NotEnoughBalance() + } + feesLRU.del(getFeesKey(a, t)) + throw e + }) const LibcoreBridge: WalletBridge = { scanAccountsOnDevice(currency, devicePath) { @@ -169,7 +177,7 @@ const LibcoreBridge: WalletBridge = { createTransaction: () => ({ amount: BigNumber(0), recipient: '', - feePerByte: BigNumber(0), + feePerByte: null, isRBF: false, }), diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index 952fdaf9..75f6ff04 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -20,13 +20,13 @@ import { import FeesRippleKind from 'components/FeesField/RippleKind' import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName' -import { NotEnoughBalance } from 'config/errors' +import { NotEnoughBalance, FeeNotLoaded } from 'config/errors' import type { WalletBridge, EditProps } from './types' type Transaction = { amount: BigNumber, recipient: string, - fee: BigNumber, + fee: ?BigNumber, tag: ?number, } @@ -51,6 +51,8 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps) => ( async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOperationBroadcasted }) { const api = apiForEndpointConfig(a.endpointConfig) + const { fee } = t + if (!fee) throw new FeeNotLoaded() try { await api.connect() const amount = formatAPICurrencyXRP(t.amount) @@ -66,7 +68,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera }, } const instruction = { - fee: formatAPICurrencyXRP(t.fee).value, + fee: formatAPICurrencyXRP(fee).value, maxLedgerVersionOffset: 12, } @@ -97,7 +99,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera accountId: a.id, type: 'OUT', value: t.amount, - fee: t.fee, + fee, blockHash: null, blockHeight: null, senders: [a.freshAddress], @@ -452,7 +454,7 @@ const RippleJSBridge: WalletBridge = { createTransaction: () => ({ amount: BigNumber(0), recipient: '', - fee: BigNumber(0), + fee: null, tag: undefined, }), @@ -495,10 +497,11 @@ const RippleJSBridge: WalletBridge = { getTransactionRecipient: (a, t) => t.recipient, checkValidTransaction: async (a, t) => { + if (!t.fee) throw new FeeNotLoaded() const r = await getServerInfo(a.endpointConfig) if ( t.amount - .plus(t.fee) + .plus(t.fee || 0) .plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) .isLessThanOrEqualTo(a.balance) ) { @@ -507,9 +510,9 @@ const RippleJSBridge: WalletBridge = { 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) => Observable.create(o => { diff --git a/src/components/FeesField/BitcoinKind.js b/src/components/FeesField/BitcoinKind.js index d2ce95c0..88d992fd 100644 --- a/src/components/FeesField/BitcoinKind.js +++ b/src/components/FeesField/BitcoinKind.js @@ -8,6 +8,7 @@ import { translate } from 'react-i18next' import type { T } from 'types/common' +import { FeeNotLoaded } from 'config/errors' import InputCurrency from 'components/base/InputCurrency' import Select from 'components/base/Select' import type { Fees } from 'api/Fees' @@ -17,7 +18,7 @@ import Box from '../base/Box' type Props = { account: Account, - feePerByte: BigNumber, + feePerByte: ?BigNumber, onChange: BigNumber => void, t: T, } @@ -81,16 +82,18 @@ class FeesField extends Component { items = items.sort((a, b) => a.blockCount - b.blockCount) } items.push(customItem) - const selectedItem = prevState.selectedItem.feePerByte.eq(feePerByte) - ? prevState.selectedItem - : items.find(f => f.feePerByte.eq(feePerByte)) || items[items.length - 1] + const selectedItem = !feePerByte + ? customItem + : prevState.selectedItem.feePerByte.eq(feePerByte) + ? prevState.selectedItem + : items.find(f => f.feePerByte.eq(feePerByte)) || items[items.length - 1] return { items, selectedItem } } componentDidUpdate({ fees: prevFees }: OwnProps) { const { feePerByte, fees, onChange } = this.props const { items, isFocused } = this.state - if (fees && fees !== prevFees && feePerByte.isZero() && !isFocused) { + if (fees && fees !== prevFees && !feePerByte && !isFocused) { // initialize with the median const feePerByte = (items.find(item => item.blockCount === defaultBlockCount) || items[0]) .feePerByte @@ -127,7 +130,7 @@ class FeesField extends Component { const satoshi = units[units.length - 1] return ( - +