diff --git a/config/default.js b/config/default.js index 6acc7138f19..8ce966a9b7d 100644 --- a/config/default.js +++ b/config/default.js @@ -102,7 +102,8 @@ module.exports = { invoices: { expire: 3600, baseRetryDelay: 1000, - retryCount: 3, // Number of retries for pay invoice failure + retryCount: 2, // Number of retries for pay invoice failure + feeIncrementExponent: 1.1, // Exponent applied to fee limit on payment retry attempts }, autopay: { diff --git a/renderer/components/Pay/Pay.js b/renderer/components/Pay/Pay.js index 64518b4d118..10fe6dc6420 100644 --- a/renderer/components/Pay/Pay.js +++ b/renderer/components/Pay/Pay.js @@ -6,7 +6,14 @@ import get from 'lodash/get' import { Box, Flex } from 'rebass' import { animated, Keyframes, Transition } from 'react-spring/renderprops.cjs' import { FormattedMessage, injectIntl, intlShape } from 'react-intl' -import { decodePayReq, getMinFee, getMaxFee, isOnchain, isLn } from '@zap/utils/crypto' +import { + decodePayReq, + getMinFee, + getMaxFee, + getMaxFeeInclusive, + isOnchain, + isLn, +} from '@zap/utils/crypto' import { convert } from '@zap/utils/btc' import { Bar, @@ -559,10 +566,10 @@ class Pay extends React.Component { const formState = this.formApi.getState() const { speed, payReq, isCoinSweep } = formState.values - let minFee, maxFee + let minFee, maxFeeInclusive if (routes.length) { minFee = getMinFee(routes) - maxFee = getMaxFee(routes) + maxFeeInclusive = getMaxFeeInclusive(routes) } const render = () => { // convert entered amount to satoshis @@ -584,7 +591,7 @@ class Pay extends React.Component { return ( */ const handleDecreaseRetries = (state, { paymentRequest }) => { const paymentsSending = state.paymentsSending.map(payment => { - if (payment.paymentRequest === paymentRequest) { - return { ...payment, remainingRetries: payment.remainingRetries - 1 } + if (payment.paymentRequest === paymentRequest && payment.status === PAYMENT_STATUS_SENDING) { + return { + ...payment, + remainingRetries: payment.remainingRetries - 1, + feeLimit: payment.feeLimit + ? Math.ceil(payment.feeLimit * config.invoices.feeIncrementExponent) + : payment.feeLimit, + } } return payment }) @@ -162,7 +172,7 @@ export const sendPayment = data => dispatch => { const payment = { ...data, - status: 'sending', + status: PAYMENT_STATUS_SENDING, isSending: true, creation_date: Math.round(new Date() / 1000), payment_hash: paymentHashTag.data, @@ -308,6 +318,7 @@ export const paymentFailed = ({ payment_request, error }) => async (dispatch, ge const paymentSending = getLastSendingEntry(state, payment_request) // errors that trigger retry mechanism const RETRIABLE_ERRORS = [ + 'payment attempt not completed before timeout', // ErrPaymentAttemptTimeout 'unable to find a path to destination', // ErrNoPathFound 'target not found', // ErrTargetNotInNetwork ] @@ -365,7 +376,7 @@ const ACTION_HANDLERS = { } return { ...item, - status: 'successful', + status: PAYMENT_STATUS_SUCCESSFUL, } }), } @@ -379,7 +390,7 @@ const ACTION_HANDLERS = { } return { ...item, - status: 'failed', + status: PAYMENT_STATUS_FAILED, error, } }), diff --git a/utils/crypto.js b/utils/crypto.js index b0f070e3e39..0b260408615 100644 --- a/utils/crypto.js +++ b/utils/crypto.js @@ -1,4 +1,6 @@ import get from 'lodash/get' +import config from 'config' +import range from 'lodash/range' import { address } from 'bitcoinjs-lib' import lightningRequestReq from 'bolt11' import coininfo from 'coininfo' @@ -172,9 +174,9 @@ export const getNodeAlias = (pubkey, nodes = []) => { } /** - * getMinFee - Given a list of routest, find the minimum fee. + * getMinFee - Given a list of routes, find the minimum fee. * - * @param {*} routes List of routes + * @param {Array} routes List of routes * @returns {number} minimum fee rounded up to the nearest satoshi */ export const getMinFee = (routes = []) => { @@ -188,9 +190,9 @@ export const getMinFee = (routes = []) => { } /** - * getMaxFee - Given a list of routest, find the maximum fee. + * getMaxFee - Given a list of routes, find the maximum fee. * - * @param {*} routes List of routes + * @param {Array} routes List of routes * @returns {number} maximum fee */ export const getMaxFee = routes => { @@ -203,10 +205,29 @@ export const getMaxFee = routes => { return fee + 1 } +/** + * getMaxFee - Given a list of routes, find the maximum fee factoring in all possible payment retry attempts. + * + * @param {Array} routes List of routes + * @returns {number} maximum fee + */ +export const getMaxFeeInclusive = routes => { + if (!routes || !routes.length) { + return null + } + + const { + invoices: { retryCount, feeIncrementExponent }, + } = config + + const fee = getMaxFee(routes) + return range(retryCount).reduce(max => Math.ceil(max * feeIncrementExponent), fee) +} + /** * getFeeRange - Given a list of routest, find the maximum and maximum fee. * - * @param {*} routes List of routes + * @param {Array} routes List of routes * @returns {{min:number, max:number}} object with keys `min` and `max` */ export const getFeeRange = (routes = []) => ({