diff --git a/__tests__/unit/components/Input/InputFee.spec.js b/__tests__/unit/components/Input/InputFee.spec.js index cdc6d5f283..080636a500 100644 --- a/__tests__/unit/components/Input/InputFee.spec.js +++ b/__tests__/unit/components/Input/InputFee.spec.js @@ -275,27 +275,33 @@ describe('InputFee', () => { }) describe('prepareFeeStatistics', () => { - describe('when the average fee of the network is more than the V1 fee', () => { + describe('when any fee of the network is more than the V1 max fee', () => { beforeEach(() => { mockNetwork.feeStatistics = [{ type: 0, fees: { - avgFee: 1000 * 1e8, - maxFee: 0.03 * 1e8, - minFee: 0.0006 * 1e8 + avgFee: 900 * 1e8, + maxFee: 1000 * 1e8, + minFee: 800 * 1e8 } }] }) - it('should use the V1 fee as average always', () => { + it('should use the V1 max fee', () => { const wrapper = mountComponent() + const maxV1fee = new BigNumber(wrapper.vm.maxV1fee * 1e-8) + + expect(wrapper.vm.feeChoices.MAXIMUM).toBeInstanceOf(BigNumber) + expect(wrapper.vm.feeChoices.MAXIMUM).toEqual(maxV1fee) expect(wrapper.vm.feeChoices.AVERAGE).toBeInstanceOf(BigNumber) - expect(wrapper.vm.feeChoices.AVERAGE.toString()).toEqual('0.1') + expect(wrapper.vm.feeChoices.AVERAGE).toEqual(maxV1fee) + expect(wrapper.vm.feeChoices.MINIMUM).toBeInstanceOf(BigNumber) + expect(wrapper.vm.feeChoices.MINIMUM).toEqual(maxV1fee) }) }) - describe('when the average fee of the network is less than the V1 fee', () => { + describe('when any fee of the network is less than the V1 max fee', () => { beforeEach(() => { mockNetwork.feeStatistics = [{ type: 0, @@ -307,64 +313,37 @@ describe('InputFee', () => { }] }) - it('should use it as average', () => { + it('should use it as the fee', () => { const wrapper = mountComponent() + expect(wrapper.vm.feeChoices.MAXIMUM).toBeInstanceOf(BigNumber) + expect(wrapper.vm.feeChoices.MAXIMUM).toBeWithin(0.03, 0.03000001) expect(wrapper.vm.feeChoices.AVERAGE).toBeInstanceOf(BigNumber) - expect(wrapper.vm.feeChoices.AVERAGE).toBeWithin(0.0048, 0.0048000001) + expect(wrapper.vm.feeChoices.AVERAGE).toBeWithin(0.0048, 0.00480001) + expect(wrapper.vm.feeChoices.MINIMUM).toBeInstanceOf(BigNumber) + expect(wrapper.vm.feeChoices.MINIMUM).toBeWithin(0.0006, 0.00060001) }) }) - describe('when the maximum fee of the network is more than the V1 fee', () => { + describe('when the network returns no statistics', () => { beforeEach(() => { - mockNetwork.feeStatistics = [{ - type: 0, - fees: { - avgFee: 0.0048 * 1e8, - maxFee: 1000 * 1e8, - minFee: 0.0006 * 1e8 - } - }] + mockNetwork.feeStatistics = [] }) - it('should use the V1 fee as maximum always', () => { + it('should use V1 max fee for maximum', () => { const wrapper = mountComponent() - expect(wrapper.vm.feeChoices.MAXIMUM).toBeInstanceOf(BigNumber) - expect(wrapper.vm.feeChoices.MAXIMUM.toString()).toEqual('0.1') - }) - }) - - describe('when the maximum fee of the network is less than the V1 fee', () => { - beforeEach(() => { - mockNetwork.feeStatistics = [{ - type: 0, - fees: { - avgFee: 0.0048 * 1e8, - maxFee: 0.03 * 1e8, - minFee: 0.0006 * 1e8 - } - }] - }) - - it('should use it as maximum', () => { - const wrapper = mountComponent() + const maxV1fee = (wrapper.vm.maxV1fee * 1e-8).toString() expect(wrapper.vm.feeChoices.MAXIMUM).toBeInstanceOf(BigNumber) - expect(wrapper.vm.feeChoices.MAXIMUM).toBeWithin(0.03, 0.03000001) - }) - }) - - describe('when the network returns no statistics', () => { - beforeEach(() => { - mockNetwork.feeStatistics = [] + expect(wrapper.vm.feeChoices.MAXIMUM.toString()).toBe(maxV1fee) }) - it('should use it as maximum', () => { + it('should use the absolute minimum fee (0.00000001) for minimum', () => { const wrapper = mountComponent() - expect(wrapper.vm.feeChoices.MAXIMUM).toBeInstanceOf(BigNumber) - expect(wrapper.vm.feeChoices.MAXIMUM.toString()).toBe('0.1') + expect(wrapper.vm.feeChoices.MINIMUM).toBeInstanceOf(BigNumber) + expect(wrapper.vm.feeChoices.MINIMUM.toString()).toBe('0.00000001') }) }) }) diff --git a/src/renderer/components/Input/InputFee.vue b/src/renderer/components/Input/InputFee.vue index 898b88e3ff..c325dfa54a 100644 --- a/src/renderer/components/Input/InputFee.vue +++ b/src/renderer/components/Input/InputFee.vue @@ -191,28 +191,31 @@ export default { return { avgFee: this.maxV1fee, - maxFee: this.maxV1fee + maxFee: this.maxV1fee, + minFee: 1 } }, lastFee () { return this.$store.getters['session/lastFeeByType'](this.transactionType, this.transactionGroup) }, feeChoiceMin () { - return this.feeChoices.MINIMUM + return this.currency_subToUnit(1) }, feeChoiceMax () { return this.isAdvancedFee ? this.feeChoices.MAXIMUM.multipliedBy(10) : this.feeChoices.MAXIMUM }, feeChoices () { - const { avgFee, maxFee } = this.feeStatistics + const { avgFee, maxFee, minFee } = this.feeStatistics - // Even if the network provides average or maximum fees higher than V1, they will be corrected + // If any of the fees are higher than the maximum V1 fee, than use the maximum. const average = this.currency_subToUnit(avgFee < this.maxV1fee ? avgFee : this.maxV1fee) + const minimum = this.currency_subToUnit(minFee < this.maxV1fee ? minFee : this.maxV1fee) + const maximum = this.currency_subToUnit(maxFee < this.maxV1fee ? maxFee : this.maxV1fee) const fees = { - MINIMUM: this.currency_subToUnit(1), + MINIMUM: minimum, AVERAGE: average, - MAXIMUM: this.currency_subToUnit(maxFee < this.maxV1fee ? maxFee : this.maxV1fee), + MAXIMUM: maximum, INPUT: average, ADVANCED: average } @@ -220,7 +223,7 @@ export default { return this.lastFee ? Object.assign({}, { LAST: this.currency_subToUnit(this.lastFee) }, fees) : fees }, minimumError () { - const min = this.feeChoices.MINIMUM + const min = this.feeChoiceMin const fee = this.currency_format(min, { currency: this.currency, currencyDisplay: 'code' }) return this.$t('INPUT_FEE.ERROR.LESS_THAN_MINIMUM', { fee }) }, diff --git a/src/renderer/services/client.js b/src/renderer/services/client.js index e9d87abbb2..5bf24b3185 100644 --- a/src/renderer/services/client.js +++ b/src/renderer/services/client.js @@ -7,6 +7,7 @@ import TransactionService from '@/services/transaction' import { TransactionBuilderService } from './crypto/transaction-builder.service' import { TransactionSigner } from './crypto/transaction-signer' import BigNumber from '@/plugins/bignumber' +import { cammelToUpperSnake } from '@/utils' export default class ClientService { /** @@ -73,43 +74,71 @@ export default class ClientService { } static async fetchFeeStatistics (server, timeout) { + let data + try { - const { body } = await ClientService.newConnection(server, timeout) + const response = await ClientService.newConnection(server, timeout) .api('node') .fees(7) - if (!body.data[0]) { - return Object.values(TRANSACTION_GROUPS) - .filter(group => !!body.data[group]) - .reduce((accumulator, group) => { - accumulator[group] = Object.keys(body.data[group]).map(key => { - const fee = body.data[group][key] - - return { - type: TRANSACTION_TYPES[`GROUP_${group}`][key.toUpperCase()], - fees: { - minFee: Number(fee.min), - maxFee: Number(fee.max), - avgFee: Number(fee.avg) - } - } - }) - - return accumulator - }, {}) - } - - return body.data.map(fee => ({ - type: Number(fee.type), - fees: { - minFee: Number(fee.min), - maxFee: Number(fee.max), - avgFee: Number(fee.avg) - } - })) + data = response.body.data } catch (error) { return [] } + + /* + The peer can send 2 types of response: an Array and and Object. + In case it sends an Object, the fees should be parsed according to the + transaction groups and types from @config. + */ + + // Case it is an Object + if (!Array.isArray(data)) { + // Remove the groups that are not in the response + const groupsIds = Object.values(TRANSACTION_GROUPS).filter(groupId => !!data[groupId]) + + const parsedFees = groupsIds.reduce((accumulator, groupId) => { + const retrivedTypeNames = Object.keys(data[groupId]) + + // Parse the fees and add to accumulator + accumulator[groupId] = retrivedTypeNames.map(typeName => { + const fees = data[groupId][typeName] + + /* + Notice that the types are in different format. + Response is in cammelCase. Eg: 'bussinesUpdate' + @config is in UPPER_SNAKE_CASE. Eg: 'BUSSINES_UPDATE' + */ + const groupName = `GROUP_${groupId}` + const parsedTypeName = cammelToUpperSnake(typeName) + + const type = TRANSACTION_TYPES[groupName][parsedTypeName] + + return { + type, + fees: { + minFee: Number(fees.min), + maxFee: Number(fees.max), + avgFee: Number(fees.avg) + } + } + }) + + return accumulator + }, {}) + + return parsedFees + } + + // Case the response is an Array + return data.map(fee => ({ + type: Number(fee.type), + fees: { + minFee: Number(fee.min), + maxFee: Number(fee.max), + avgFee: Number(fee.avg) + } + })) } constructor () { diff --git a/src/renderer/utils/index.js b/src/renderer/utils/index.js index 25117c8b73..d507fffd04 100644 --- a/src/renderer/utils/index.js +++ b/src/renderer/utils/index.js @@ -43,11 +43,19 @@ const max = nums => { } } +/** + * Converts cammelCaseString to UPPER_CAMMEL_CASE_STRING. + * @param {string} string The cammelCaseString + * @returns {string} The UPPER_CAMMEL_CASE. + */ +const cammelToUpperSnake = string => string.split(/(?=[A-Z])/).join('_').toUpperCase() + export { upperFirst, capitalize, isNil, min, max, - sortByProps + sortByProps, + cammelToUpperSnake }