diff --git a/package.json b/package.json index be3405670..d331a46df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-im", - "version": "2.7.0", + "version": "2.8.0", "private": true, "scripts": { "serve": "vue-cli-service serve", diff --git a/src/components/BuyTokensDialog.vue b/src/components/BuyTokensDialog.vue index 4f2afeb92..093f525a2 100644 --- a/src/components/BuyTokensDialog.vue +++ b/src/components/BuyTokensDialog.vue @@ -31,7 +31,7 @@ - + @@ -40,6 +40,16 @@ CoinDeal + + + + + + + + Atomars + + @@ -52,6 +62,7 @@ import validateAddress from '@/lib/validateAddress' import Icon from '@/components/icons/BaseIcon' import CryptoIcon from '@/components/icons/CryptoIcon' import CdlIcon from '@/components/icons/common/Cdl' +import AtomarsIcon from '@/components/icons/common/Atomars' export default { computed: { @@ -94,7 +105,8 @@ export default { components: { Icon, CryptoIcon, - CdlIcon + CdlIcon, + AtomarsIcon }, props: { value: { diff --git a/src/components/SendFundsForm.vue b/src/components/SendFundsForm.vue index eedfd0c47..b67a6f0f9 100644 --- a/src/components/SendFundsForm.vue +++ b/src/components/SendFundsForm.vue @@ -79,7 +79,7 @@ @@ -174,10 +174,11 @@ import QrcodeCapture from '@/components/QrcodeCapture' import QrcodeScannerDialog from '@/components/QrcodeScannerDialog' import get from 'lodash/get' import { BigNumber } from 'bignumber.js' +import { INCREASE_FEE_MULTIPLIER } from '../lib/constants' import { parseURI } from '@/lib/uri' import { sendMessage } from '@/lib/adamant-api' -import { Cryptos, CryptoAmountPrecision, CryptoNaturalUnits, TransactionStatus as TS, isErc20, getMinAmount } from '@/lib/constants' +import { Cryptos, CryptoAmountPrecision, CryptoNaturalUnits, TransactionStatus as TS, isErc20, isFeeEstimate, isEthBased, getMinAmount } from '@/lib/constants' import validateAddress from '@/lib/validateAddress' import { formatNumber, isNumeric } from '@/lib/numericHelpers' import partnerName from '@/mixins/partnerName' @@ -238,6 +239,14 @@ export default { return BigNumber(this.transferFee).toFixed() }, + /** + * Label for fee field. Estimate fee or precise value. + * @returns {string} + */ + transferFeeLabel () { + return isFeeEstimate(this.currency) ? this.$t('transfer.commission_estimate_label') : this.$t('transfer.commission_label') + }, + /** * Transfer currency (may differ from the amount currency in case of ERC-20 tokens) * @returns {string} @@ -369,7 +378,7 @@ export default { } }, allowIncreaseFee () { - return this.currency === Cryptos.BTC + return (this.currency === Cryptos.BTC) || isEthBased(this.currency) } }, watch: { @@ -423,7 +432,7 @@ export default { } else { this.$store.dispatch('snackbar/show', { message: abstract, - timeout: 3000 + timeout: 5000 }) } }, @@ -562,7 +571,8 @@ export default { admAddress: this.address, address: this.cryptoAddress, comments: this.comment, - fee: this.transferFee + fee: this.transferFee, + increaseFee: this.increaseFee }) } }, @@ -620,7 +630,7 @@ export default { return right.length <= units }, calculateTransferFee (amount) { - const coef = this.increaseFee ? 2 : 1 + const coef = this.increaseFee ? INCREASE_FEE_MULTIPLIER : 1 return coef * this.$store.getters[`${this.currency.toLowerCase()}/fee`](amount) } }, diff --git a/src/components/icons/common/Atomars.vue b/src/components/icons/common/Atomars.vue new file mode 100644 index 000000000..e5e7b6bbc --- /dev/null +++ b/src/components/icons/common/Atomars.vue @@ -0,0 +1,5 @@ + diff --git a/src/i18n/en.json b/src/i18n/en.json index 4ae8ad174..2fb1ae588 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -230,6 +230,7 @@ "balance": "Balance", "comments_label": "Comment", "commission_label": "Transfer fee", + "commission_estimate_label": "Estimated transfer fee", "confirm_approve": "Confirm", "confirm_cancel": "Cancel", "confirm_message": "Confirm transfer of {amount} {crypto} to address {address}.", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index d6d37bf54..288a0bd20 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -232,6 +232,7 @@ "balance": "Баланс", "comments_label": "Комментарий", "commission_label": "Комиссия за перевод", + "commission_estimate_label": "Приблизительная комиссия за перевод", "confirm_approve": "Подтвердить", "confirm_cancel": "Отмена", "confirm_message": "Подтвердите перевод {amount} {crypto} на адрес {address}.", diff --git a/src/lib/constants.js b/src/lib/constants.js index 6bd912f2a..eb74de226 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -55,6 +55,10 @@ export const BTC_BASED = Object.freeze([ export const isErc20 = crypto => ERC20.includes(crypto) +export const isEthBased = crypto => isErc20(crypto) || crypto === Cryptos.ETH + +export const isFeeEstimate = crypto => isEthBased(crypto) + export const isBtcBased = crypto => BTC_BASED.includes(crypto) /** Number of decimal places for the different crypto amounts */ @@ -88,7 +92,7 @@ export const CryptoNaturalUnits = { export const Fees = { /** Storing a value into the KVS */ KVS: 0.001, - /** Transfering tokens */ + /** Transferring tokens */ ADM_TRANSFER: 0.5, NOT_ADM_TRANSFER: 0.001 } @@ -117,11 +121,22 @@ export const WelcomeMessage = { ADAMANT_ICO: 'ADAMANT Tokens' } -/** Gas value for the ETH transfers */ -export const ETH_TRANSFER_GAS = 21000 +/** + * These gas limit values are used only for estimate fees for ETH & ERC-20 transfers in the Send tokens form, + * Actual gas limit values are calculated with estimateGas(transactionObject) + * when each specific transaction is created + */ + +/** Gas limit value for the ETH transfers */ +export const ETH_TRANSFER_GAS = 22000 // Default gas limit; while to be calculated with estimateGas(transactionObject) +/** Gas limit value for the ERC-20 transfers */ +export const ERC20_TRANSFER_GAS = ETH_TRANSFER_GAS * 2 + +/** Gas price multiplier. To be sure a transaction will be confirmed */ +export const ETH_GASPRICE_MULTIPLIER = 1.1 -/** Gas value for the ERC-20 transfers */ -export const ERC20_TRANSFER_GAS = ETH_TRANSFER_GAS * 10 +/** Increase fee multiplier. Used in SendFundsForm */ +export const INCREASE_FEE_MULTIPLIER = 2 export default { EPOCH, diff --git a/src/lib/textHelpers.js b/src/lib/textHelpers.js index eccbd86fe..f8d2d0163 100644 --- a/src/lib/textHelpers.js +++ b/src/lib/textHelpers.js @@ -6,13 +6,37 @@ export function copyToClipboard (data) { let el = document.createElement('textarea') el.value = data - document.body.appendChild(el) - el.select() - document.execCommand('copy') + + var isiOSDevice = navigator.userAgent.match(/ipad|iphone/i) + if (isiOSDevice) { + copyToClipboardIos(el) + } else { + el.select() + document.execCommand('copy') + } document.body.removeChild(el) } +/** + * Copy to clipboard helper for iOS devices + * + * @param {HTMLTextAreaElement} el + */ +function copyToClipboardIos (el) { + var range = document.createRange() + el.contentEditable = true + el.readOnly = false + range.selectNodeContents(el) + + var s = window.getSelection() + s.removeAllRanges() + s.addRange(range) + el.setSelectionRange(0, 999999) // A big number, to cover anything that could be inside the element. + + document.execCommand('copy') +} + /** * Download file helper. * diff --git a/src/store/modules/erc20/erc20-actions.js b/src/store/modules/erc20/erc20-actions.js index 8d0834716..271fe3784 100644 --- a/src/store/modules/erc20/erc20-actions.js +++ b/src/store/modules/erc20/erc20-actions.js @@ -1,7 +1,7 @@ import abiDecoder from 'abi-decoder' import * as ethUtils from '../../../lib/eth-utils' -import { ERC20_TRANSFER_GAS } from '../../../lib/constants' +import { INCREASE_FEE_MULTIPLIER } from '../../../lib/constants' import Erc20 from './erc20.abi.json' import createActions from '../eth-base/eth-base-actions' @@ -13,17 +13,23 @@ const STATUS_INTERVAL = 8000 // Setup decoder abiDecoder.addABI(Erc20) -const initTransaction = (api, context, ethAddress, amount) => { +const initTransaction = (api, context, ethAddress, amount, increaseFee) => { const contract = api.eth.contract(Erc20).at(context.state.contractAddress) - return { + const transaction = { from: context.state.address, to: context.state.contractAddress, value: api.fromDecimal('0'), - gasLimit: api.fromDecimal(ERC20_TRANSFER_GAS), + // gasLimit: api.fromDecimal(ERC20_TRANSFER_GAS), // Don't take default value, instead calculate with estimateGas(transactionObject) gasPrice: api.fromDecimal(context.getters.gasPrice), data: contract.transfer.getData(ethAddress, ethUtils.toWhole(amount, context.state.decimals)) } + + let gasLimit = api.eth.estimateGas(transaction) + gasLimit = increaseFee ? (gasLimit * INCREASE_FEE_MULTIPLIER).toString(16) : gasLimit.toString(16) + transaction.gas = '0x' + gasLimit + + return transaction } const parseTransaction = (context, tx) => { diff --git a/src/store/modules/eth-base/eth-base-actions.js b/src/store/modules/eth-base/eth-base-actions.js index 94ee48df3..0074186d9 100644 --- a/src/store/modules/eth-base/eth-base-actions.js +++ b/src/store/modules/eth-base/eth-base-actions.js @@ -69,10 +69,10 @@ export default function createActions (config) { } }, - sendTokens (context, { amount, admAddress, address, comments }) { + sendTokens (context, { amount, admAddress, address, comments, increaseFee }) { address = address.trim() const crypto = context.state.crypto - const ethTx = initTransaction(api, context, address, amount) + const ethTx = initTransaction(api, context, address, amount, increaseFee) return utils.promisify(api.eth.getTransactionCount, context.state.address, 'pending') .then(count => { diff --git a/src/store/modules/eth/actions.js b/src/store/modules/eth/actions.js index 8f28075a1..a6edb2ce8 100644 --- a/src/store/modules/eth/actions.js +++ b/src/store/modules/eth/actions.js @@ -1,7 +1,7 @@ import * as utils from '../../../lib/eth-utils' import createActions from '../eth-base/eth-base-actions' -import { ETH_TRANSFER_GAS } from '../../../lib/constants' +import { ETH_TRANSFER_GAS, ETH_GASPRICE_MULTIPLIER, INCREASE_FEE_MULTIPLIER } from '../../../lib/constants' import { storeCryptoAddress } from '../../../lib/store-crypto-address' /** Timestamp of the most recent status update */ @@ -17,15 +17,19 @@ function storeEthAddress (context) { storeCryptoAddress(context.state.crypto, context.state.address) } -const initTransaction = (api, context, ethAddress, amount) => { +const initTransaction = (api, context, ethAddress, amount, increaseFee) => { const transaction = { from: context.state.address, to: ethAddress, value: api.fromDecimal(utils.toWei(amount)), - // gas: api.fromDecimal(ETH_TRANSFER_GAS), + // gas: api.fromDecimal(ETH_TRANSFER_GAS), // Don't take default value, instead calculate with estimateGas(transactionObject) gasPrice: api.fromDecimal(context.getters.gasPrice) } - transaction.gas = '0x' + api.eth.estimateGas(transaction).toString(16) + + let gasLimit = api.eth.estimateGas(transaction) + gasLimit = increaseFee ? (gasLimit * INCREASE_FEE_MULTIPLIER).toString(16) : gasLimit.toString(16) + transaction.gas = '0x' + gasLimit + return transaction } @@ -67,10 +71,10 @@ const createSpecificActions = (api, queue) => ({ // Current gas price api.eth.getGasPrice.request((err, price) => { if (!err) { - const gasPrice = 3 * price.toNumber() + const gasPrice = Math.round(ETH_GASPRICE_MULTIPLIER * price.toNumber()) context.commit('gasPrice', { gasPrice, - fee: utils.calculateFee(ETH_TRANSFER_GAS, gasPrice) + fee: +(+utils.calculateFee(ETH_TRANSFER_GAS, gasPrice)).toFixed(7) }) } }),