diff --git a/renderer/components/Activity/PaymentModal/Htlc.js b/renderer/components/Activity/PaymentModal/Htlc.js
new file mode 100644
index 00000000000..f9dadb54a6f
--- /dev/null
+++ b/renderer/components/Activity/PaymentModal/Htlc.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Flex } from 'rebass/styled-components'
+import { CoinBig } from '@zap/utils/coin'
+import { Text } from 'components/UI'
+import { CryptoSelector, CryptoValue } from 'containers/UI'
+import HtlcHops from './HtlcHops'
+
+const Htlc = ({ route, isAmountVisible = true, ...rest }) => {
+ const amountExcludingFees = CoinBig(route.totalAmt)
+ .minus(route.totalFees)
+ .toString()
+ return (
+
+ {isAmountVisible && (
+
+
+
+
+ )}
+
+
+ )
+}
+
+Htlc.propTypes = {
+ isAmountVisible: PropTypes.bool,
+ route: PropTypes.object.isRequired,
+}
+
+export default Htlc
diff --git a/renderer/components/Activity/PaymentModal/HtlcHops.js b/renderer/components/Activity/PaymentModal/HtlcHops.js
new file mode 100644
index 00000000000..1b68895931c
--- /dev/null
+++ b/renderer/components/Activity/PaymentModal/HtlcHops.js
@@ -0,0 +1,50 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Flex } from 'rebass/styled-components'
+import { useIntl } from 'react-intl'
+import { CoinBig } from '@zap/utils/coin'
+import { getDisplayNodeName } from 'reducers/payment/utils'
+import { Truncate } from 'components/Util'
+import ArrowRight from 'components/Icon/ArrowRight'
+import messages from './messages'
+
+const HtlcHops = ({ hops, ...rest }) => {
+ const { formatMessage, formatNumber } = useIntl()
+ return (
+
+ {hops.map((hop, index) => {
+ const displayName = getDisplayNodeName(hop)
+ const hasFee = CoinBig(hop.feeMsat).gt(0)
+ const isLast = index === hops.length - 1
+ const multiHop = hops.length > 1
+ return (
+
+ {multiHop && (
+
+
+
+ )}
+
+
+ )
+ })}
+
+ )
+}
+
+HtlcHops.propTypes = {
+ hops: PropTypes.array.isRequired,
+}
+
+export default HtlcHops
diff --git a/renderer/components/Activity/PaymentModal/Route.js b/renderer/components/Activity/PaymentModal/Route.js
index 902c6387720..202af8d046e 100644
--- a/renderer/components/Activity/PaymentModal/Route.js
+++ b/renderer/components/Activity/PaymentModal/Route.js
@@ -1,70 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
-import { Box, Flex } from 'rebass/styled-components'
-import { useIntl } from 'react-intl'
-import { CoinBig } from '@zap/utils/coin'
-import { getDisplayNodeName } from 'reducers/payment/utils'
-import { Truncate } from 'components/Util'
-import { CryptoSelector, CryptoValue } from 'containers/UI'
-import { Bar, Text } from 'components/UI'
-import ArrowRight from 'components/Icon/ArrowRight'
-import messages from './messages'
-
-const HtlcHops = ({ hops, ...rest }) => {
- const { formatMessage, formatNumber } = useIntl()
- return (
-
- {hops.map(hop => {
- const displayName = getDisplayNodeName(hop)
- const hasFee = CoinBig(hop.feeMsat).gt(0)
- return (
-
-
-
-
-
-
- )
- })}
-
- )
-}
-
-HtlcHops.propTypes = {
- hops: PropTypes.array.isRequired,
-}
-
-const Htlc = ({ htlc, isAmountVisible = true, ...rest }) => {
- const amountExcludingFees = CoinBig(htlc.route.totalAmt)
- .minus(htlc.route.totalFees)
- .toString()
- return (
-
- {isAmountVisible && (
-
-
-
-
- )}
-
-
- )
-}
-
-Htlc.propTypes = {
- htlc: PropTypes.object.isRequired,
- isAmountVisible: PropTypes.bool.isRequired,
-}
+import { Box } from 'rebass/styled-components'
+import { Bar } from 'components/UI'
+import Htlc from './Htlc'
const Route = ({ htlcs, ...rest }) => {
return (
@@ -75,7 +13,7 @@ const Route = ({ htlcs, ...rest }) => {
return (
{!isFirst && }
-
+
)
})}
diff --git a/renderer/components/Activity/PaymentModal/index.js b/renderer/components/Activity/PaymentModal/index.js
index 9360e9e78ee..455136dbce0 100644
--- a/renderer/components/Activity/PaymentModal/index.js
+++ b/renderer/components/Activity/PaymentModal/index.js
@@ -1 +1,4 @@
export PaymentModal from './PaymentModal'
+export Route from './Route'
+export Htlc from './Htlc'
+export HtlcHops from './HtlcHops'
diff --git a/renderer/components/Pay/PaySummary.js b/renderer/components/Pay/PaySummary.js
index 4aa0c45f3b8..2d442574341 100644
--- a/renderer/components/Pay/PaySummary.js
+++ b/renderer/components/Pay/PaySummary.js
@@ -38,6 +38,7 @@ const PaySummary = props => {
minFee={getMinFee(routes)}
mt={-3}
payReq={payReq}
+ route={routes[0]}
/>
)
}
diff --git a/renderer/components/Pay/PaySummaryLightning.js b/renderer/components/Pay/PaySummaryLightning.js
index 572af1d62bd..bb20bdd4b2e 100644
--- a/renderer/components/Pay/PaySummaryLightning.js
+++ b/renderer/components/Pay/PaySummaryLightning.js
@@ -9,6 +9,7 @@ import BigArrowRight from 'components/Icon/BigArrowRight'
import { Bar, DataRow, Link, Spinner, Text, Tooltip } from 'components/UI'
import { CryptoSelector, CryptoValue, FiatValue } from 'containers/UI'
import { Truncate } from 'components/Util'
+import { HtlcHops } from 'components/Activity/PaymentModal'
import messages from './messages'
const ConfigLink = ({ feeLimit, openModal, ...rest }) => (
@@ -34,6 +35,7 @@ class PaySummaryLightning extends React.Component {
nodes: PropTypes.array,
openModal: PropTypes.func.isRequired,
payReq: PropTypes.string.isRequired,
+ route: PropTypes.object,
}
static defaultProps = {
@@ -110,6 +112,7 @@ class PaySummaryLightning extends React.Component {
minFee,
nodes,
payReq,
+ route,
...rest
} = this.props
@@ -157,9 +160,13 @@ class PaySummaryLightning extends React.Component {
-
-
-
+ {route ? (
+
+ ) : (
+
+
+
+ )}
diff --git a/renderer/containers/App/App.js b/renderer/containers/App/App.js
index aaf9d7445e8..7e2401d48a0 100644
--- a/renderer/containers/App/App.js
+++ b/renderer/containers/App/App.js
@@ -15,6 +15,7 @@ import {
} from 'reducers/lnurl'
import { initBackupService } from 'reducers/backup'
import { infoSelectors } from 'reducers/info'
+import { paySelectors } from 'reducers/pay'
import { setModals, modalSelectors } from 'reducers/modal'
import { fetchSuggestedNodes } from 'reducers/channels'
import { initTickers } from 'reducers/ticker'
@@ -26,7 +27,7 @@ const mapStateToProps = state => ({
activeWalletSettings: walletSelectors.activeWalletSettings(state),
isAppReady: appSelectors.isAppReady(state),
isSyncedToGraph: infoSelectors.isSyncedToGraph(),
- redirectPayReq: state.pay.redirectPayReq,
+ redirectPayReq: paySelectors.redirectPayReq(state),
modals: modalSelectors.getModalState(state),
lnurlWithdrawParams: lnurlSelectors.lnurlWithdrawParams(state),
willShowLnurlAuthPrompt: lnurlSelectors.willShowLnurlAuthPrompt(state),
diff --git a/renderer/containers/Channels/ChannelCreateForm.js b/renderer/containers/Channels/ChannelCreateForm.js
index f534cc38c88..5c4a9a616f8 100644
--- a/renderer/containers/Channels/ChannelCreateForm.js
+++ b/renderer/containers/Channels/ChannelCreateForm.js
@@ -2,7 +2,7 @@ import { connect } from 'react-redux'
import ChannelCreateForm from 'components/Channels/ChannelCreateForm'
import { fetchTickers, tickerSelectors } from 'reducers/ticker'
import { openChannel } from 'reducers/channels'
-import { queryFees } from 'reducers/pay'
+import { queryFees, paySelectors } from 'reducers/pay'
import { balanceSelectors } from 'reducers/balance'
import { updateContactFormSearchQuery, contactFormSelectors } from 'reducers/contactsform'
import { walletSelectors } from 'reducers/wallet'
@@ -15,8 +15,8 @@ const mapStateToProps = state => ({
cryptoUnit: tickerSelectors.cryptoUnit(state),
walletBalance: balanceSelectors.walletBalanceConfirmed(state),
cryptoUnitName: tickerSelectors.cryptoUnitName(state),
- isQueryingFees: state.pay.isQueryingFees,
- onchainFees: state.pay.onchainFees,
+ isQueryingFees: paySelectors.isQueryingFees(state),
+ onchainFees: paySelectors.onchainFees(state),
lndTargetConfirmations: settingsSelectors.currentConfig(state).lndTargetConfirmations,
})
diff --git a/renderer/containers/Pay.js b/renderer/containers/Pay.js
index d808cb6045d..a525cc3e76a 100644
--- a/renderer/containers/Pay.js
+++ b/renderer/containers/Pay.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import { Pay } from 'components/Pay'
import { fetchTickers, tickerSelectors } from 'reducers/ticker'
-import { setRedirectPayReq, queryFees, queryRoutes } from 'reducers/pay'
+import { setRedirectPayReq, queryFees, queryRoutes, paySelectors } from 'reducers/pay'
import { balanceSelectors } from 'reducers/balance'
import { addFilter } from 'reducers/activity'
import { channelsSelectors } from 'reducers/channels'
@@ -18,11 +18,11 @@ const mapStateToProps = state => ({
channelBalance: balanceSelectors.channelBalance(state),
cryptoUnit: tickerSelectors.cryptoUnit(state),
cryptoUnitName: tickerSelectors.cryptoUnitName(state),
- isQueryingFees: state.pay.isQueryingFees,
+ isQueryingFees: paySelectors.isQueryingFees(state),
lndTargetConfirmations: settingsSelectors.currentConfig(state).lndTargetConfirmations,
- redirectPayReq: state.pay.redirectPayReq,
- onchainFees: state.pay.onchainFees,
- routes: state.pay.routes,
+ redirectPayReq: paySelectors.redirectPayReq(state),
+ onchainFees: paySelectors.onchainFees(state),
+ routes: paySelectors.routes(state),
maxOneTimeSend: channelsSelectors.maxOneTimeSend(state),
walletBalanceConfirmed: balanceSelectors.walletBalanceConfirmed(state),
})
diff --git a/renderer/containers/Pay/PaySummaryLightning.js b/renderer/containers/Pay/PaySummaryLightning.js
index 2b72e5cca7d..332b1dd7ebe 100644
--- a/renderer/containers/Pay/PaySummaryLightning.js
+++ b/renderer/containers/Pay/PaySummaryLightning.js
@@ -1,5 +1,6 @@
import { connect } from 'react-redux'
import PaySummaryLightning from 'components/Pay/PaySummaryLightning'
+import { paySelectors } from 'reducers/pay'
import { settingsSelectors } from 'reducers/settings'
import { tickerSelectors } from 'reducers/ticker'
import { networkSelectors } from 'reducers/network'
@@ -7,7 +8,7 @@ import { openModal } from 'reducers/modal'
const mapStateToProps = state => ({
cryptoUnitName: tickerSelectors.cryptoUnitName(state),
- isQueryingRoutes: state.pay.isQueryingRoutes,
+ isQueryingRoutes: paySelectors.isQueryingRoutes(state),
nodes: networkSelectors.nodes(state),
feeLimit: settingsSelectors.currentConfig(state).payments.feeLimit,
})
diff --git a/renderer/containers/Pay/PaySummaryOnChain.js b/renderer/containers/Pay/PaySummaryOnChain.js
index 2afb1e1575e..2e82aad86cc 100644
--- a/renderer/containers/Pay/PaySummaryOnChain.js
+++ b/renderer/containers/Pay/PaySummaryOnChain.js
@@ -1,14 +1,14 @@
import { connect } from 'react-redux'
import PaySummaryOnChain from 'components/Pay/PaySummaryOnChain'
import { tickerSelectors } from 'reducers/ticker'
-import { queryFees } from 'reducers/pay'
+import { queryFees, paySelectors } from 'reducers/pay'
import { networkSelectors } from 'reducers/network'
const mapStateToProps = state => ({
cryptoUnitName: tickerSelectors.cryptoUnitName(state),
- isQueryingFees: state.pay.isQueryingFees,
+ isQueryingFees: paySelectors.isQueryingFees(state),
nodes: networkSelectors.nodes(state),
- onchainFees: state.pay.onchainFees,
+ onchainFees: paySelectors.onchainFees(state),
})
const mapDispatchToProps = {
diff --git a/renderer/reducers/index.js b/renderer/reducers/index.js
index b233d800fa7..c2a9968bdad 100644
--- a/renderer/reducers/index.js
+++ b/renderer/reducers/index.js
@@ -44,6 +44,7 @@ import lnurl from './lnurl'
* @property {import('./invoice').State} invoice Invoice reducer.
* @property {import('./lnurl').State} lnurl Lnurl reducer.
* @property {import('./network').State} network Network reducer.
+ * @property {import('./pay').State} pay Pay reducer.
* @property {import('./payment').State} payment Payment reducer.
* @property {import('./settings').State} settings Settings reducer.
* @property {import('./transaction').State} transaction Transaction reducer.
diff --git a/renderer/reducers/pay/index.js b/renderer/reducers/pay/index.js
index 59f24a6d882..2770a77db3c 100644
--- a/renderer/reducers/pay/index.js
+++ b/renderer/reducers/pay/index.js
@@ -1,6 +1,7 @@
import payReducer from './reducer'
export default payReducer
+export paySelectors from './selectors'
export * from './constants'
export * from './reducer'
export * from './ipc'
diff --git a/renderer/reducers/pay/reducer.js b/renderer/reducers/pay/reducer.js
index 8f3652d7410..053a153527d 100644
--- a/renderer/reducers/pay/reducer.js
+++ b/renderer/reducers/pay/reducer.js
@@ -22,10 +22,22 @@ const {
SET_REDIRECT_PAY_REQ,
} = constants
+/**
+ * @typedef State
+ * @property {boolean} isQueryingRoutes Boolean indicating if routes are being probed
+ * @property {boolean} isQueryingFees Boolean indicating if fees are being queried
+ * @property {{fast:string|null, medium:string|null, slow:string|null}} onchainFees Onchain fee rates
+ * @property {string|null} queryFeesError Query fees error message
+ * @property {string|null} queryRoutesError Query routes error message
+ * @property {string|null} redirectPayReq Payrequest injected from external source
+ * @property {object[]} routes Routes from last probe attempt
+ */
+
// ------------------------------------
// Initial State
// ------------------------------------
+/** @type {State} */
const initialState = {
isQueryingRoutes: false,
isQueryingFees: false,
diff --git a/renderer/reducers/pay/selectors.js b/renderer/reducers/pay/selectors.js
new file mode 100644
index 00000000000..e17407008d0
--- /dev/null
+++ b/renderer/reducers/pay/selectors.js
@@ -0,0 +1,60 @@
+import { createSelector } from 'reselect'
+import { networkSelectors } from 'reducers/network'
+import { decorateRoute } from 'reducers/payment/utils'
+
+/**
+ * @typedef {import('../index').State} State
+ */
+
+const routesSelector = state => state.pay.routes
+const nodesSelector = state => networkSelectors.nodes(state)
+
+/**
+ * routes - Routes relating to current payment probe.
+ *
+ * @param {State} state Redux state
+ * @returns {object} Config overrides
+ */
+export const routes = createSelector(routesSelector, nodesSelector, (routes, nodes) =>
+ routes.map(route => decorateRoute(route, nodes))
+)
+
+/**
+ * isQueryingFees - Is querying fees.
+ *
+ * @param {State} state Redux state
+ * @returns {boolean} Boolean indicating if fees are being queried
+ */
+const isQueryingFees = state => state.pay.isQueryingFees
+
+/**
+ * isQueryingRoutes - Is querying routes.
+ *
+ * @param {State} state Redux state
+ * @returns {boolean} Boolean indicating if routes are being probed
+ */
+const isQueryingRoutes = state => state.pay.isQueryingRoutes
+
+/**
+ * onchainFees - Onchain fee rates.
+ *
+ * @param {State} state Redux state
+ * @returns {{fast:string|null, medium:string|null, slow:string|null}} Onchain fee rates
+ */
+const onchainFees = state => state.pay.onchainFees
+
+/**
+ * redirectPayReq - Payrequest injected from external source.
+ *
+ * @param {State} state Redux state
+ * @returns {string|null} Payrequest injected from external source
+ */
+const redirectPayReq = state => state.pay.redirectPayReq
+
+export default {
+ isQueryingFees,
+ isQueryingRoutes,
+ onchainFees,
+ redirectPayReq,
+ routes,
+}
diff --git a/renderer/reducers/payment/utils.js b/renderer/reducers/payment/utils.js
index 8f90c210c23..dc32da3f80e 100644
--- a/renderer/reducers/payment/utils.js
+++ b/renderer/reducers/payment/utils.js
@@ -80,15 +80,7 @@ export const decoratePayment = (payment, nodes = []) => {
// Try to add some info about the nodes involved in payment htlcs.
if (payment.htlcs) {
- const decoratedHtlcs = cloneDeep(payment.htlcs)
- decoratedHtlcs.map(htlc => {
- htlc.route.hops.map(hop => {
- hop.alias = getNodeAlias(hop.pubKey, nodes)
- return hop
- })
- return htlc
- })
- decoration.htlcs = decoratedHtlcs
+ decoration.htlcs = decorateHtlcs(payment.htlcs, nodes)
}
return {
@@ -97,6 +89,38 @@ export const decoratePayment = (payment, nodes = []) => {
}
}
+/**
+ * decorateHtlcs - Decorate htlcs list with custom/computed properties.
+ *
+ * @param {object[]} htlcs Htlcs
+ * @param {object[]} nodes Nodes
+ * @returns {object} Decorated htlcs
+ */
+export const decorateHtlcs = (htlcs, nodes = []) => {
+ const decoratedHtlcs = cloneDeep(htlcs)
+ decoratedHtlcs.map(htlc => {
+ htlc.route = decorateRoute(htlc.route, nodes)
+ return htlc
+ })
+ return decoratedHtlcs
+}
+
+/**
+ * decorateRoute - Decorate route object with custom/computed properties.
+ *
+ * @param {object} route Route
+ * @param {object[]} nodes Nodes
+ * @returns {object} Decorated route
+ */
+export const decorateRoute = (route, nodes = []) => {
+ const decoratedRoute = cloneDeep(route)
+ decoratedRoute.hops = decoratedRoute.hops.map(hop => {
+ hop.alias = getNodeAlias(hop.pubKey, nodes)
+ return hop
+ })
+ return decoratedRoute
+}
+
/**
* prepareKeysendPayload - Prepare a keysend payment.
*