Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
feat(wallet): basic keysend support
Browse files Browse the repository at this point in the history
fix #3333
fix #3338
  • Loading branch information
mrfelton committed Mar 24, 2020
1 parent f58de73 commit 8621eb6
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 97 deletions.
10 changes: 5 additions & 5 deletions renderer/components/Form/LightningInvoiceInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, useIntl } from 'react-intl'
import { useFieldState } from 'informed'
import { isOnchain, isLn, decodePayReq } from '@zap/utils/crypto'
import { isOnchain, isBolt11, isPubkey, decodePayReq } from '@zap/utils/crypto'
import { Message } from 'components/UI'
import TextArea from './TextArea'
import messages from './messages'
Expand All @@ -20,7 +20,7 @@ const validate = (intl, network, chain, value) => {
{ chain: chainName }
)
// If we have a LN invoice, ensure the invoice has an amount.
if (isLn(value, chain, network)) {
if (isBolt11(value, chain, network)) {
try {
const invoice = decodePayReq(value)
if (!invoice) {
Expand All @@ -33,7 +33,7 @@ const validate = (intl, network, chain, value) => {
} catch (e) {
return invalidRequestMessage
}
} else if (!isOnchain(value, chain, network)) {
} else if (!isOnchain(value, chain, network) && !isPubkey(value)) {
return invalidRequestMessage
}
}
Expand All @@ -45,7 +45,7 @@ const LightningInvoiceInput = props => {
const intl = useIntl()
const fieldState = useFieldState(field)
const { value } = fieldState
let chainName = isLn(value, chain, network) ? 'lightning' : chain
let chainName = isBolt11(value, chain, network) || isPubkey(value) ? 'lightning' : chain
if (network !== 'mainnet') {
chainName += ` (${network})`
}
Expand All @@ -62,7 +62,7 @@ const LightningInvoiceInput = props => {
{value && !fieldState.error && (
<Message mt={2} variant="success">
<FormattedMessage
{...messages.valid_request}
{...messages[isPubkey(value) ? 'valid_pubkey' : 'valid_request']}
values={{
chain: chainName,
}}
Expand Down
67 changes: 45 additions & 22 deletions renderer/components/Pay/Pay.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import config from 'config'
import { injectIntl } from 'react-intl'
import { decodePayReq, getMaxFee, isOnchain, isLn } from '@zap/utils/crypto'
import { decodePayReq, getMaxFee, isOnchain, isBolt11, isPubkey } from '@zap/utils/crypto'
import { Panel } from 'components/UI'
import { Form } from 'components/Form'
import { getAmountInSats, getFeeRate } from './utils'
Expand Down Expand Up @@ -62,7 +62,8 @@ class Pay extends React.Component {
state = {
currentStep: PAY_FORM_STEPS.address,
previousStep: null,
isLn: null,
isPubkey: null,
isBolt11: null,
isOnchain: null,
}

Expand All @@ -75,7 +76,7 @@ class Pay extends React.Component {

componentDidUpdate(prevProps, prevState) {
const { redirectPayReq, queryRoutes, setRedirectPayReq } = this.props
const { currentStep, invoice, isOnchain } = this.state
const { currentStep, invoice, isOnchain, isPubkey } = this.state
const { address, amount } = redirectPayReq || {}
const { payReq: prevPayReq } = prevProps || {}
const { address: prevAddress, amount: prevAmount } = prevPayReq || {}
Expand Down Expand Up @@ -111,21 +112,34 @@ class Pay extends React.Component {
return
}

// If we now have a valid onchain address, trigger the form submit to move to the amount step.
const isNowPubkey = isPubkey && isPubkey !== prevState.isPubkey
if (currentStep === PAY_FORM_STEPS.address && isNowPubkey) {
this.formApi.submitForm()
return
}

// If we now have a valid lightning invoice submit the form.
const isNowLightning = invoice && invoice !== prevState.invoice
if (currentStep === PAY_FORM_STEPS.address && isNowLightning) {
this.formApi.submitForm()
return
}

// update route.
if (
invoice &&
prevState.currentStep === PAY_FORM_STEPS.address &&
currentStep === PAY_FORM_STEPS.summary
) {
const { payeeNodeKey } = invoice
queryRoutes(payeeNodeKey, this.amountInSats())
// Query routes when moving to LN summary screen.
const isNowSummary =
currentStep === PAY_FORM_STEPS.summary && prevState.currentStep !== PAY_FORM_STEPS.summary
if (isNowSummary) {
let payeeNodeKey
if (invoice) {
payeeNodeKey = invoice.payeeNodeKey // eslint-disable-line prefer-destructuring
} else if (isPubkey) {
const {
values: { payReq },
} = this.formApi.getState()
payeeNodeKey = payReq
}
payeeNodeKey && queryRoutes(payeeNodeKey, this.amountInSats())
}
}

Expand Down Expand Up @@ -259,9 +273,9 @@ class Pay extends React.Component {
* @returns {string[]} List of enabled form steps
*/
steps = () => {
const { isLn, isOnchain, invoice } = this.state
const { isBolt11, isPubkey, isOnchain, invoice } = this.state
let steps = [PAY_FORM_STEPS.address]
if (isLn) {
if (isBolt11) {
// If we have an invoice and the invoice has an amount, this is a 2 step form.
if (invoice && (invoice.satoshis || invoice.millisatoshis)) {
steps.push(PAY_FORM_STEPS.summary)
Expand All @@ -270,7 +284,7 @@ class Pay extends React.Component {
else {
steps = [PAY_FORM_STEPS.address, PAY_FORM_STEPS.amount, PAY_FORM_STEPS.summary]
}
} else if (isOnchain) {
} else if (isOnchain || isPubkey) {
steps = [PAY_FORM_STEPS.address, PAY_FORM_STEPS.amount, PAY_FORM_STEPS.summary]
}
return steps
Expand Down Expand Up @@ -301,41 +315,47 @@ class Pay extends React.Component {
}

/**
* handlePayReqChange - Set isLn/isOnchain state based on payReq value.
* handlePayReqChange - Set isBolt11/isOnchain/isPubkey state based on payReq value.
*
* @param {string} payReq Payment request
*/
handlePayReqChange = payReq => {
const { chain, network } = this.props
const state = {
currentStep: PAY_FORM_STEPS.address,
isLn: null,
isBolt11: null,
isPubkey: null,
isOnchain: null,
invoice: null,
}

// See if the user has entered a valid lightning payment request.
if (isLn(payReq, chain, network)) {
if (isBolt11(payReq, chain, network)) {
try {
const invoice = decodePayReq(payReq)
state.invoice = invoice
} catch (e) {
return
}
state.isLn = true
state.isBolt11 = true
}

// Otherwise, see if we have a valid onchain address.
else if (isOnchain(payReq, chain, network)) {
state.isOnchain = true
}

// Or a valid pubkey.
else if (isPubkey(payReq)) {
state.isPubkey = true
}

// Update the state with our findings.
this.setState(state)
}

render() {
const { currentStep, invoice, isLn, isOnchain, previousStep } = this.state
const { currentStep, invoice, isBolt11, isOnchain, isPubkey, previousStep } = this.state
const {
chain,
chainName,
Expand Down Expand Up @@ -374,8 +394,9 @@ class Pay extends React.Component {
<PayPanelHeader
chainName={chainName}
cryptoUnitName={cryptoUnitName}
isLn={isLn}
isBolt11={isBolt11}
isOnchain={isOnchain}
isPubkey={isPubkey}
/>
</Panel.Header>
<Panel.Body py={3}>
Expand All @@ -393,8 +414,9 @@ class Pay extends React.Component {
initialAmountFiat={initialAmountFiat}
intl={intl}
invoice={invoice}
isLn={isLn}
isBolt11={isBolt11}
isOnchain={isOnchain}
isPubkey={isPubkey}
isQueryingFees={isQueryingFees}
lndTargetConfirmations={lndTargetConfirmations}
network={network}
Expand All @@ -415,9 +437,10 @@ class Pay extends React.Component {
currentStep={currentStep}
formState={formState}
invoice={invoice}
isLn={isLn}
isBolt11={isBolt11}
isOnchain={isOnchain}
isProcessing={isProcessing}
isPubkey={isPubkey}
maxOneTimeSend={maxOneTimeSend}
previousStep={this.goToPreviousStep}
walletBalanceConfirmed={walletBalanceConfirmed}
Expand Down
10 changes: 5 additions & 5 deletions renderer/components/Pay/PayAddressField.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PayAddressField extends React.Component {
formState: PropTypes.object,
handlePayReqChange: PropTypes.func.isRequired,
intl: intlShape.isRequired,
isLn: PropTypes.bool,
isBolt11: PropTypes.bool,
network: PropTypes.string.isRequired,
redirectPayReq: PropTypes.object,
}
Expand All @@ -48,11 +48,11 @@ class PayAddressField extends React.Component {
}

getPaymentRequestLabel = () => {
const { currentStep, isLn } = this.props
const { currentStep, isBolt11 } = this.props
let payReqLabel = 'request_label_onchain'
if (currentStep === PAY_FORM_STEPS.address) {
payReqLabel = 'request_label_combined'
} else if (isLn) {
} else if (isBolt11) {
payReqLabel = 'request_label_offchain'
}

Expand All @@ -66,11 +66,11 @@ class PayAddressField extends React.Component {
currentStep,
formState,
handlePayReqChange,
isLn,
isBolt11,
network,
redirectPayReq,
} = this.props
const addressFieldState = currentStep === PAY_FORM_STEPS.address || isLn ? 'big' : 'small'
const addressFieldState = currentStep === PAY_FORM_STEPS.address || isBolt11 ? 'big' : 'small'

const { payReq } = formState.values
const { submits } = formState
Expand Down
10 changes: 7 additions & 3 deletions renderer/components/Pay/PayPanelBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ const PayPanelBody = props => {
initialAmountFiat,
intl,
invoice,
isLn,
isBolt11,
isOnchain,
isPubkey,
isQueryingFees,
lndTargetConfirmations,
network,
Expand All @@ -49,7 +50,7 @@ const PayPanelBody = props => {
formApi={formState}
handlePayReqChange={handlePayReqChange}
intl={intl}
isLn={isLn}
isBolt11={isBolt11}
network={network}
redirectPayReq={redirectPayReq}
/>
Expand All @@ -62,6 +63,7 @@ const PayPanelBody = props => {
intl={intl}
invoice={invoice}
isOnchain={isOnchain}
isPubkey={isPubkey}
isQueryingFees={isQueryingFees}
lndTargetConfirmations={lndTargetConfirmations}
onchainFees={onchainFees}
Expand All @@ -73,6 +75,7 @@ const PayPanelBody = props => {
currentStep={currentStep}
formApi={formApi}
isOnchain={isOnchain}
isPubkey={isPubkey}
lndTargetConfirmations={lndTargetConfirmations}
onchainFees={onchainFees}
routes={routes}
Expand All @@ -95,8 +98,9 @@ PayPanelBody.propTypes = {
initialAmountFiat: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
intl: intlShape.isRequired,
invoice: PropTypes.object,
isLn: PropTypes.bool,
isBolt11: PropTypes.bool,
isOnchain: PropTypes.bool,
isPubkey: PropTypes.bool,
isQueryingFees: PropTypes.bool,
lndTargetConfirmations: PropTypes.shape({
fast: PropTypes.number.isRequired,
Expand Down
25 changes: 18 additions & 7 deletions renderer/components/Pay/PayPanelFooter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ import { PAY_FORM_STEPS } from './constants'

const isEnoughFunds = props => {
// convert entered amount to satoshis
const { amountInSats, channelBalance, invoice, isLn, isOnchain, walletBalanceConfirmed } = props
const {
amountInSats,
channelBalance,
invoice,
isBolt11,
isOnchain,
isPubkey,
walletBalanceConfirmed,
} = props

// Determine whether we have enough funds available.
let hasEnoughFunds = true
if (isLn && invoice) {
if ((isBolt11 && invoice) || isPubkey) {
hasEnoughFunds = amountInSats <= channelBalance
} else if (isOnchain) {
hasEnoughFunds = amountInSats <= walletBalanceConfirmed
Expand Down Expand Up @@ -58,23 +66,24 @@ const PayPanelFooter = props => {
cryptoUnitName,
currentStep,
formState,
isLn,
isBolt11,
isOnchain,
isPubkey,
isProcessing,
previousStep,
walletBalanceConfirmed,
intl,
} = props

const renderLiquidityWarning = props => {
const { currentStep, maxOneTimeSend, cryptoUnit, isLn, amountInSats } = props
const { currentStep, maxOneTimeSend, cryptoUnit, isBolt11, isPubkey, amountInSats } = props

if (currentStep !== PAY_FORM_STEPS.summary) {
return null
}

const isNotEnoughFunds = !isEnoughFunds(props)
const isAboveMax = isLn && amountInSats > maxOneTimeSend
const isAboveMax = (isBolt11 || isPubkey) && amountInSats > maxOneTimeSend
const formattedMax = intl.formatNumber(convert('sats', cryptoUnit, maxOneTimeSend), {
maximumFractionDigits: 8,
})
Expand All @@ -100,7 +109,8 @@ const PayPanelFooter = props => {

// Determine which buttons should be visible.
const hasBackButton = currentStep !== PAY_FORM_STEPS.address
const hasSubmitButton = currentStep !== PAY_FORM_STEPS.address || isOnchain || isLn
const hasSubmitButton =
currentStep !== PAY_FORM_STEPS.address || isOnchain || isBolt11 || isPubkey

return (
<Flex flexDirection="column">
Expand Down Expand Up @@ -157,9 +167,10 @@ PayPanelFooter.propTypes = {
formState: PropTypes.object.isRequired,
intl: intlShape.isRequired,
invoice: PropTypes.object,
isLn: PropTypes.bool,
isBolt11: PropTypes.bool,
isOnchain: PropTypes.bool,
isProcessing: PropTypes.bool,
isPubkey: PropTypes.bool,
maxOneTimeSend: PropTypes.string.isRequired,
previousStep: PropTypes.func.isRequired,
walletBalanceConfirmed: PropTypes.string.isRequired,
Expand Down
Loading

0 comments on commit 8621eb6

Please sign in to comment.