Browse Source

Merge pull request #864 from mrfelton/feat/ui-lightning-invoice

feat(ui): add LightningInvoiceInput component
renovate/lint-staged-8.x
JimmyMow 6 years ago
committed by GitHub
parent
commit
295ee8c698
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .storybook/config.js
  2. 2
      app/components/Activity/Activity.js
  3. 2
      app/components/Activity/InvoiceModal/InvoiceModal.js
  4. 2
      app/components/Activity/PaymentModal/PaymentModal.js
  5. 5
      app/components/Activity/TransactionModal/TransactionModal.js
  6. 2
      app/components/Contacts/AddChannel/AddChannel.js
  7. 2
      app/components/Contacts/ConnectManually/ConnectManually.js
  8. 7
      app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js
  9. 8
      app/components/Form/Pay/Pay.js
  10. 9
      app/components/Form/Request/Request.js
  11. 2
      app/components/GlobalError/GlobalError.js
  12. 2
      app/components/Onboarding/FormContainer/FormContainer.js
  13. 2
      app/components/Onboarding/Login/Login.js
  14. 1
      app/components/UI/GlobalStyle.js
  15. 109
      app/components/UI/Input.js
  16. 24
      app/components/UI/Label.js
  17. 71
      app/components/UI/LightningInvoiceInput.js
  18. 2
      app/components/UI/MainContent.js
  19. 4
      app/components/UI/Modal.js
  20. 2
      app/components/UI/Select.js
  21. 2
      app/components/UI/Sidebar.js
  22. 99
      app/components/UI/TextArea.js
  23. 27
      app/components/UI/index.js
  24. 5
      app/components/Wallet/Wallet.js
  25. 5
      app/containers/Root.js
  26. 73
      app/lib/utils/crypto.js
  27. 1
      package.json
  28. 4
      stories/components/background.stories.js
  29. 2
      stories/components/bar.stories.js
  30. 2
      stories/components/button.stories.js
  31. 2
      stories/components/dropdown.stories.js
  32. 94
      stories/components/form.stories.js
  33. 4
      stories/components/menu.stories.js
  34. 2
      stories/components/modal.stories.js
  35. 2
      stories/components/notification.stories.js
  36. 4
      stories/components/page-elements.stories.js
  37. 2
      stories/components/spinner.stories.js
  38. 3
      stories/components/typography.stories.js
  39. 2
      test/unit/components/UI/BackgroundDark.spec.js
  40. 2
      test/unit/components/UI/BackgroundLight.spec.js
  41. 2
      test/unit/components/UI/BackgroundLightest.spec.js
  42. 2
      test/unit/components/UI/Bar.spec.js
  43. 2
      test/unit/components/UI/Button.spec.js
  44. 2
      test/unit/components/UI/Dropdown.spec.js
  45. 2
      test/unit/components/UI/Heading.spec.js
  46. 2
      test/unit/components/UI/Input.spec.js
  47. 21
      test/unit/components/UI/LightningInvoiceInput.spec.js
  48. 2
      test/unit/components/UI/MainContent.spec.js
  49. 4
      test/unit/components/UI/Menu.spec.js
  50. 2
      test/unit/components/UI/Modal.spec.js
  51. 3
      test/unit/components/UI/Notification.spec.js
  52. 2
      test/unit/components/UI/Page.spec.js
  53. 2
      test/unit/components/UI/Range.spec.js
  54. 2
      test/unit/components/UI/Select.spec.js
  55. 2
      test/unit/components/UI/Sidebar.spec.js
  56. 2
      test/unit/components/UI/Spinner.spec.js
  57. 2
      test/unit/components/UI/Text.spec.js
  58. 2
      test/unit/components/UI/TextArea.spec.js
  59. 2
      test/unit/components/UI/Toggle.spec.js
  60. 45
      test/unit/components/UI/__snapshots__/Input.spec.js.snap
  61. 60
      test/unit/components/UI/__snapshots__/LightningInvoiceInput.spec.js.snap
  62. 44
      test/unit/components/UI/__snapshots__/Select.spec.js.snap
  63. 45
      test/unit/components/UI/__snapshots__/TextArea.spec.js.snap
  64. 100
      test/unit/utils/crypto.spec.js
  65. 47
      yarn.lock

3
.storybook/config.js

@ -12,8 +12,7 @@ import { setIntlConfig, withIntl } from 'storybook-addon-intl'
import StoryRouter from 'storybook-react-router'
import { dark, light } from 'themes'
import { getDefaultLocale, locales } from 'lib/i18n'
import BackgroundDark from 'components/UI/BackgroundDark'
import GlobalStyle from 'components/UI/GlobalStyle'
import { BackgroundDark, GlobalStyle } from 'components/UI'
// Register supported locales.
import '../app/lib/i18n/locale'

2
app/components/Activity/Activity.js

@ -6,7 +6,7 @@ import FaRepeat from 'react-icons/lib/fa/repeat'
import { FormattedMessage, injectIntl } from 'react-intl'
import { Flex } from 'rebass'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import Wallet from 'components/Wallet'
import Invoice from './Invoice'
import Payment from './Payment'

2
app/components/Activity/InvoiceModal/InvoiceModal.js

@ -4,7 +4,7 @@ import QRCode from 'qrcode.react'
import copy from 'copy-to-clipboard'
import { showNotification } from 'lib/utils/notifications'
import Value from 'components/Value'
import Dropdown from 'components/UI/Dropdown'
import { Dropdown } from 'components/UI'
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import Countdown from '../Countdown'
import messages from './messages'

2
app/components/Activity/PaymentModal/PaymentModal.js

@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'components/UI/Dropdown'
import PaperPlane from 'components/Icon/PaperPlane'
import Zap from 'components/Icon/Zap'
import { Dropdown } from 'components/UI'
import Value from 'components/Value'
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import messages from './messages'

5
app/components/Activity/TransactionModal/TransactionModal.js

@ -1,16 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'components/UI/Dropdown'
import PaperPlane from 'components/Icon/PaperPlane'
import Hand from 'components/Icon/Hand'
import ChainLink from 'components/Icon/ChainLink'
import { Dropdown } from 'components/UI'
import { blockExplorer } from 'lib/utils'
import Value from 'components/Value'
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import messages from './messages'
import styles from './TransactionModal.scss'
const TransactionModal = ({

2
app/components/Contacts/AddChannel/AddChannel.js

@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import X from 'components/Icon/X'
import { FormattedMessage } from 'react-intl'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import messages from './messages'
import styles from './AddChannel.scss'

2
app/components/Contacts/ConnectManually/ConnectManually.js

@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, injectIntl } from 'react-intl'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import messages from './messages'
import styles from './ConnectManually.scss'

7
app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js

@ -1,15 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import FaExclamationCircle from 'react-icons/lib/fa/exclamation-circle'
import AmountInput from 'components/AmountInput'
import Button from 'components/UI/Button'
import Dropdown from 'components/UI/Dropdown'
import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage } from 'react-intl'
import messages from './messages'
import styles from './SubmitChannelForm.scss'
class SubmitChannelForm extends React.Component {

8
app/components/Form/Pay/Pay.js

@ -1,14 +1,10 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { btc } from 'lib/utils'
import PaperPlane from 'components/Icon/PaperPlane'
import ChainLink from 'components/Icon/ChainLink'
import { btc } from 'lib/utils'
import AmountInput from 'components/AmountInput'
import Button from 'components/UI/Button'
import Dropdown from 'components/UI/Dropdown'
import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage, injectIntl } from 'react-intl'
import messages from './messages'

9
app/components/Form/Request/Request.js

@ -1,16 +1,11 @@
import React from 'react'
import PropTypes from 'prop-types'
import Hand from 'components/Icon/Hand'
import { btc } from 'lib/utils'
import Hand from 'components/Icon/Hand'
import AmountInput from 'components/AmountInput'
import Button from 'components/UI/Button'
import Dropdown from 'components/UI/Dropdown'
import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage, injectIntl } from 'react-intl'
import messages from './messages'
import styles from './Request.scss'
const Request = ({

2
app/components/GlobalError/GlobalError.js

@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { animated, Transition } from 'react-spring'
import { Box } from 'rebass'
import errorToUserFriendly from 'lib/utils/userFriendlyErrors'
import Notification from 'components/UI/Notification'
import { Notification } from 'components/UI'
class GlobalError extends React.Component {
static propTypes = {

2
app/components/Onboarding/FormContainer/FormContainer.js

@ -6,7 +6,7 @@ import FaAngleLeft from 'react-icons/lib/fa/angle-left'
import FaAngleRight from 'react-icons/lib/fa/angle-right'
import ZapLogo from 'components/Icon/ZapLogo'
import ZapLogoBlack from 'components/Icon/ZapLogoBlack'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import messages from './messages'
import styles from './FormContainer.scss'

2
app/components/Onboarding/Login/Login.js

@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, injectIntl } from 'react-intl'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import messages from './messages'
import styles from './Login.scss'

1
app/components/UI/GlobalStyle.js

@ -9,7 +9,6 @@ const GlobalStyle = createGlobalStyle`
}
body {
position: relative;
box-sizing: border-box;
overflow-y: hidden;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);

109
app/components/UI/Input.js

@ -1,9 +1,28 @@
import React from 'react'
import { asField } from 'informed'
import { Input as Base } from 'styled-system-html'
import { withTheme } from 'styled-components'
import system from '@rebass/components'
import { styles } from 'styled-system'
import { Flex } from 'rebass'
import FormFieldMessage from 'components/UI/FormFieldMessage'
import { FormFieldMessage } from 'components/UI'
// Create an html input element that accepts all style props from styled-system.
const SystemInput = system(
{
as: 'input',
border: 1,
borderColor: 'gray',
borderRadius: 5,
bg: 'transparent',
color: 'primaryText',
fontFamily: 'sans',
fontSize: 'm',
fontWeight: 'light',
p: 3,
width: 1
},
...Object.keys(styles)
)
/**
* @render react
@ -11,15 +30,26 @@ import FormFieldMessage from 'components/UI/FormFieldMessage'
* @example
* <Input />
*/
class Input extends React.PureComponent {
class Input extends React.Component {
static displayName = 'Input'
state = {
hasFocus: false
}
constructor(props) {
super(props)
const { forwardedRef } = this.props
this.inputRef = forwardedRef || React.createRef()
}
render() {
const {
css,
onChange,
onBlur,
onFocus,
initialValue,
field,
forwardedRef,
theme,
fieldApi,
@ -27,42 +57,40 @@ class Input extends React.PureComponent {
justifyContent,
...rest
} = this.props
const { readOnly } = this.props
const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
const isValid = value && !fieldState.error
// Calculate the border color based on the current field state.
let borderColor
if (fieldState.touched) {
if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
}
if (readOnly) {
borderColor = theme.colors.gray
} else if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
}
return (
<Flex flexDirection="column" justifyContent={justifyContent}>
<Base
outline="none"
borderRadius="5px"
borderColor={borderColor || theme.colors.white}
border="1px solid white"
bg="transparent"
color="white"
p={3}
type="text"
fontSize="m"
width={1}
css={{
'&:focus': {
<SystemInput
borderColor={borderColor || theme.colors.gray}
css={Object.assign(
{
outline: 'none',
border: `1px solid ${borderColor || theme.colors.lightningOrange} }`
}
}}
'&:not([readOnly]):not([disabled]):focus': {
border: `1px solid ${
isValid ? theme.colors.superGreen : theme.colors.lightningOrange
} }`
}
},
css
)}
{...rest}
value={!value && value !== 0 ? '' : value}
name={field}
ref={forwardedRef}
ref={this.inputRef}
value={!value && value !== 0 ? '' : initialValue || value}
onChange={e => {
setValue(e.target.value)
if (onChange) {
@ -71,14 +99,33 @@ class Input extends React.PureComponent {
}}
onBlur={e => {
setTouched()
// Make the state aware that the element is now focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onBlur) {
onBlur(e)
}
}}
onFocus={e => {
// Make the state aware that the element is no longer focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onFocus) {
onFocus(e)
}
}}
error={fieldState.error}
/>
{fieldState.error && (
<FormFieldMessage variant="error" justifyContent={justifyContent}>
<FormFieldMessage
variant={hasFocus ? 'warning' : 'error'}
justifyContent={justifyContent}
mt={2}
>
{fieldState.error}
</FormFieldMessage>
)}

24
app/components/UI/Label.js

@ -1,5 +1,20 @@
import React from 'react'
import { Label as Base } from 'styled-system-html'
import system from '@rebass/components'
import { styles } from 'styled-system'
// Create an html input element that accepts all style props from styled-system.
const SystemLabel = system(
{
as: 'label',
display: 'block',
color: 'primaryText',
fontSize: 'm',
fontWeight: 'normal',
width: 1,
mb: 1
},
...Object.keys(styles)
)
/**
* @render react
@ -7,13 +22,12 @@ import { Label as Base } from 'styled-system-html'
* @example
* <Label />
*/
class Label extends React.Component {
class Label extends React.PureComponent {
static displayName = 'Label'
render() {
return (
<Base css={{ display: 'block' }} color="white" fontWeight="bold" mb={1} {...this.props} />
)
const { readOnly } = this.props
return <SystemLabel {...this.props} opacity={readOnly ? 0.6 : null} />
}
}

71
app/components/UI/LightningInvoiceInput.js

@ -0,0 +1,71 @@
import React from 'react'
import PropTypes from 'prop-types'
import { asField } from 'informed'
import { isOnchain, isLn } from 'lib/utils/crypto'
import TextArea from 'components/UI/TextArea'
import FormFieldMessage from 'components/UI/FormFieldMessage'
/**
* @render react
* @name LightningInvoiceInput
* @example
* <LightningInvoiceInput
network="testnet"
field="testnet"
id="testnet"
validateOnChange
validateOnBlur />
*/
class LightningInvoiceInput extends React.Component {
static displayName = 'LightningInvoiceInput'
static propTypes = {
required: PropTypes.bool,
chain: PropTypes.oneOf(['bitcoin', 'litecoin']),
network: PropTypes.oneOf(['mainnet', 'testnet', 'regtest'])
}
static defaultProps = {
required: false
}
validate = value => {
const { network, chain, required } = this.props
if (required && (!value || value.trim() === '')) {
return 'This is a required field'
}
if (value && !isLn(value, chain, network) && !isOnchain(value, chain, network)) {
return 'Not a valid address.'
}
}
render() {
return (
<InformedTextArea
placeholder="Paste a Lightning Payment Request or Bitcoin Address here"
rows={5}
{...this.props}
validate={this.validate}
/>
)
}
}
const InformedTextArea = asField(({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState
const { chain, network, ...rest } = props
return (
<React.Fragment>
<TextArea {...rest} />
{value &&
!fieldState.error && (
<FormFieldMessage variant="success" mt={2}>
Valid {isLn(value, chain, network) ? 'lightning' : chain} address{' '}
{network !== 'mainnet' && `(${network})`}
</FormFieldMessage>
)}
</React.Fragment>
)
})
export default LightningInvoiceInput

2
app/components/UI/MainContent.js

@ -1,5 +1,5 @@
import React from 'react'
import BackgroundDark from 'components/UI/BackgroundDark'
import { BackgroundDark } from 'components/UI'
/**
* @render react

4
app/components/UI/Modal.js

@ -1,9 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Box, Flex } from 'rebass'
import BackgroundDark from 'components/UI/BackgroundDark'
import { BackgroundDark } from 'components/UI'
import X from 'components/Icon/X'
/**

2
app/components/UI/Select.js

@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { asField } from 'informed'
import Input from 'components/UI/Input'
import { Input } from 'components/UI'
import styled, { withTheme } from 'styled-components'
import Downshift from 'downshift'
import { Box } from 'rebass'

2
app/components/UI/Sidebar.js

@ -1,5 +1,5 @@
import React from 'react'
import BackgroundLightest from 'components/UI/BackgroundLightest'
import { BackgroundLightest } from 'components/UI'
/**
* @render react

99
app/components/UI/TextArea.js

@ -1,9 +1,29 @@
import React from 'react'
import { asField } from 'informed'
import { TextArea as Base } from 'styled-system-html'
import system from '@rebass/components'
import { styles } from 'styled-system'
import { withTheme } from 'styled-components'
import { Flex } from 'rebass'
import FormFieldMessage from 'components/UI/FormFieldMessage'
import { FormFieldMessage } from 'components/UI'
// Create an html textarea element that accepts all style props from styled-system.
const SystemTextArea = system(
{
as: 'textarea',
border: 1,
borderColor: 'gray',
borderRadius: '5px',
bg: 'transparent',
color: 'primaryText',
fontFamily: 'sans',
fontSize: 'm',
fontWeight: 'light',
p: 3,
width: 1,
rows: 5
},
...Object.keys(styles)
)
/**
* @render react
@ -14,10 +34,22 @@ import FormFieldMessage from 'components/UI/FormFieldMessage'
class TextArea extends React.PureComponent {
static displayName = 'TextArea'
state = {
hasFocus: false
}
constructor(props) {
super(props)
const { forwardedRef } = this.props
this.inputRef = forwardedRef || React.createRef()
}
render() {
const {
css,
onChange,
onBlur,
onFocus,
initialValue,
forwardedRef,
theme,
@ -26,11 +58,17 @@ class TextArea extends React.PureComponent {
justifyContent,
...rest
} = this.props
const { readOnly } = this.props
const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
const isValid = value && !fieldState.error
// Calculate the border color based on the current field state.
let borderColor
if (fieldState.error) {
if (readOnly) {
borderColor = theme.colors.gray
} else if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
@ -38,27 +76,23 @@ class TextArea extends React.PureComponent {
return (
<Flex flexDirection="column" justifyContent={justifyContent}>
<Base
outline="none"
borderRadius="5px"
borderColor={borderColor || theme.colors.white}
border="1px solid white"
bg="transparent"
color="white"
p={3}
type="text"
fontSize="m"
width={1}
rows="5"
css={{
'&:focus': {
<SystemTextArea
borderColor={borderColor || theme.colors.gray}
opacity={readOnly ? 0.6 : null}
css={Object.assign(
{
outline: 'none',
border: `1px solid ${borderColor || theme.colors.lightningOrange} }`
}
}}
'&:not([readOnly]):not([disabled]):focus': {
border: `1px solid ${
isValid ? theme.colors.superGreen : theme.colors.lightningOrange
} }`
}
},
css
)}
{...rest}
ref={forwardedRef}
value={!value && value !== 0 ? '' : value}
ref={this.inputRef}
value={!value && value !== 0 ? '' : initialValue || value}
onChange={e => {
setValue(e.target.value)
if (onChange) {
@ -67,14 +101,33 @@ class TextArea extends React.PureComponent {
}}
onBlur={e => {
setTouched()
// Make the state aware that the element is now focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onBlur) {
onBlur(e)
}
}}
onFocus={e => {
// Make the state aware that the element is no longer focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onFocus) {
onFocus(e)
}
}}
error={fieldState.error}
/>
{fieldState.error && (
<FormFieldMessage variant="error" justifyContent={justifyContent}>
<FormFieldMessage
variant={hasFocus ? 'warning' : 'error'}
justifyContent={justifyContent}
mt={2}
>
{fieldState.error}
</FormFieldMessage>
)}

27
app/components/UI/index.js

@ -0,0 +1,27 @@
export BackgroundDark from './BackgroundDark'
export BackgroundLight from './BackgroundLight'
export BackgroundLightest from './BackgroundLightest'
export Bar from './Bar'
export Button from './Button'
export Dropdown from './Dropdown'
export FormFieldMessage from './FormFieldMessage'
export GlobalStyle from './GlobalStyle'
export Heading from './Heading'
export Input from './Input'
export Label from './Label'
export LightningInvoiceInput from './LightningInvoiceInput'
export MainContent from './MainContent'
export Menu from './Menu'
export MenuItem from './MenuItem'
export MenuItemGroup from './MenuItemGroup'
export Modal from './Modal'
export Notification from './Notification'
export Page from './Page'
export Range from './Range'
export Select from './Select'
export Sidebar from './Sidebar'
export Spinner from './Spinner'
export Text from './Text'
export TextArea from './TextArea'
export Titlebar from './Titlebar'
export Toggle from './Toggle'

5
app/components/Wallet/Wallet.js

@ -6,10 +6,7 @@ import { Box, Flex } from 'rebass'
import { btc, blockExplorer } from 'lib/utils'
import Value from 'components/Value'
import Settings from 'components/Settings'
import Button from 'components/UI/Button'
import Text from 'components/UI/Text'
import Dropdown from 'components/UI/Dropdown'
import { Button, Dropdown, Text } from 'components/UI'
import CheckAnimated from 'components/Icon/CheckAnimated'
import ZapLogo from 'components/Icon/ZapLogo'
import ZapLogoBlack from 'components/Icon/ZapLogoBlack'

5
app/containers/Root.js

@ -10,12 +10,9 @@ import { loadingSelectors, setLoading, setMounted } from 'reducers/loading'
import { initCurrency, initLocale } from 'reducers/locale'
import { initTheme, themeSelectors } from 'reducers/theme'
import Page from 'components/UI/Page'
import Titlebar from 'components/UI/Titlebar'
import { Page, Titlebar, GlobalStyle } from 'components/UI'
import GlobalError from 'components/GlobalError'
import GlobalStyle from 'components/UI/GlobalStyle'
import withLoading from 'components/withLoading'
import Onboarding from './Onboarding'
import Syncing from './Syncing'
import App from './App'

73
app/lib/utils/crypto.js

@ -0,0 +1,73 @@
import bitcoin from 'bitcoinjs-lib'
import bech32 from 'lib/utils/bech32'
/**
* Test to see if a string is a valid on-chain address.
* @param {String} input string to check.
* @param {String} [network='mainnet'] network to check (mainnet, testnet).
* @return {Boolean} boolean indicating wether the address is a valid on-chain address.
*/
export const isOnchain = (input, chain = 'bitcoin', network = 'mainnet') => {
if (chain !== 'bitcoin') {
// TODO: Implement address checking for litecoin.
return true
}
try {
bitcoin.address.toOutputScript(
input,
network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet
)
return true
} catch (e) {
return false
}
}
/**
* Test to see if a string is a valid lightning address.
* @param {String} input string to check.
* @param {String} [network='bitcoin'] chain to check (bitcoin, litecoin).
* @param {String} [network='mainnet'] network to check (mainnet, testnet, regtest).
* @return {Boolean} boolean indicating wether the address is a lightning address.
*/
export const isLn = (input, chain = 'bitcoin', network = 'mainnet') => {
let prefix = 'ln'
// Prefixes come from SLIP-0173
// See https://github.com/satoshilabs/slips/blob/master/slip-0173.md
if (chain === 'bitcoin') {
switch (network) {
case 'mainnet':
prefix = 'lnbc'
break
case 'testnet':
prefix = 'lntb'
break
case 'regtest':
prefix = 'lnbcrt'
break
}
} else if (chain === 'litecoin') {
switch (network) {
case 'mainnet':
prefix = 'lnltc'
break
case 'testnet':
prefix = 'lntltc'
break
case 'regtest':
prefix = 'lnrltc'
break
}
}
if (!input.startsWith(prefix)) {
return false
}
try {
bech32.decode(input)
return true
} catch (e) {
return false
}
}

1
package.json

@ -349,7 +349,6 @@
"source-map-support": "^0.5.9",
"split2": "^3.0.0",
"styled-components": "^4.0.3",
"styled-system-html": "^2.0.2",
"tildify": "^1.2.0",
"untildify": "^3.0.3",
"validator": "^10.8.0"

4
stories/components/background.stories.js

@ -1,8 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import BackgroundDark from 'components/UI/BackgroundDark'
import BackgroundLight from 'components/UI/BackgroundLight'
import BackgroundLightest from 'components/UI/BackgroundLightest'
import { BackgroundDark, BackgroundLight, BackgroundLightest } from 'components/UI'
storiesOf('Components.Background', module)
.add('dark', () => (

2
stories/components/bar.stories.js

@ -1,5 +1,5 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Bar from 'components/UI/Bar'
import { Bar } from 'components/UI'
storiesOf('Components.Bar', module).add('bar', () => <Bar />)

2
stories/components/button.stories.js

@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Button from 'components/UI/Button'
import { Button } from 'components/UI'
import SystemNavPrevious from 'components/Icon/SystemNavPrevious'
import SystemNavNext from 'components/Icon/SystemNavNext'

2
stories/components/dropdown.stories.js

@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { StateDecorator, Store } from '@sambego/storybook-state'
import Dropdown from 'components/UI/Dropdown'
import { Dropdown } from 'components/UI'
const store = new Store({
crypto: 'btc',

94
stories/components/form.stories.js

@ -2,16 +2,19 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { Box } from 'rebass'
import Page from 'components/UI/Page'
import MainContent from 'components/UI/MainContent'
import Input from 'components/UI/Input'
import Label from 'components/UI/Label'
import Select from 'components/UI/Select'
import TextArea from 'components/UI/TextArea'
import Button from 'components/UI/Button'
import Toggle from 'components/UI/Toggle'
import Range from 'components/UI/Range'
import { Form } from 'informed'
import {
Page,
MainContent,
Input,
Label,
LightningInvoiceInput,
Select,
TextArea,
Button,
Toggle,
Range
} from 'components/UI'
const validate = value => {
return !value || value.length < 5 ? 'Field must be at least five characters' : null
@ -42,6 +45,40 @@ storiesOf('Components.Form', module)
<TextArea field="fieldName" placeholder="Type here" />
</Form>
))
.add('Lightning Invoice Textarea', () => (
<React.Fragment>
<Box my={4}>
<Box>
<Label htmlFor="testnet">Bitcoin or Lightning address (testnet)</Label>
</Box>
<Form id="testnet">
<LightningInvoiceInput
chain="bitcoin"
network="testnet"
field="testnet"
id="testnet"
validateOnBlur
validateOnChange
/>
</Form>
</Box>
<Box>
<Box>
<Label htmlFor="mainnet">Bitcoin or Lightning address (mainnet)</Label>
</Box>
<Form id="testnet">
<LightningInvoiceInput
chain="bitcoin"
network="mainnet"
field="mainnet"
id="mainnet"
validateOnBlur
validateOnChange
/>
</Form>
</Box>
</React.Fragment>
))
.add('Select', () => (
<Form>
<Select field="fieldName" items={selectItems} />
@ -63,7 +100,7 @@ storiesOf('Components.Form', module)
<Form>
{({ formState }) => (
<React.Fragment>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="input1">Example Field</Label>
</Box>
@ -78,7 +115,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="textarea1">Example Textarea</Label>
</Box>
@ -92,7 +129,34 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="testnet">Bitcoin or Lightning address (testnet)</Label>
</Box>
<LightningInvoiceInput
chain="bitcoin"
network="testnet"
field="testnet"
id="testnet"
validateOnBlur
validateOnChange
/>
</Box>
<Box>
<Box>
<Label htmlFor="mainnet">Bitcoin or Lightning address (mainnet)</Label>
</Box>
<LightningInvoiceInput
chain="bitcoin"
network="mainnet"
field="mainnet"
id="mainnet"
validateOnBlur
validateOnChange
/>
</Box>
<Box my={4}>
<Box>
<Label htmlFor="selectfield1">Example Select</Label>
</Box>
@ -107,7 +171,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="checkbox1">Example Toggle</Label>
</Box>
@ -116,7 +180,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="slider1">Example Range</Label>
</Box>
@ -125,7 +189,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Button>Submit</Button>
</Box>

4
stories/components/menu.stories.js

@ -1,9 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Menu from 'components/UI/Menu'
import MenuItem from 'components/UI/MenuItem'
import MenuItemGroup from 'components/UI/MenuItemGroup'
import { Menu, MenuItem, MenuItemGroup } from 'components/UI'
storiesOf('Components.Menu', module).add('Menu', () => (
<Menu onSelect={action('select')}>

2
stories/components/modal.stories.js

@ -1,6 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Modal from 'components/UI/Modal'
import { Modal } from 'components/UI'
storiesOf('Components.Modal', module).add('Modal', () => (
<Modal>

2
stories/components/notification.stories.js

@ -1,6 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Notification from 'components/UI/Notification'
import { Notification } from 'components/UI'
storiesOf('Components.Notification', module)
.add('Success', () => <Notification variant="success">Success message</Notification>)

4
stories/components/page-elements.stories.js

@ -1,8 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Page from 'components/UI/Page'
import MainContent from 'components/UI/MainContent'
import Sidebar from 'components/UI/Sidebar'
import { Page, MainContent, Sidebar } from 'components/UI'
storiesOf('Components.Layouts', module)
.add('MainContent', () => <MainContent>Main content</MainContent>)

2
stories/components/spinner.stories.js

@ -1,6 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Spinner from 'components/UI/Spinner'
import { Spinner } from 'components/UI'
storiesOf('Components.Spinners', module).add('circle', () => <Spinner />)

3
stories/components/typography.stories.js

@ -1,7 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import Heading from 'components/UI/Heading'
import Text from 'components/UI/Text'
import { Heading, Text } from 'components/UI'
storiesOf('Components.Typography', module)
.add('heading', () => (

2
test/unit/components/UI/BackgroundDark.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import BackgroundDark from 'components/UI/BackgroundDark'
import renderer from 'react-test-renderer'
import { BackgroundDark } from 'components/UI'
describe('component.UI.BackgroundDark', () => {
it('should render correctly', () => {

2
test/unit/components/UI/BackgroundLight.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import BackgroundLight from 'components/UI/BackgroundLight'
import renderer from 'react-test-renderer'
import { BackgroundLight } from 'components/UI'
describe('component.UI.BackgroundLight', () => {
it('should render correctly', () => {

2
test/unit/components/UI/BackgroundLightest.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import BackgroundLightest from 'components/UI/BackgroundLightest'
import renderer from 'react-test-renderer'
import { BackgroundLightest } from 'components/UI'
describe('component.UI.BackgroundLightest', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Bar.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Bar from 'components/UI/Bar'
import renderer from 'react-test-renderer'
import { Bar } from 'components/UI'
describe('component.UI.Bar', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Button.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Button from 'components/UI/Button'
import renderer from 'react-test-renderer'
import { Button } from 'components/UI'
describe('component.UI.Button', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Dropdown.spec.js

@ -1,5 +1,5 @@
import React from 'react'
import Dropdown from 'components/UI/Dropdown'
import { Dropdown } from 'components/UI'
import renderer from 'react-test-renderer'
import { dark } from 'themes'

2
test/unit/components/UI/Heading.spec.js

@ -1,8 +1,8 @@
import React from 'react'
import Heading from 'components/UI/Heading'
import renderer from 'react-test-renderer'
import { configure, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { Heading } from 'components/UI'
configure({ adapter: new Adapter() })

2
test/unit/components/UI/Input.spec.js

@ -1,9 +1,9 @@
import React from 'react'
import { Form } from 'informed'
import Input from 'components/UI/Input'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Input } from 'components/UI'
describe('component.UI.Input', () => {
it('should render correctly', () => {

21
test/unit/components/UI/LightningInvoiceInput.spec.js

@ -0,0 +1,21 @@
import React from 'react'
import { Form } from 'informed'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { LightningInvoiceInput } from 'components/UI'
describe('component.UI.LightningInvoiceInput', () => {
it('should render correctly', () => {
const tree = renderer
.create(
<ThemeProvider theme={dark}>
<Form>
<LightningInvoiceInput field="name" chain="bitcoin" network="mainnet" theme={dark} />
</Form>
</ThemeProvider>
)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

2
test/unit/components/UI/MainContent.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import MainContent from 'components/UI/MainContent'
import renderer from 'react-test-renderer'
import { MainContent } from 'components/UI'
describe('component.UI.MainContent', () => {
it('should render correctly', () => {

4
test/unit/components/UI/Menu.spec.js

@ -1,10 +1,8 @@
import React from 'react'
import Menu from 'components/UI/Menu'
import MenuItem from 'components/UI/MenuItem'
import MenuItemGroup from 'components/UI/MenuItemGroup'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Menu, MenuItem, MenuItemGroup } from 'components/UI'
describe('component.UI.Menu', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Modal.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Modal from 'components/UI/Modal'
import renderer from 'react-test-renderer'
import { Modal } from 'components/UI'
describe('component.UI.Modal', () => {
it('should render correctly', () => {

3
test/unit/components/UI/Notification.spec.js

@ -1,12 +1,11 @@
import React from 'react'
import Notification from 'components/UI/Notification'
import renderer from 'react-test-renderer'
import { configure, shallow } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import SystemSuccess from 'components/Icon/SystemSuccess'
import SystemWarning from 'components/Icon/SystemWarning'
import SystemError from 'components/Icon/SystemError'
import Spinner from 'components/UI/Spinner'
import { Notification, Spinner } from 'components/UI'
configure({ adapter: new Adapter() })

2
test/unit/components/UI/Page.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Page from 'components/UI/Page'
import renderer from 'react-test-renderer'
import { Page } from 'components/UI'
describe('component.UI.Page', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Range.spec.js

@ -1,9 +1,9 @@
import React from 'react'
import { Form } from 'informed'
import Range from 'components/UI/Range'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Range } from 'components/UI'
describe('component.UI.Range', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Select.spec.js

@ -1,9 +1,9 @@
import React from 'react'
import { Form } from 'informed'
import Select from 'components/UI/Select'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Select } from 'components/UI'
describe('component.UI.Toggle', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Sidebar.spec.js

@ -1,8 +1,8 @@
import React from 'react'
import Sidebar from 'components/UI/Sidebar'
import renderer from 'react-test-renderer'
import { configure, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import { Sidebar } from 'components/UI'
configure({ adapter: new Adapter() })

2
test/unit/components/UI/Spinner.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Spinner from 'components/UI/Spinner'
import renderer from 'react-test-renderer'
import { Spinner } from 'components/UI'
describe('component.UI.Spinner', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Text.spec.js

@ -1,6 +1,6 @@
import React from 'react'
import Text from 'components/UI/Text'
import renderer from 'react-test-renderer'
import { Text } from 'components/UI'
describe('component.UI.Text', () => {
it('should render correctly', () => {

2
test/unit/components/UI/TextArea.spec.js

@ -1,9 +1,9 @@
import React from 'react'
import { Form } from 'informed'
import Input from 'components/UI/Input'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Input } from 'components/UI'
describe('component.UI.Input', () => {
it('should render correctly', () => {

2
test/unit/components/UI/Toggle.spec.js

@ -1,9 +1,9 @@
import React from 'react'
import { Form } from 'informed'
import Toggle from 'components/UI/Toggle'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { Toggle } from 'components/UI'
describe('component.UI.Toggle', () => {
it('should render correctly', () => {

45
test/unit/components/UI/__snapshots__/Input.spec.js.snap

@ -1,33 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`component.UI.Input should render correctly 1`] = `
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c1 {
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
}
.c1:focus {
outline: none;
border: 1px solid #fd9800;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
<form
@ -39,12 +41,15 @@ exports[`component.UI.Input should render correctly 1`] = `
>
<input
className="c1"
name="name"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
outline="none"
type="text"
onFocus={[Function]}
value=""
width={1}
/>
</div>
</form>

60
test/unit/components/UI/__snapshots__/LightningInvoiceInput.spec.js.snap

@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`component.UI.LightningInvoiceInput should render correctly 1`] = `
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c1 {
padding: 16px;
width: 100%;
font-size: 13px;
color: #fff;
background-color: transparent;
color: #fff;
background-color: transparent;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
outline: none;
}
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
<form
onReset={[Function]}
onSubmit={[Function]}
>
<div
className="c0"
>
<textarea
className="c1"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
opacity={null}
placeholder="Paste a Lightning Payment Request or Bitcoin Address here"
required={false}
rows={5}
value=""
width={1}
/>
</div>
</form>
`;

44
test/unit/components/UI/__snapshots__/Select.spec.js.snap

@ -1,33 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`component.UI.Toggle should render correctly 1`] = `
.c1 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c2 {
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
}
.c2:focus {
outline: none;
border: 1px solid #fd9800;
}
.c1 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
.c2:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
.c3 {
@ -132,17 +134,19 @@ exports[`component.UI.Toggle should render correctly 1`] = `
aria-labelledby="downshift-0-label"
autoComplete="off"
className="c2"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
id="downshift-0-input"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
outline="none"
placeholder="Please select"
type="text"
value=""
width={1}
/>
</div>
</div>

45
test/unit/components/UI/__snapshots__/TextArea.spec.js.snap

@ -1,33 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`component.UI.Input should render correctly 1`] = `
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c1 {
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
}
.c1:focus {
outline: none;
border: 1px solid #fd9800;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
<form
@ -39,12 +41,15 @@ exports[`component.UI.Input should render correctly 1`] = `
>
<input
className="c1"
name="name"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
outline="none"
type="text"
onFocus={[Function]}
value=""
width={1}
/>
</div>
</form>

100
test/unit/utils/crypto.spec.js

@ -0,0 +1,100 @@
/* eslint-disable max-len */
import { isLn, isOnchain } from 'lib/utils/crypto'
const VALID_BITCOIN_MAINNET_LN =
'lnbc10u1pduey89pp57gt0mqvh9gv4m5kkxmy9a0a46ha5jlzr3mcfcz2fx8tzu63vpjksdq8w3jhxaqcqzystfg0drarrx89nvpegwykvfr4fypvwz2d9ktcr6tj5s08f0nn8gdjnv74y9amksk3rjw7englhjrsev70k77vwf603qh2pr4tnqeue6qp5n92gy'
const VALID_BITCOIN_TESTNET_LN =
'lntb10u1pdue0gxpp5uljstna5aenp3yku3ft4wn8y63qfqdgqfh4cqqxz8z58undqp8hqdqqcqzysxqyz5vqqwaeuuh0fy52tqx6rrq6kya4lwm6v523wyqe9nesd5a3mszcq7j4e9mv8rd2vhmp7ycxswtktvs8gqq8lu5awjwfevnvfc4rzp8fmacpp4h27e'
const VALID_LITECOIN_MAINNET_LN =
'lnltc241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66859t2d55efrxdlgqg9hdqskfstdmyssdw4fjc8qdl522ct885pqk7acn2aczh0jeht0xhuhnkmm3h0qsrxedlwm9x86787zzn4qwwwcpjkl3t2'
const VALID_LITECOIN_TESTNET_LN =
'lntltc241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66m2eq2fx9uctzkmj30meaghyskkgsd6geap5qg9j2ae444z24a4p8xg3a6g73p8l7d689vtrlgzj0wyx2h6atq8dfty7wmkt4frx9g9sp730h5a'
describe('Crypto.isLn', () => {
describe('Bitcoin', () => {
describe('Mainnet', () => {
it('should pass with a valid invoice ', () => {
expect(isLn(VALID_BITCOIN_MAINNET_LN, 'bitcoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_BITCOIN_TESTNET_LN, 'bitcoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid invoice', () => {
expect(isLn(VALID_BITCOIN_TESTNET_LN, 'bitcoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_BITCOIN_MAINNET_LN, 'bitcoin', 'testnet')).toBeFalsy()
})
})
})
describe('Litecoin', () => {
describe('Mainnet', () => {
it('should pass with a valid invoice ', () => {
expect(isLn(VALID_LITECOIN_MAINNET_LN, 'litecoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_LITECOIN_TESTNET_LN, 'litecoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid invoice', () => {
expect(isLn(VALID_LITECOIN_TESTNET_LN, 'litecoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_LITECOIN_MAINNET_LN, 'litecoin', 'testnet')).toBeFalsy()
})
})
})
})
const VALID_BITCOIN_MAINNET = '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC'
const VALID_BITCOIN_TESTNET = '2NBMEX8USTXf5uPiW16QYX2AETytrJBCK52'
const VALID_LITECOIN_MAINNET = 'LW9Q7QU8dgaGBerVkxbmVfurr2E1cFudrn'
const VALID_LITECOIN_TESTNET = '2MvCEgZGPnwA5HmZ3GDXB4EteMZzxgS54it'
describe('Crypto.isOnchain', () => {
describe('Bitcoin', () => {
describe('Mainnet', () => {
it('should pass with a valid address ', () => {
expect(isOnchain(VALID_BITCOIN_MAINNET, 'bitcoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid address ', () => {
expect(isOnchain(VALID_BITCOIN_TESTNET, 'bitcoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid address', () => {
expect(isOnchain(VALID_BITCOIN_TESTNET, 'bitcoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid address ', () => {
expect(isOnchain(VALID_BITCOIN_MAINNET, 'bitcoin', 'testnet')).toBeFalsy()
})
})
})
describe('Litecoin', () => {
describe('Mainnet', () => {
it('should pass with a valid address ', () => {
expect(isOnchain(VALID_LITECOIN_MAINNET, 'litecoin', 'mainnet')).toBeTruthy()
})
// FIXME: TWe don't yet fully support litecoin, so this check always returns true for litecoin addresses
it('should pass with an invalid address ', () => {
expect(isOnchain(VALID_LITECOIN_TESTNET, 'litecoin', 'mainnet')).toBeTruthy()
})
})
describe('Testnet', () => {
it('should pass with a valid address', () => {
expect(isOnchain(VALID_LITECOIN_TESTNET, 'litecoin', 'testnet')).toBeTruthy()
})
// FIXME: TWe don't yet fully support litecoin, so this check always returns true for litecoin addresses
it('should pass with an invalid address ', () => {
expect(isOnchain(VALID_LITECOIN_MAINNET, 'litecoin', 'testnet')).toBeTruthy()
})
})
})
})

47
yarn.lock

@ -4263,7 +4263,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.0.3, buffer@^5.1.0:
buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==
@ -5460,7 +5460,7 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
css-to-react-native@^2.0.3, css-to-react-native@^2.2.2:
css-to-react-native@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965"
integrity sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw==
@ -7278,7 +7278,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
fbjs@^0.8.12, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
fbjs@^0.8.12, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
@ -13333,16 +13333,16 @@ react-intl@^2.7.2:
intl-relativeformat "^2.1.0"
invariant "^2.1.1"
react-is@^16.3.1, react-is@^16.5.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
react-is@^16.3.2, react-is@^16.6.0:
version "16.6.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.0.tgz#456645144581a6e99f6816ae2bd24ee94bdd0c01"
integrity sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==
react-is@^16.5.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -15298,21 +15298,6 @@ style-search@^0.1.0:
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
styled-components@^3.4.5:
version "3.4.10"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1"
integrity sha512-TA8ip8LoILgmSAFd3r326pKtXytUUGu5YWuqZcOQVwVVwB6XqUMn4MHW2IuYJ/HAD81jLrdQed8YWfLSG1LX4Q==
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.16"
hoist-non-react-statics "^2.5.0"
prop-types "^15.5.4"
react-is "^16.3.1"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
styled-components@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.0.3.tgz#6c1a95a93857aa613fdfc26ad40899217100d8c3"
@ -15328,15 +15313,6 @@ styled-components@^4.0.3:
stylis-rule-sheet "^0.0.10"
supports-color "^5.5.0"
styled-system-html@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/styled-system-html/-/styled-system-html-2.0.2.tgz#2a95f7720eea44b33d0e29c77289f070fe3dd318"
integrity sha512-QtqV+xc16ogG9ni+k/ZLytQ7Q5mpd+jCd1SioUUgm/6YaptSRop5+2qiKLL4acUxIV3liKuZbGyhxGsWeA0Dlg==
dependencies:
html-tags "^2.0.0"
styled-components "^3.4.5"
system-components "^3.0.0"
styled-system@^3.0.1, styled-system@^3.1.4:
version "3.1.11"
resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-3.1.11.tgz#a91a38cf7a0f0e625b897a04fdd506a359a3629f"
@ -15551,13 +15527,6 @@ symbol.prototype.description@^1.0.0:
dependencies:
has-symbols "^1.0.0"
system-components@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/system-components/-/system-components-3.0.1.tgz#a4e7d8a7b184aa0612245a4f1bd54b408132e0c0"
integrity sha512-9awqnq7esNnqgmOOqU4i0w/9/YsJFm+1IS9lAyuk6a52IrcdToFqH7vFJmnr56FNLsXjOFohonmeHs0QOmvWeQ==
dependencies:
styled-system "^3.0.1"
table@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"

Loading…
Cancel
Save