Browse Source

Merge pull request #440 from mrfelton/build/developer-workflow

Developer workflow improvements
renovate/lint-staged-8.x
Ben Woosley 6 years ago
committed by GitHub
parent
commit
2e6e0a8f3d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .commitlintrc
  2. 51
      .eslintrc
  3. 6
      .gitignore
  4. 6
      .huskyrc
  5. 6
      .lintstagedrc
  6. 22
      .prettierignore
  7. 15
      .prettierrc
  8. 3
      app/api/index.js
  9. 12
      app/components/Activity/ActivityModal.js
  10. 35
      app/components/Activity/Countdown.js
  11. 38
      app/components/Activity/InvoiceModal.js
  12. 25
      app/components/Activity/PaymentModal.js
  13. 38
      app/components/Activity/TransactionModal.js
  14. 56
      app/components/Contacts/AddChannel.js
  15. 5
      app/components/Contacts/ChannelForm.js
  16. 13
      app/components/Contacts/ConnectManually.js
  17. 48
      app/components/Contacts/ContactModal.js
  18. 95
      app/components/Contacts/ContactsForm.js
  19. 173
      app/components/Contacts/Network.js
  20. 46
      app/components/Contacts/SubmitChannelForm.js
  21. 37
      app/components/Contacts/SuggestedNodes.js
  22. 7
      app/components/CurrencyIcon/CurrencyIcon.js
  23. 5
      app/components/Form/Form.js
  24. 106
      app/components/Form/Pay.js
  25. 48
      app/components/Form/Request.js
  26. 4
      app/components/Onboarding/Alias.js
  27. 28
      app/components/Onboarding/ConnectionDetails.js
  28. 29
      app/components/Onboarding/FormContainer.js
  29. 9
      app/components/Onboarding/InitWallet.js
  30. 23
      app/components/Onboarding/Login.js
  31. 8
      app/components/Onboarding/NewAezeedPassword.js
  32. 8
      app/components/Onboarding/NewWalletPassword.js
  33. 22
      app/components/Onboarding/NewWalletSeed.js
  34. 54
      app/components/Onboarding/Onboarding.js
  35. 37
      app/components/Onboarding/ReEnterSeed.js
  36. 13
      app/components/Onboarding/RecoverForm.js
  37. 22
      app/components/Onboarding/Signup.js
  38. 6
      app/components/Onboarding/Syncing.js
  39. 13
      app/components/Value/Value.js
  40. 36
      app/components/Wallet/ReceiveModal.js
  41. 72
      app/components/Wallet/Wallet.js
  42. 6
      app/containers/Root.js
  43. 4
      app/lnd/index.js
  44. 9
      app/lnd/lib/lightning.js
  45. 9
      app/lnd/lib/walletUnlocker.js
  46. 16
      app/lnd/methods/channelController.js
  47. 35
      app/lnd/methods/index.js
  48. 18
      app/lnd/methods/invoicesController.js
  49. 24
      app/lnd/methods/networkController.js
  50. 16
      app/lnd/methods/paymentsController.js
  51. 14
      app/lnd/methods/peersController.js
  52. 60
      app/lnd/methods/walletController.js
  53. 2
      app/lnd/subscribe/transactions.js
  54. 11
      app/lnd/walletUnlockerMethods/index.js
  55. 10
      app/main.dev.js
  56. 228
      app/menu.js
  57. 67
      app/reducers/activity.js
  58. 2
      app/reducers/address.js
  59. 15
      app/reducers/balance.js
  60. 151
      app/reducers/channels.js
  61. 74
      app/reducers/contactsform.js
  62. 2
      app/reducers/error.js
  63. 6
      app/reducers/info.js
  64. 43
      app/reducers/invoice.js
  65. 10
      app/reducers/ipc.js
  66. 25
      app/reducers/lnd.js
  67. 84
      app/reducers/network.js
  68. 18
      app/reducers/onboarding.js
  69. 115
      app/reducers/payform.js
  70. 14
      app/reducers/payment.js
  71. 41
      app/reducers/peers.js
  72. 6
      app/reducers/requestform.js
  73. 42
      app/reducers/ticker.js
  74. 33
      app/reducers/transaction.js
  75. 2
      app/routes.js
  76. 120
      app/routes/activity/components/Activity.js
  77. 34
      app/routes/activity/components/components/Invoice/Invoice.js
  78. 26
      app/routes/activity/components/components/Payment/Payment.js
  79. 22
      app/routes/activity/components/components/Transaction/Transaction.js
  80. 18
      app/routes/activity/containers/ActivityContainer.js
  81. 25
      app/routes/app/components/App.js
  82. 48
      app/routes/app/containers/AppContainer.js
  83. 11
      app/store/configureStore.dev.js
  84. 12
      app/utils/blockExplorer.js
  85. 3
      app/utils/log.js
  86. 4
      app/utils/usd.js
  87. 22
      package.json
  88. 2
      test/e2e/e2e.spec.js
  89. 5
      test/reducers/balance.spec.js
  90. 5
      test/reducers/info.spec.js
  91. 7
      test/reducers/ticker.spec.js
  92. 4
      test/runTests.js
  93. 5
      test/utils/usd.spec.js
  94. 23
      webpack.config.base.js
  95. 10
      webpack.config.renderer.dev.dll.js
  96. 6
      webpack.config.renderer.dev.js
  97. 22
      webpack.config.renderer.prod.js
  98. 774
      yarn.lock

3
.commitlintrc

@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

51
.eslintrc

@ -12,7 +12,11 @@
"plugin:import/errors", "plugin:import/errors",
"plugin:import/warnings", "plugin:import/warnings",
"plugin:jsx-a11y/strict", "plugin:jsx-a11y/strict",
"plugin:promise/recommended" "plugin:promise/recommended",
"prettier",
"prettier/flowtype",
"prettier/react",
"plugin:prettier/recommended"
], ],
"env": { "env": {
"browser": true, "browser": true,
@ -21,42 +25,45 @@
"rules": { "rules": {
"comma-dangle": ["error", "never"], "comma-dangle": ["error", "never"],
"semi": ["error", "never"], "semi": ["error", "never"],
"indent": 2, "prettier/prettier": "error",
"jsx-quotes": ["error", "prefer-single"],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/no-did-mount-set-state": 0, "react/no-did-mount-set-state": 0,
"jsx-a11y/no-static-element-interactions": 0, "jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/no-noninteractive-element-interactions": 0, "jsx-a11y/no-noninteractive-element-interactions": 0,
"jsx-a11y/click-events-have-key-events": 0, "jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/label-has-for": [ 2, { "jsx-a11y/label-has-for": [
"components": [ "Label" ], 2,
"required": { {
"every": [ "id" ] "components": ["Label"],
}, "required": {
"allowChildren": false "every": ["id"]
}], },
"allowChildren": false
}
],
"react/no-array-index-key": 0, "react/no-array-index-key": 0,
"react/forbid-prop-types": 0, "react/forbid-prop-types": 0,
"camelcase": 0, "camelcase": 0,
"curly": ["error", "all"],
"react/require-default-props": 0, "react/require-default-props": 0,
"max-len": ["error", 150], "max-len": ["error", { "code": 150, "ignoreUrls": true }],
"import/no-extraneous-dependencies": 0, "import/no-extraneous-dependencies": 0,
"no-confusing-arrow": "error",
"no-mixed-operators": "error",
"no-new": 0, "no-new": 0,
"no-tabs": "error",
"compat/compat": "error", "compat/compat": "error",
"prefer-destructuring": ["error", { "prefer-destructuring": [
"array": false, "error",
"object": true {
}], "array": false,
"object": true
}
],
"prefer-promise-reject-errors": 0, "prefer-promise-reject-errors": 0,
"no-param-reassign": [2, { "props": false }] "no-param-reassign": [2, { "props": false }]
}, },
"plugins": [ "plugins": ["flowtype", "import", "json", "markdown", "prettier", "promise", "compat", "react"],
"flowtype",
"import",
"promise",
"compat",
"react"
],
"settings": { "settings": {
"import/resolver": { "import/resolver": {
"node": { "node": {

6
.gitignore

@ -52,4 +52,8 @@ main.js.map
npm-debug.log.* npm-debug.log.*
# lnd binary # lnd binary
resources/bin/ resources/bin/
# Opt in files
.opt-in
.opt-out

6
.huskyrc

@ -0,0 +1,6 @@
{
"hooks": {
"commit-msg": "opt --in commit-msg --exec 'commitlint -E HUSKY_GIT_PARAMS'",
"pre-commit": "opt --in pre-commit --exec 'lint-staged'"
}
}

6
.lintstagedrc

@ -0,0 +1,6 @@
{
"linters": {
"*.{js,json,md}": ["npm run lint-fix-base", "git add"],
"*.scss": ["npm run lint-styles-fix-base", "git add"]
}
}

22
.prettierignore

@ -0,0 +1,22 @@
# App source
package.json
package-lock.json
.git
.babelrc
node_modules
coverage
# App packaged
release
app/main.prod.js
app/main.prod.js.map
app/renderer.prod.js
app/renderer.prod.js.map
app/style.css
app/style.css.map
app/utils/bech32.js
dist
dll
main.js
main.js.map

15
.prettierrc

@ -0,0 +1,15 @@
{
"bracketSpacing": true,
"printWidth": 150,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"overrides": [
{
"files": [".babelrc", ".commitlintrc", ".eslintrc", ".huskyrc", ".lintstagedrc", ".prettierrc", ".stylelintrc"],
"options": {
"parser": "json"
}
}
]
}

3
app/api/index.js

@ -11,7 +11,8 @@ export function requestTicker(id) {
} }
export function requestTickers(ids) { export function requestTickers(ids) {
return axios.all(ids.map(id => requestTicker(id))) return axios
.all(ids.map(id => requestTicker(id)))
.then(axios.spread((btcTicker, ltcTicker) => ({ btcTicker: btcTicker[0], ltcTicker: ltcTicker[0] }))) .then(axios.spread((btcTicker, ltcTicker) => ({ btcTicker: btcTicker[0], ltcTicker: ltcTicker[0] })))
} }

12
app/components/Activity/ActivityModal.js

@ -25,7 +25,9 @@ const ActivityModal = ({
INVOICE: InvoiceModal INVOICE: InvoiceModal
} }
if (!modalType) { return null } if (!modalType) {
return null
}
const SpecificModal = MODAL_COMPONENTS[modalType] const SpecificModal = MODAL_COMPONENTS[modalType]
return ( return (
@ -35,13 +37,7 @@ const ActivityModal = ({
<Isvg src={x} /> <Isvg src={x} />
</span> </span>
</div> </div>
<SpecificModal <SpecificModal {...modalProps} network={network} ticker={ticker} currentTicker={currentTicker} toggleCurrencyProps={toggleCurrencyProps} />
{...modalProps}
network={network}
ticker={ticker}
currentTicker={currentTicker}
toggleCurrencyProps={toggleCurrencyProps}
/>
</div> </div>
) )
} }

35
app/components/Activity/Countdown.js

@ -34,7 +34,8 @@ class Countdown extends React.Component {
const convertTwoDigits = n => (n > 9 ? n : `0${n}`.slice(-2)) const convertTwoDigits = n => (n > 9 ? n : `0${n}`.slice(-2))
const now = new Date().getTime() const now = new Date().getTime()
const distance = (this.props.countDownDate * 1000) - now const countDownSeconds = this.props.countDownDate * 1000
const distance = countDownSeconds - now
if (distance <= 0) { if (distance <= 0) {
this.setState({ expired: true }) this.setState({ expired: true })
@ -56,32 +57,22 @@ class Countdown extends React.Component {
} }
render() { render() {
const { const { days, hours, minutes, seconds, expired } = this.state
days,
hours,
minutes,
seconds,
expired
} = this.state
if (expired) { return <span className={`${styles.container} ${styles.expired}`}>Expired</span> } if (expired) {
if (!days && !hours && !minutes && !seconds) { return <span className={styles.container} /> } return <span className={`${styles.container} ${styles.expired}`}>Expired</span>
}
if (!days && !hours && !minutes && !seconds) {
return <span className={styles.container} />
}
return ( return (
<span className={styles.container}> <span className={styles.container}>
<i className={styles.caption}>Expires in</i> <i className={styles.caption}>Expires in</i>
<i> <i>{days > 0 && `${days}:`}</i>
{days > 0 && `${days}:`} <i>{hours > 0 && `${hours}:`}</i>
</i> <i>{minutes > 0 && `${minutes}:`}</i>
<i> <i>{seconds >= 0 && `${seconds}`}</i>
{hours > 0 && `${hours}:`}
</i>
<i>
{minutes > 0 && `${minutes}:`}
</i>
<i>
{seconds >= 0 && `${seconds}`}
</i>
</span> </span>
) )
} }

38
app/components/Activity/InvoiceModal.js

@ -20,20 +20,14 @@ const InvoiceModal = ({
ticker, ticker,
currentTicker, currentTicker,
toggleCurrencyProps: { toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
}) => { }) => {
const copyPaymentRequest = () => { const copyPaymentRequest = () => {
copy(invoice.payment_request) copy(invoice.payment_request)
showNotification('Noice', 'Successfully copied to clipboard') showNotification('Noice', 'Successfully copied to clipboard')
} }
const countDownDate = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)) const countDownDate = parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)
return ( return (
<div className={styles.container}> <div className={styles.container}>
@ -42,11 +36,11 @@ const InvoiceModal = ({
<h2>Payment Request</h2> <h2>Payment Request</h2>
<QRCode <QRCode
value={invoice.payment_request} value={invoice.payment_request}
renderAs='svg' renderAs="svg"
size={150} size={150}
bgColor='transparent' bgColor="transparent"
fgColor='white' fgColor="white"
level='L' level="L"
className={styles.qrcode} className={styles.qrcode}
/> />
<Countdown countDownDate={countDownDate} /> <Countdown countDownDate={countDownDate} />
@ -58,22 +52,24 @@ const InvoiceModal = ({
<Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} /> <Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} />
</h1> </h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section> </section>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</section> </section>
<section className={styles.date}> <section className={styles.date}>
<p> <p>
<Moment format='MM/DD/YYYY'>{invoice.creation_date * 1000}</Moment> <Moment format="MM/DD/YYYY">{invoice.creation_date * 1000}</Moment>
</p>
<p className={styles.notPaid}>
{!invoice.settled && 'Not Paid'}
</p> </p>
<p className={styles.notPaid}>{!invoice.settled && 'Not Paid'}</p>
</section> </section>
</div> </div>

25
app/components/Activity/PaymentModal.js

@ -19,13 +19,7 @@ const PaymentModal = ({
ticker, ticker,
currentTicker, currentTicker,
toggleCurrencyProps: { toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
}) => ( }) => (
<div className={styles.container}> <div className={styles.container}>
<header className={styles.header}> <header className={styles.header}>
@ -51,19 +45,22 @@ const PaymentModal = ({
<Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} /> <Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} />
</h1> </h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</section> </section>
</div> </div>
<div className={styles.date}> <div className={styles.date}>
<Moment format='LLL'>{payment.creation_date * 1000}</Moment> <Moment format="LLL">{payment.creation_date * 1000}</Moment>
</div> </div>
<footer className={styles.footer}> <footer className={styles.footer}>

38
app/components/Activity/TransactionModal.js

@ -21,13 +21,7 @@ const TransactionModal = ({
currentTicker, currentTicker,
network, network,
toggleCurrencyProps: { toggleCurrencyProps: { setActivityModalCurrencyFilters, showCurrencyFilters, currencyName, currentCurrencyFilters, onCurrencyFilterClick }
setActivityModalCurrencyFilters,
showCurrencyFilters,
currencyName,
currentCurrencyFilters,
onCurrencyFilterClick
}
}) => ( }) => (
<div className={styles.container}> <div className={styles.container}>
<header className={styles.header}> <header className={styles.header}>
@ -38,7 +32,9 @@ const TransactionModal = ({
<section className={styles.details}> <section className={styles.details}>
<div> <div>
<Isvg src={link} /> <Isvg src={link} />
<span className={styles.link} onClick={() => blockExplorer.showTransaction(network, transaction.tx_hash)}>On-Chain</span> <span className={styles.link} onClick={() => blockExplorer.showTransaction(network, transaction.tx_hash)}>
On-Chain
</span>
</div> </div>
<div> <div>
<Value value={transaction.total_fees} currency={ticker.currency} currentTicker={currentTicker} /> <Value value={transaction.total_fees} currency={ticker.currency} currentTicker={currentTicker} />
@ -49,30 +45,26 @@ const TransactionModal = ({
<div className={styles.amount}> <div className={styles.amount}>
<h1> <h1>
<i className={`${styles.symbol} ${transaction.amount > 0 && styles.active}`}> <i className={`${styles.symbol} ${transaction.amount > 0 && styles.active}`}>{transaction.amount > 0 ? '+' : '-'}</i>
{
transaction.amount > 0 ?
'+'
:
'-'
}
</i>
<Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} /> <Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} />
</h1> </h1>
<section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setActivityModalCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</section> </section>
</div> </div>
<div className={styles.date}> <div className={styles.date}>
<Moment format='LLL'>{transaction.time_stamp * 1000}</Moment> <Moment format="LLL">{transaction.time_stamp * 1000}</Moment>
</div> </div>
<footer className={styles.footer}> <footer className={styles.footer}>

56
app/components/Contacts/AddChannel.js

@ -21,7 +21,7 @@ const AddChannel = ({
showManualForm, showManualForm,
openManualForm openManualForm
}) => { }) => {
const renderRightSide = (node) => { const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) { if (loadingChannelPubkeys.includes(node.pub_key)) {
return ( return (
<span className={styles.inactive}> <span className={styles.inactive}>
@ -57,11 +57,7 @@ const AddChannel = ({
} }
if (!node.addresses.length) { if (!node.addresses.length) {
return ( return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
<span className={`${styles.private} ${styles.inactive}`}>
Private
</span>
)
} }
return ( return (
@ -79,7 +75,7 @@ const AddChannel = ({
) )
} }
const searchUpdated = (search) => { const searchUpdated = search => {
updateContactFormSearchQuery(search) updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) { if (search.includes('@') && search.split('@')[0].length === 66) {
@ -91,8 +87,8 @@ const AddChannel = ({
<div className={styles.container}> <div className={styles.container}>
<header className={styles.header}> <header className={styles.header}>
<input <input
type='text' type="text"
placeholder='Search the network...' placeholder="Search the network..."
className={styles.searchInput} className={styles.searchInput}
value={contactsform.searchQuery} value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)} onChange={event => searchUpdated(event.target.value)}
@ -105,38 +101,36 @@ const AddChannel = ({
<section className={styles.nodes}> <section className={styles.nodes}>
<ul className={styles.networkResults}> <ul className={styles.networkResults}>
{ {filteredNetworkNodes.map(node => (
filteredNetworkNodes.map(node => (
<li key={node.pub_key}> <li key={node.pub_key}>
<section> <section>
{ {node.alias.length > 0 ? (
node.alias.length > 0 ? <h2>
<h2> <span>{node.alias.trim()}</span>
<span>{node.alias.trim()}</span> <span>
<span>({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})</span> ({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})
</h2> </span>
: </h2>
<h2> ) : (
<span>{node.pub_key}</span> <h2>
</h2> <span>{node.pub_key}</span>
} </h2>
</section> )}
<section>
{renderRightSide(node)}
</section> </section>
<section>{renderRightSide(node)}</section>
</li> </li>
)) ))}
}
</ul> </ul>
</section> </section>
{ {showManualForm && (
showManualForm &&
<section className={styles.manualForm}> <section className={styles.manualForm}>
<p>Hm, looks like we can&apos;t see that node from here, wanna try to manually connect?</p> <p>Hm, looks like we can&apos;t see that node from here, wanna try to manually connect?</p>
<div className={styles.manualConnectButton} onClick={openManualForm}>Connect Manually</div> <div className={styles.manualConnectButton} onClick={openManualForm}>
Connect Manually
</div>
</section> </section>
} )}
</div> </div>
) )
} }

5
app/components/Contacts/ChannelForm.js

@ -15,7 +15,9 @@ const FORM_TYPES = {
} }
const ChannelForm = ({ formType, formProps, closeForm }) => { const ChannelForm = ({ formType, formProps, closeForm }) => {
if (!formType) { return null } if (!formType) {
return null
}
const FormComponent = FORM_TYPES[formType] const FormComponent = FORM_TYPES[formType]
return ( return (
@ -30,7 +32,6 @@ const ChannelForm = ({ formType, formProps, closeForm }) => {
) )
} }
ChannelForm.propTypes = { ChannelForm.propTypes = {
formType: PropTypes.string, formType: PropTypes.string,
formProps: PropTypes.object.isRequired, formProps: PropTypes.object.isRequired,

13
app/components/Contacts/ConnectManually.js

@ -55,8 +55,8 @@ class ConnectManually extends React.Component {
<section className={styles.peer}> <section className={styles.peer}>
<div className={styles.input}> <div className={styles.input}>
<input <input
type='text' type="text"
placeholder='pubkey@host' placeholder="pubkey@host"
value={manualSearchQuery} value={manualSearchQuery}
onChange={event => updateManualFormSearchQuery(event.target.value)} onChange={event => updateManualFormSearchQuery(event.target.value)}
/> />
@ -64,16 +64,11 @@ class ConnectManually extends React.Component {
</section> </section>
<section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}> <section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}>
{showErrors.manualInput && {showErrors.manualInput && <span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>}
<span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>
}
</section> </section>
<section className={styles.submit}> <section className={styles.submit}>
<div <div className={`${styles.button} ${manualFormIsValid.isValid && styles.active}`} onClick={formSubmitted}>
className={`${styles.button} ${manualFormIsValid.isValid && styles.active}`}
onClick={formSubmitted}
>
Submit Submit
</div> </div>
</section> </section>

48
app/components/Contacts/ContactModal.js

@ -9,15 +9,10 @@ import { btc } from 'utils'
import styles from './ContactModal.scss' import styles from './ContactModal.scss'
const ContactModal = ({ const ContactModal = ({ isOpen, channel, closeContactModal, channelNodes, closeChannel, closingChannelIds }) => {
isOpen, if (!channel) {
channel, return <span />
closeContactModal, }
channelNodes,
closeChannel,
closingChannelIds
}) => {
if (!channel) { return <span /> }
const customStyles = { const customStyles = {
overlay: { overlay: {
@ -46,22 +41,19 @@ const ContactModal = ({
return ( return (
<ReactModal <ReactModal
isOpen={isOpen} isOpen={isOpen}
contentLabel='No Overlay Click Modal' contentLabel="No Overlay Click Modal"
ariaHideApp ariaHideApp
shouldCloseOnOverlayClick shouldCloseOnOverlayClick
onRequestClose={closeContactModal} onRequestClose={closeContactModal}
parentSelector={() => document.body} parentSelector={() => document.body}
style={customStyles} style={customStyles}
> >
{ {channel && (
channel &&
<div className={styles.container}> <div className={styles.container}>
<header className={styles.header}> <header className={styles.header}>
<div className={`${styles.status} ${channel.active && styles.online}`}> <div className={`${styles.status} ${channel.active && styles.online}`}>
<FaCircle style={{ verticalAlign: 'top' }} /> <FaCircle style={{ verticalAlign: 'top' }} />
<span> <span>{channel.active ? 'Online' : 'Offline'}</span>
{channel.active ? 'Online' : 'Offline'}
</span>
</div> </div>
<div className={styles.closeContainer}> <div className={styles.closeContainer}>
<span onClick={closeContactModal}> <span onClick={closeContactModal}>
@ -71,10 +63,7 @@ const ContactModal = ({
</header> </header>
<section className={styles.title}> <section className={styles.title}>
{ {node && <h1>{node.alias}</h1>}
node &&
<h1>{node.alias}</h1>
}
<h2>{channel.remote_pubkey}</h2> <h2>{channel.remote_pubkey}</h2>
</section> </section>
@ -106,19 +95,18 @@ const ContactModal = ({
</section> </section>
<footer> <footer>
{ {closingChannelIds.includes(channel.chan_id) ? (
closingChannelIds.includes(channel.chan_id) ? <span className={styles.inactive}>
<span className={styles.inactive}> <div className={styles.loading}>
<div className={styles.loading}> <div className={styles.spinner} />
<div className={styles.spinner} /> </div>
</div> </span>
</span> ) : (
: <div onClick={removeClicked}>Remove</div>
<div onClick={removeClicked}>Remove</div> )}
}
</footer> </footer>
</div> </div>
} )}
</ReactModal> </ReactModal>
) )
} }

95
app/components/Contacts/ContactsForm.js

@ -35,7 +35,7 @@ class ContactsForm extends React.Component {
const { editing } = this.state const { editing } = this.state
const renderRightSide = (node) => { const renderRightSide = node => {
if (loadingChannelPubkeys.includes(node.pub_key)) { if (loadingChannelPubkeys.includes(node.pub_key)) {
return ( return (
<span className={styles.inactive}> <span className={styles.inactive}>
@ -71,11 +71,7 @@ class ContactsForm extends React.Component {
} }
if (!node.addresses.length) { if (!node.addresses.length) {
return ( return <span className={`${styles.private} ${styles.inactive}`}>Private</span>
<span className={`${styles.private} ${styles.inactive}`}>
Private
</span>
)
} }
return ( return (
@ -90,12 +86,13 @@ class ContactsForm extends React.Component {
} }
const inputClicked = () => { const inputClicked = () => {
if (editing) { return } if (editing) {
return
}
this.setState({ editing: true }) this.setState({ editing: true })
} }
const manualFormSubmit = () => { const manualFormSubmit = () => {
if (!manualFormIsValid.isValid) { if (!manualFormIsValid.isValid) {
updateManualFormErrors(manualFormIsValid.errors) updateManualFormErrors(manualFormIsValid.errors)
@ -112,7 +109,7 @@ class ContactsForm extends React.Component {
updateManualFormSearchQuery('') updateManualFormSearchQuery('')
} }
const searchUpdated = (search) => { const searchUpdated = search => {
updateContactFormSearchQuery(search) updateContactFormSearchQuery(search)
if (search.includes('@') && search.split('@')[0].length === 66) { if (search.includes('@') && search.split('@')[0].length === 66) {
@ -124,7 +121,7 @@ class ContactsForm extends React.Component {
<div> <div>
<ReactModal <ReactModal
isOpen={contactsform.isOpen} isOpen={contactsform.isOpen}
contentLabel='No Overlay Click Modal' contentLabel="No Overlay Click Modal"
ariaHideApp ariaHideApp
shouldCloseOnOverlayClick shouldCloseOnOverlayClick
onRequestClose={() => closeContactsForm} onRequestClose={() => closeContactsForm}
@ -143,8 +140,8 @@ class ContactsForm extends React.Component {
<div className={styles.form}> <div className={styles.form}>
<div className={styles.search}> <div className={styles.search}>
<input <input
type='text' type="text"
placeholder='Find contact by alias or pubkey' placeholder="Find contact by alias or pubkey"
className={styles.searchInput} className={styles.searchInput}
value={contactsform.searchQuery} value={contactsform.searchQuery}
onChange={event => searchUpdated(event.target.value)} onChange={event => searchUpdated(event.target.value)}
@ -152,70 +149,63 @@ class ContactsForm extends React.Component {
</div> </div>
<ul className={styles.networkResults}> <ul className={styles.networkResults}>
{ {filteredNetworkNodes.map(node => (
filteredNetworkNodes.map(node => ( <li key={node.pub_key}>
<li key={node.pub_key}> <section>
<section> {node.alias.length > 0 ? (
{ <h2>
node.alias.length > 0 ? <span>{node.alias.trim()}</span>
<h2> <span>
<span>{node.alias.trim()}</span> ({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})
<span>({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})</span> </span>
</h2> </h2>
: ) : (
<h2> <h2>
<span>{node.pub_key}</span> <span>{node.pub_key}</span>
</h2> </h2>
} )}
</section> </section>
<section> <section>{renderRightSide(node)}</section>
{renderRightSide(node)} </li>
</section> ))}
</li>
))
}
</ul> </ul>
</div> </div>
{ {showManualForm && (
showManualForm &&
<div className={styles.manualForm}> <div className={styles.manualForm}>
<h2>Hm, looks like we cant see that contact from here. Want to try and manually connect?</h2> <h2>Hm, looks like we cant see that contact from here. Want to try and manually connect?</h2>
<section> <section>
<input <input
type='text' type="text"
placeholder='pubkey@host' placeholder="pubkey@host"
value={contactsform.manualSearchQuery} value={contactsform.manualSearchQuery}
onChange={event => updateManualFormSearchQuery(event.target.value)} onChange={event => updateManualFormSearchQuery(event.target.value)}
/> />
<div className={styles.submit} onClick={manualFormSubmit}>Submit</div> <div className={styles.submit} onClick={manualFormSubmit}>
Submit
</div>
{ {loadingChannelPubkeys.length > 0 && (
loadingChannelPubkeys.length > 0 &&
<div className={styles.manualFormSpinner}> <div className={styles.manualFormSpinner}>
<div className={styles.loading}> <div className={styles.loading}>
<div className={styles.spinner} /> <div className={styles.spinner} />
</div> </div>
</div> </div>
} )}
</section> </section>
<section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}> <section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}>
{showErrors.manualInput && {showErrors.manualInput && <span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>}
<span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span>
}
</section> </section>
</div> </div>
} )}
<footer className={styles.footer}> <footer className={styles.footer}>
<div> <div>
<span> <span>Use</span>
Use
</span>
<span className={styles.amount}> <span className={styles.amount}>
<input <input
type='text' type="text"
value={contactsform.contactCapacity} value={contactsform.contactCapacity}
onChange={event => updateContactCapacity(event.target.value)} onChange={event => updateContactCapacity(event.target.value)}
onClick={inputClicked} onClick={inputClicked}
@ -226,10 +216,7 @@ class ContactsForm extends React.Component {
</span> </span>
<span className={styles.caption}> <span className={styles.caption}>
BTC per contact BTC per contact
<i <i data-hint="You aren't spending anything, just moving money onto the Lightning Network" className="hint--top">
data-hint="You aren't spending anything, just moving money onto the Lightning Network"
className='hint--top'
>
<FaQuestionCircle style={{ verticalAlign: 'top' }} /> <FaQuestionCircle style={{ verticalAlign: 'top' }} />
</i> </i>
</span> </span>

173
app/components/Contacts/Network.js

@ -23,15 +23,7 @@ class Network extends Component {
render() { render() {
const { const {
channels: { channels: { searchQuery, filterPulldown, filter, selectedChannel, loadingChannelPubkeys, closingChannelIds, channels },
searchQuery,
filterPulldown,
filter,
selectedChannel,
loadingChannelPubkeys,
closingChannelIds,
channels
},
currentChannels, currentChannels,
balance, balance,
ticker, ticker,
@ -85,12 +77,12 @@ class Network extends Component {
} }
// when the user clicks the action to close the channel // when the user clicks the action to close the channel
const removeClicked = (channel) => { const removeClicked = channel => {
closeChannel({ channel_point: channel.channel_point, chan_id: channel.chan_id, force: !channel.active }) closeChannel({ channel_point: channel.channel_point, chan_id: channel.chan_id, force: !channel.active })
} }
// when a user clicks a channel // when a user clicks a channel
const channelClicked = (channel) => { const channelClicked = channel => {
// selectedChannel === channel ? setSelectedChannel(null) : setSelectedChannel(channel) // selectedChannel === channel ? setSelectedChannel(null) : setSelectedChannel(channel)
if (selectedChannel === channel) { if (selectedChannel === channel) {
setSelectedChannel(null) setSelectedChannel(null)
@ -99,27 +91,36 @@ class Network extends Component {
} }
} }
const displayNodeName = (channel) => { const displayNodeName = channel => {
const node = find(nodes, n => channel.remote_pubkey === n.pub_key) const node = find(nodes, n => channel.remote_pubkey === n.pub_key)
if (node && node.alias.length) { return node.alias } if (node && node.alias.length) {
return node.alias
}
return channel.remote_pubkey ? channel.remote_pubkey.substring(0, 10) : channel.remote_node_pub.substring(0, 10) return channel.remote_pubkey ? channel.remote_pubkey.substring(0, 10) : channel.remote_node_pub.substring(0, 10)
} }
const channelStatus = (channel) => { const channelStatus = channel => {
// if the channel has a confirmation_height property that means it's pending // if the channel has a confirmation_height property that means it's pending
if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) { return 'pending' } if (Object.prototype.hasOwnProperty.call(channel, 'confirmation_height')) {
return 'pending'
}
// if the channel has a closing tx that means it's closing // if the channel has a closing tx that means it's closing
if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) { return 'closing' } if (Object.prototype.hasOwnProperty.call(channel, 'closing_txid')) {
return 'closing'
}
// if we are in the process of closing this channel // if we are in the process of closing this channel
if (closingChannelIds.includes(channel.chan_id)) { return 'closing' } if (closingChannelIds.includes(channel.chan_id)) {
return 'closing'
}
// if the channel isn't active that means the remote peer isn't online // if the channel isn't active that means the remote peer isn't online
if (!channel.active) { return 'offline' } if (!channel.active) {
return 'offline'
}
// if all of the above conditionals fail we can assume the node is online :) // if all of the above conditionals fail we can assume the node is online :)
return 'online' return 'online'
@ -136,7 +137,7 @@ class Network extends Component {
{btc.satoshisToBtc(balance.channelBalance)}BTC ${usdAmount ? usdAmount.toLocaleString() : ''} {btc.satoshisToBtc(balance.channelBalance)}BTC ${usdAmount ? usdAmount.toLocaleString() : ''}
</span> </span>
</section> </section>
<section className={`${styles.addChannel} hint--bottom-left`} onClick={openContactsForm} data-hint='Open a channel'> <section className={`${styles.addChannel} hint--bottom-left`} onClick={openContactsForm} data-hint="Open a channel">
<span className={styles.plusContainer}> <span className={styles.plusContainer}>
<Isvg src={plus} /> <Isvg src={plus} />
</span> </span>
@ -144,48 +145,48 @@ class Network extends Component {
</header> </header>
<div className={styles.channels}> <div className={styles.channels}>
{ {!loadingChannelPubkeys.length && !channels.length && <SuggestedNodes {...suggestedNodesProps} />}
(!loadingChannelPubkeys.length && !channels.length) &&
<SuggestedNodes {...suggestedNodesProps} />
}
{ {(loadingChannelPubkeys.length || channels.length) && (
(loadingChannelPubkeys.length || channels.length) &&
<header className={styles.listHeader}> <header className={styles.listHeader}>
<section> <section>
<h2 onClick={toggleFilterPulldown} className={styles.filterTitle}> <h2 onClick={toggleFilterPulldown} className={styles.filterTitle}>
{filter.name} <span className={filterPulldown && styles.pulldown}><FaAngleDown /></span> {filter.name}{' '}
<span className={filterPulldown && styles.pulldown}>
<FaAngleDown />
</span>
</h2> </h2>
<ul className={`${styles.filters} ${filterPulldown && styles.active}`}> <ul className={`${styles.filters} ${filterPulldown && styles.active}`}>
{ {nonActiveFilters.map(f => (
nonActiveFilters.map(f => ( <li key={f.key} onClick={() => changeFilter(f)}>
<li key={f.key} onClick={() => changeFilter(f)}> {f.name}
{f.name} </li>
</li> ))}
))
}
</ul> </ul>
</section> </section>
<section className={styles.refreshContainer}> <section className={styles.refreshContainer}>
<span className={styles.refresh} onClick={refreshClicked} ref={(ref) => { this.repeat = ref }}> <span
{ className={styles.refresh}
this.state.refreshing ? onClick={refreshClicked}
<FaRepeat /> ref={ref => {
: this.repeat = ref
'Refresh' }}
} >
{this.state.refreshing ? <FaRepeat /> : 'Refresh'}
</span> </span>
</section> </section>
</header> </header>
} )}
<ul className={filterPulldown && styles.fade}> <ul className={filterPulldown && styles.fade}>
{ {loadingChannelPubkeys.length &&
loadingChannelPubkeys.length && loadingChannelPubkeys.map((loadingPubkey) => { loadingChannelPubkeys.map(loadingPubkey => {
// TODO(jimmymow): refactor this out. same logic is in displayNodeName above // TODO(jimmymow): refactor this out. same logic is in displayNodeName above
const node = find(nodes, n => loadingPubkey === n.pub_key) const node = find(nodes, n => loadingPubkey === n.pub_key)
const nodeDisplay = () => { const nodeDisplay = () => {
if (node && node.alias.length) { return node.alias } if (node && node.alias.length) {
return node.alias
}
return loadingPubkey.substring(0, 10) return loadingPubkey.substring(0, 10)
} }
@ -193,17 +194,16 @@ class Network extends Component {
return ( return (
<li key={loadingPubkey} className={styles.channel}> <li key={loadingPubkey} className={styles.channel}>
<section className={styles.channelTitle}> <section className={styles.channelTitle}>
<span className={`${styles.loading} hint--left`} data-hint='loading'> <span className={`${styles.loading} hint--left`} data-hint="loading">
<i className={styles.spinner} /> <i className={styles.spinner} />
</span> </span>
<span>{nodeDisplay()}</span> <span>{nodeDisplay()}</span>
</section> </section>
</li> </li>
) )
}) })}
} {currentChannels.length &&
{ currentChannels.map((channelObj, index) => {
currentChannels.length && currentChannels.map((channelObj, index) => {
const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj const channel = Object.prototype.hasOwnProperty.call(channelObj, 'channel') ? channelObj.channel : channelObj
const pubkey = channel.remote_node_pub || channel.remote_pubkey const pubkey = channel.remote_node_pub || channel.remote_pubkey
@ -215,22 +215,20 @@ class Network extends Component {
> >
<section className={styles.channelTitle}> <section className={styles.channelTitle}>
<span className={`${styles[channelStatus(channelObj)]} hint--right`} data-hint={channelStatus(channelObj)}> <span className={`${styles[channelStatus(channelObj)]} hint--right`} data-hint={channelStatus(channelObj)}>
{ {closingChannelIds.includes(channel.chan_id) ? (
closingChannelIds.includes(channel.chan_id) ? <span className={styles.loading}>
<span className={styles.loading}> <i className={`${styles.spinner} ${styles.closing}`} />
<i className={`${styles.spinner} ${styles.closing}`} /> </span>
</span> ) : (
: <FaCircle />
<FaCircle /> )}
}
</span> </span>
<span>{displayNodeName(channel)}</span> <span>{displayNodeName(channel)}</span>
{ {selectedChannel === channel && (
selectedChannel === channel &&
<span onClick={() => blockExplorer.showTransaction(network, channel.channel_point.split(':')[0])}> <span onClick={() => blockExplorer.showTransaction(network, channel.channel_point.split(':')[0])}>
<FaExternalLink /> <FaExternalLink />
</span> </span>
} )}
</section> </section>
<section className={styles.channelDetails}> <section className={styles.channelDetails}>
@ -242,65 +240,54 @@ class Network extends Component {
<section> <section>
<h5>Pay Limit</h5> <h5>Pay Limit</h5>
<p> <p>
<Value <Value value={channel.local_balance} currency={ticker.currency} currentTicker={currentTicker} />
value={channel.local_balance}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i> {ticker.currency.toUpperCase()}</i> <i> {ticker.currency.toUpperCase()}</i>
</p> </p>
</section> </section>
<section> <section>
<h5>Request Limit</h5> <h5>Request Limit</h5>
<p> <p>
<Value <Value value={channel.remote_balance} currency={ticker.currency} currentTicker={currentTicker} />
value={channel.remote_balance}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i>{ticker.currency.toUpperCase()}</i> <i>{ticker.currency.toUpperCase()}</i>
</p> </p>
</section> </section>
</div> </div>
<div className={styles.actions}> <div className={styles.actions}>
{ {closingChannelIds.includes(channel.chan_id) && (
closingChannelIds.includes(channel.chan_id) && <section>
<section> <span className={`${styles.loading} hint--left`} data-hint="closing">
<span className={`${styles.loading} hint--left`} data-hint='closing'> <i>Closing</i> <i className={`${styles.spinner} ${styles.closing}`} />
<i>Closing</i> <i className={`${styles.spinner} ${styles.closing}`} /> </span>
</span>
</section>
}
{
(Object.prototype.hasOwnProperty.call(channel, 'active') && !closingChannelIds.includes(channel.chan_id)) &&
<section onClick={() => removeClicked(channel)}>
<div>Disconnect</div>
</section> </section>
} )}
{Object.prototype.hasOwnProperty.call(channel, 'active') &&
!closingChannelIds.includes(channel.chan_id) && (
<section onClick={() => removeClicked(channel)}>
<div>Disconnect</div>
</section>
)}
</div> </div>
</section> </section>
</li> </li>
) )
}) })}
}
</ul> </ul>
</div> </div>
{ {(loadingChannelPubkeys.length || channels.length) && (
(loadingChannelPubkeys.length || channels.length) &&
<footer className={styles.search}> <footer className={styles.search}>
<label htmlFor='search' className={`${styles.label} ${styles.input}`}> <label htmlFor="search" className={`${styles.label} ${styles.input}`}>
<Isvg src={search} /> <Isvg src={search} />
</label> </label>
<input <input
id='search' id="search"
type='text' type="text"
className={`${styles.text} ${styles.input}`} className={`${styles.text} ${styles.input}`}
placeholder='search by alias or pubkey' placeholder="search by alias or pubkey"
value={searchQuery} value={searchQuery}
onChange={event => updateChannelSearchQuery(event.target.value)} onChange={event => updateChannelSearchQuery(event.target.value)}
/> />
</footer> </footer>
} )}
</div> </div>
) )
} }

46
app/components/Contacts/SubmitChannelForm.js

@ -38,7 +38,9 @@ class SubmitChannelForm extends React.Component {
const formSubmitted = () => { const formSubmitted = () => {
// dont submit to LND if they havent set channel capacity amount // dont submit to LND if they havent set channel capacity amount
if (contactCapacity <= 0) { return } if (contactCapacity <= 0) {
return
}
// submit the channel to LND // submit the channel to LND
openChannel({ pubkey: node.pub_key, host: node.addresses[0].addr, local_amt: contactCapacity }) openChannel({ pubkey: node.pub_key, host: node.addresses[0].addr, local_amt: contactCapacity })
@ -55,8 +57,8 @@ class SubmitChannelForm extends React.Component {
<header className={styles.header}> <header className={styles.header}>
<h1>Add Funds to Network</h1> <h1>Add Funds to Network</h1>
<p> <p>
Adding a connection will help you send and receive money on the Lightning Network. Adding a connection will help you send and receive money on the Lightning Network. You aren&apos;t spening any money, rather moving the
You aren&apos;t spening any money, rather moving the money you plan to use onto the network. money you plan to use onto the network.
</p> </p>
</header> </header>
@ -67,37 +69,36 @@ class SubmitChannelForm extends React.Component {
<section className={styles.amount}> <section className={styles.amount}>
<div className={styles.input}> <div className={styles.input}>
<input <input
type='number' type="number"
min='0' min="0"
size='' size=""
placeholder='0.00000000' placeholder="0.00000000"
value={contactCapacity || ''} value={contactCapacity || ''}
onChange={event => updateContactCapacity(event.target.value)} onChange={event => updateContactCapacity(event.target.value)}
id='amount' id="amount"
/> />
<div className={styles.currency}> <div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setContactsCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setContactsCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section> </section>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</div> </div>
</div> </div>
<div className={styles.usdAmount}> <div className={styles.usdAmount}>{`${contactFormUsdAmount || 0} USD`}</div>
{`${contactFormUsdAmount || 0} USD`}
</div>
</section> </section>
<section className={styles.submit}> <section className={styles.submit}>
<div <div className={`${styles.button} ${contactCapacity > 0 && styles.active}`} onClick={formSubmitted}>
className={`${styles.button} ${contactCapacity > 0 && styles.active}`}
onClick={formSubmitted}
>
Submit Submit
</div> </div>
</section> </section>
@ -111,10 +112,7 @@ SubmitChannelForm.propTypes = {
closeContactsForm: PropTypes.func.isRequired, closeContactsForm: PropTypes.func.isRequired,
node: PropTypes.object.isRequired, node: PropTypes.object.isRequired,
contactCapacity: PropTypes.PropTypes.oneOfType([ contactCapacity: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
PropTypes.number,
PropTypes.string
]),
updateContactCapacity: PropTypes.func.isRequired, updateContactCapacity: PropTypes.func.isRequired,
openChannel: PropTypes.func.isRequired, openChannel: PropTypes.func.isRequired,

37
app/components/Contacts/SuggestedNodes.js

@ -2,13 +2,8 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styles from './SuggestedNodes.scss' import styles from './SuggestedNodes.scss'
const SuggestedNodes = ({ const SuggestedNodes = ({ suggestedNodesLoading, suggestedNodes, setNode, openSubmitChannelForm }) => {
suggestedNodesLoading, const nodeClicked = n => {
suggestedNodes,
setNode,
openSubmitChannelForm
}) => {
const nodeClicked = (n) => {
// set the node public key for the submit form // set the node public key for the submit form
setNode({ pub_key: n.pubkey, addresses: [{ addr: n.host }] }) setNode({ pub_key: n.pubkey, addresses: [{ addr: n.host }] })
// open the submit form // open the submit form
@ -26,24 +21,20 @@ const SuggestedNodes = ({
return ( return (
<div className={styles.container}> <div className={styles.container}>
<header> <header>Hmmm, looks like you don&apos;t have any channels yet. Here are some suggested nodes to open a channel with to get started</header>
{'Hmmm, looks like you don\'t have any channels yet. Here are some suggested nodes to open a channel with to get started'}
</header>
<ul className={styles.suggestedNodes}> <ul className={styles.suggestedNodes}>
{ {suggestedNodes.map(node => (
suggestedNodes.map(node => ( <li key={node.pubkey}>
<li key={node.pubkey}> <section>
<section> <span>{node.nickname}</span>
<span>{node.nickname}</span> <span>{`${node.pubkey.substring(0, 30)}...`}</span>
<span>{`${node.pubkey.substring(0, 30)}...`}</span> </section>
</section> <section>
<section> <span onClick={() => nodeClicked(node)}>Connect</span>
<span onClick={() => nodeClicked(node)}>Connect</span> </section>
</section> </li>
</li> ))}
))
}
</ul> </ul>
</div> </div>
) )

7
app/components/CurrencyIcon/CurrencyIcon.js

@ -3,10 +3,9 @@ import PropTypes from 'prop-types'
import { FaDollar } from 'react-icons/lib/fa' import { FaDollar } from 'react-icons/lib/fa'
import CryptoIcon from '../CryptoIcon' import CryptoIcon from '../CryptoIcon'
const CurrencyIcon = ({ currency, crypto, styles }) => (currency === 'usd' ? const CurrencyIcon = ({ currency, crypto, styles }) => {
<FaDollar style={styles} /> return currency === 'usd' ? <FaDollar style={styles} /> : <CryptoIcon styles={styles} currency={crypto} />
: }
<CryptoIcon styles={styles} currency={crypto} />)
CurrencyIcon.propTypes = { CurrencyIcon.propTypes = {
currency: PropTypes.string.isRequired, currency: PropTypes.string.isRequired,

5
app/components/Form/Form.js

@ -15,7 +15,9 @@ const FORM_TYPES = {
} }
const Form = ({ formType, formProps, closeForm }) => { const Form = ({ formType, formProps, closeForm }) => {
if (!formType) { return null } if (!formType) {
return null
}
const FormComponent = FORM_TYPES[formType] const FormComponent = FORM_TYPES[formType]
return ( return (
@ -30,7 +32,6 @@ const Form = ({ formType, formProps, closeForm }) => {
) )
} }
Form.propTypes = { Form.propTypes = {
formType: PropTypes.string, formType: PropTypes.string,
formProps: PropTypes.object.isRequired, formProps: PropTypes.object.isRequired,

106
app/components/Form/Pay.js

@ -14,26 +14,26 @@ import styles from './Pay.scss'
class Pay extends Component { class Pay extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
isOnchain, isLn, payform: { payInput }, fetchInvoice isOnchain,
isLn,
payform: { payInput },
fetchInvoice
} = this.props } = this.props
// If on-chain, focus on amount to let user know it's editable // If on-chain, focus on amount to let user know it's editable
if (isOnchain) { this.amountInput.focus() } if (isOnchain) {
this.amountInput.focus()
}
// If LN go retrieve invoice details // If LN go retrieve invoice details
if ((prevProps.payform.payInput !== payInput) && isLn) { if (prevProps.payform.payInput !== payInput && isLn) {
fetchInvoice(payInput) fetchInvoice(payInput)
} }
} }
render() { render() {
const { const {
payform: { payform: { payInput, showErrors, invoice, showCurrencyFilters },
payInput,
showErrors,
invoice,
showCurrencyFilters
},
nodes, nodes,
ticker, ticker,
@ -58,15 +58,17 @@ class Pay extends Component {
setCurrency setCurrency
} = this.props } = this.props
const displayNodeName = (pubkey) => { const displayNodeName = pubkey => {
const node = find(nodes, n => n.pub_key === pubkey) const node = find(nodes, n => n.pub_key === pubkey)
if (node && node.alias.length) { return node.alias } if (node && node.alias.length) {
return node.alias
}
return pubkey ? pubkey.substring(0, 10) : '' return pubkey ? pubkey.substring(0, 10) : ''
} }
const onCurrencyFilterClick = (currency) => { const onCurrencyFilterClick = currency => {
if (!isLn) { if (!isLn) {
// change the input amount // change the input amount
setPayAmount(btc.convert(ticker.currency, currency, currentAmount)) setPayAmount(btc.convert(ticker.currency, currency, currentAmount))
@ -86,85 +88,87 @@ class Pay extends Component {
<div className={styles.content}> <div className={styles.content}>
<section className={styles.destination}> <section className={styles.destination}>
<div className={styles.top}> <div className={styles.top}>
<label htmlFor='paymentRequest'>Destination</label> <label htmlFor="paymentRequest">Destination</label>
<span className={`${styles.description} ${(isOnchain || isLn) && styles.active}`}> <span className={`${styles.description} ${(isOnchain || isLn) && styles.active}`}>
{isOnchain && {isOnchain && (
<i> <i>
<Isvg src={link} /> <Isvg src={link} />
<span>On-Chain (~10 minutes)</span> <span>On-Chain (~10 minutes)</span>
</i> </i>
} )}
{isLn && {isLn && (
<i> <i>
<span> <span>
{displayNodeName(invoice.destination)} ({invoice.description}) {displayNodeName(invoice.destination)} ({invoice.description})
</span> </span>
</i> </i>
} )}
</span> </span>
</div> </div>
<div className={styles.bottom}> <div className={styles.bottom}>
<textarea <textarea
type='text' type="text"
placeholder='Paste payment request or bitcoin address here' placeholder="Paste payment request or bitcoin address here"
value={payInput} value={payInput}
onChange={event => setPayInput(event.target.value)} onChange={event => setPayInput(event.target.value)}
onBlur={onPayInputBlur} onBlur={onPayInputBlur}
id='paymentRequest' id="paymentRequest"
rows='4' rows="4"
/> />
<section className={`${styles.errorMessage} ${showErrors.payInput && styles.active}`}> <section className={`${styles.errorMessage} ${showErrors.payInput && styles.active}`}>
{showErrors.payInput && {showErrors.payInput && <span>{errors.payInput}</span>}
<span>{errors.payInput}</span>
}
</section> </section>
</div> </div>
</section> </section>
<section className={styles.amount}> <section className={styles.amount}>
<div className={styles.top}> <div className={styles.top}>
<label htmlFor='amount'>Amount</label> <label htmlFor="amount">Amount</label>
<span /> <span />
</div> </div>
<div className={styles.bottom}> <div className={styles.bottom}>
<input <input
type='number' type="number"
min='0' min="0"
ref={(input) => { this.amountInput = input }} ref={input => {
size='' this.amountInput = input
placeholder='0.00000000' }}
size=""
placeholder="0.00000000"
value={currentAmount || ''} value={currentAmount || ''}
onChange={event => setPayAmount(event.target.value)} onChange={event => setPayAmount(event.target.value)}
onBlur={onPayAmountBlur} onBlur={onPayAmountBlur}
id='amount' id="amount"
readOnly={isLn} readOnly={isLn}
/> />
<div className={styles.currency}> <div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section> </section>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</div> </div>
</div> </div>
<div className={styles.usdAmount}> <div className={styles.usdAmount}>{`${usdAmount || 0} USD`}</div>
{`${usdAmount || 0} USD`}
</div>
<section className={`${styles.errorMessage} ${styles.amount} ${showErrors.amount && styles.active}`}> <section className={`${styles.errorMessage} ${styles.amount} ${showErrors.amount && styles.active}`}>
{showErrors.amount && {showErrors.amount && <span>{errors.amount}</span>}
<span>{errors.amount}</span>
}
</section> </section>
</section> </section>
<section className={styles.submit}> <section className={styles.submit}>
<div className={`${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>Pay</div> <div className={`${styles.button} ${isValid && styles.active}`} onClick={onPaySubmit}>
Pay
</div>
</section> </section>
</div> </div>
</div> </div>
@ -172,13 +176,9 @@ class Pay extends Component {
} }
} }
Pay.propTypes = { Pay.propTypes = {
payform: PropTypes.shape({ payform: PropTypes.shape({
amount: PropTypes.oneOfType([ amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string,
PropTypes.number
]),
payInput: PropTypes.string.isRequired, payInput: PropTypes.string.isRequired,
invoice: PropTypes.object.isRequired, invoice: PropTypes.object.isRequired,
showErrors: PropTypes.object.isRequired showErrors: PropTypes.object.isRequired
@ -187,14 +187,8 @@ Pay.propTypes = {
isOnchain: PropTypes.bool.isRequired, isOnchain: PropTypes.bool.isRequired,
isLn: PropTypes.bool.isRequired, isLn: PropTypes.bool.isRequired,
currentAmount: PropTypes.oneOfType([ currentAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string, usdAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.number
]),
usdAmount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
payFormIsValid: PropTypes.shape({ payFormIsValid: PropTypes.shape({
errors: PropTypes.object, errors: PropTypes.object,
isValid: PropTypes.bool isValid: PropTypes.bool

48
app/components/Form/Request.js

@ -23,7 +23,7 @@ const Request = ({
onRequestSubmit onRequestSubmit
}) => { }) => {
const onCurrencyFilterClick = (currency) => { const onCurrencyFilterClick = currency => {
// change the input amount // change the input amount
setRequestAmount(btc.convert(ticker.currency, currency, amount)) setRequestAmount(btc.convert(ticker.currency, currency, amount))
@ -41,46 +41,42 @@ const Request = ({
<div className={styles.content}> <div className={styles.content}>
<section className={styles.amount}> <section className={styles.amount}>
<div className={styles.top}> <div className={styles.top}>
<label htmlFor='amount'>Amount</label> <label htmlFor="amount">Amount</label>
<span /> <span />
</div> </div>
<div className={styles.bottom}> <div className={styles.bottom}>
<input <input type="number" value={amount || ''} onChange={event => setRequestAmount(event.target.value)} id="amount" placeholder="0.00000000" />
type='number'
value={amount || ''}
onChange={event => setRequestAmount(event.target.value)}
id='amount'
placeholder='0.00000000'
/>
<div className={styles.currency}> <div className={styles.currency}>
<section className={styles.currentCurrency} onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}> <section className={styles.currentCurrency} onClick={() => setRequestCurrencyFilters(!showCurrencyFilters)}>
<span>{currencyName}</span><span><FaAngleDown /></span> <span>{currencyName}</span>
<span>
<FaAngleDown />
</span>
</section> </section>
<ul className={showCurrencyFilters && styles.active}> <ul className={showCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</div> </div>
</div> </div>
<div className={styles.usdAmount}> <div className={styles.usdAmount}>{`${requestUsdAmount || 0} USD`}</div>
{`${requestUsdAmount || 0} USD`}
</div>
</section> </section>
<section className={styles.memo}> <section className={styles.memo}>
<div className={styles.top}> <div className={styles.top}>
<label htmlFor='memo'>Memo</label> <label htmlFor="memo">Memo</label>
</div> </div>
<div className={styles.bottom}> <div className={styles.bottom}>
<input <input
type='text' type="text"
placeholder='Details about the request' placeholder="Details about the request"
value={memo} value={memo}
onChange={event => setRequestMemo(event.target.value)} onChange={event => setRequestMemo(event.target.value)}
id='memo' id="memo"
/> />
</div> </div>
</section> </section>
@ -97,17 +93,11 @@ const Request = ({
Request.propTypes = { Request.propTypes = {
requestform: PropTypes.shape({ requestform: PropTypes.shape({
amount: PropTypes.oneOfType([ amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string,
PropTypes.number
]),
memo: PropTypes.string memo: PropTypes.string
}).isRequired, }).isRequired,
requestUsdAmount: PropTypes.oneOfType([ requestUsdAmount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string,
PropTypes.number
]),
currencyName: PropTypes.string.isRequired, currencyName: PropTypes.string.isRequired,
currentCurrencyFilters: PropTypes.array.isRequired, currentCurrencyFilters: PropTypes.array.isRequired,

4
app/components/Onboarding/Alias.js

@ -5,8 +5,8 @@ import styles from './Alias.scss'
const Alias = ({ alias, updateAlias }) => ( const Alias = ({ alias, updateAlias }) => (
<div className={styles.container}> <div className={styles.container}>
<input <input
type='text' type="text"
placeholder='Satoshi' placeholder="Satoshi"
className={styles.alias} className={styles.alias}
ref={input => input && input.focus()} ref={input => input && input.focus()}
value={alias} value={alias}

28
app/components/Onboarding/ConnectionDetails.js

@ -2,16 +2,14 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styles from './ConnectionDetails.scss' import styles from './ConnectionDetails.scss'
const ConnectionDetails = ({ const ConnectionDetails = ({ connectionHost, connectionCert, connectionMacaroon, setConnectionHost, setConnectionCert, setConnectionMacaroon }) => (
connectionHost, connectionCert, connectionMacaroon, setConnectionHost, setConnectionCert, setConnectionMacaroon
}) => (
<div className={styles.container}> <div className={styles.container}>
<div> <div>
<label htmlFor='connectionHost'>Host:</label> <label htmlFor="connectionHost">Host:</label>
<input <input
type='text' type="text"
id='connectionHost' id="connectionHost"
placeholder='Hostname / Port of the Lnd gRPC interface' placeholder="Hostname / Port of the Lnd gRPC interface"
className={styles.host} className={styles.host}
ref={input => input} ref={input => input}
value={connectionHost} value={connectionHost}
@ -19,11 +17,11 @@ const ConnectionDetails = ({
/> />
</div> </div>
<div> <div>
<label htmlFor='connectionCert'>TLS Certificate:</label> <label htmlFor="connectionCert">TLS Certificate:</label>
<input <input
type='text' type="text"
id='connectionCert' id="connectionCert"
placeholder='Path to the lnd tls cert' placeholder="Path to the lnd tls cert"
className={styles.cert} className={styles.cert}
ref={input => input} ref={input => input}
value={connectionCert} value={connectionCert}
@ -31,11 +29,11 @@ const ConnectionDetails = ({
/> />
</div> </div>
<div> <div>
<label htmlFor='connectionMacaroon'>Macaroon:</label> <label htmlFor="connectionMacaroon">Macaroon:</label>
<input <input
type='text' type="text"
id='connectionMacaroon' id="connectionMacaroon"
placeholder='Path to the lnd macaroon file' placeholder="Path to the lnd macaroon file"
className={styles.macaroon} className={styles.macaroon}
ref={input => input} ref={input => input}
value={connectionMacaroon} value={connectionMacaroon}

29
app/components/Onboarding/FormContainer.js

@ -7,13 +7,7 @@ import { FaAngleLeft, FaAngleRight } from 'react-icons/lib/fa'
import zapLogo from 'icons/zap_logo.svg' import zapLogo from 'icons/zap_logo.svg'
import styles from './FormContainer.scss' import styles from './FormContainer.scss'
const FormContainer = ({ const FormContainer = ({ title, description, back, next, children }) => (
title,
description,
back,
next,
children
}) => (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.titleBar} /> <div className={styles.titleBar} />
@ -29,28 +23,29 @@ const FormContainer = ({
<p>{description}</p> <p>{description}</p>
</div> </div>
<div className={styles.content}> <div className={styles.content}>{children}</div>
{children}
</div>
<footer className={styles.footer}> <footer className={styles.footer}>
<div className={styles.buttonsContainer}> <div className={styles.buttonsContainer}>
<section> <section>
{ {back && (
back && <div onClick={back}><FaAngleLeft style={{ verticalAlign: 'top' }} /> Back</div> <div onClick={back}>
} <FaAngleLeft style={{ verticalAlign: 'top' }} /> Back
</div>
)}
</section> </section>
<section> <section>
{ {next && (
next && <div onClick={next}>Next <FaAngleRight style={{ verticalAlign: 'top' }} /></div> <div onClick={next}>
} Next <FaAngleRight style={{ verticalAlign: 'top' }} />
</div>
)}
</section> </section>
</div> </div>
</footer> </footer>
</div> </div>
) )
FormContainer.propTypes = { FormContainer.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired, description: PropTypes.string.isRequired,

9
app/components/Onboarding/InitWallet.js

@ -5,14 +5,7 @@ import Signup from './Signup'
import styles from './InitWallet.scss' import styles from './InitWallet.scss'
const InitWallet = ({ hasSeed, loginProps, signupProps }) => ( const InitWallet = ({ hasSeed, loginProps, signupProps }) => (
<div className={styles.container}> <div className={styles.container}>{hasSeed ? <Login {...loginProps} /> : <Signup {...signupProps} />}</div>
{
hasSeed ?
<Login {...loginProps} />
:
<Signup {...signupProps} />
}
</div>
) )
InitWallet.propTypes = { InitWallet.propTypes = {

23
app/components/Onboarding/Login.js

@ -2,35 +2,22 @@ import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styles from './Login.scss' import styles from './Login.scss'
const Login = ({ const Login = ({ password, updatePassword, unlockingWallet, unlockWallet, unlockWalletError }) => (
password,
updatePassword,
unlockingWallet,
unlockWallet,
unlockWalletError
}) => (
<div className={styles.container}> <div className={styles.container}>
<input <input
type='password' type="password"
placeholder='Password' placeholder="Password"
className={`${styles.password} ${unlockWalletError.isError && styles.inputError}`} className={`${styles.password} ${unlockWalletError.isError && styles.inputError}`}
ref={input => input && input.focus()} ref={input => input && input.focus()}
value={password} value={password}
onChange={event => updatePassword(event.target.value)} onChange={event => updatePassword(event.target.value)}
/> />
<p className={`${unlockWalletError.isError && styles.active} ${styles.error}`}> <p className={`${unlockWalletError.isError && styles.active} ${styles.error}`}>{unlockWalletError.message}</p>
{unlockWalletError.message}
</p>
<section className={styles.buttons}> <section className={styles.buttons}>
<div> <div>
<span className={`${!unlockingWallet && styles.active} ${styles.button}`} onClick={() => unlockWallet(password)}> <span className={`${!unlockingWallet && styles.active} ${styles.button}`} onClick={() => unlockWallet(password)}>
{ {unlockingWallet ? <i className={styles.spinner} /> : 'Unlock'}
unlockingWallet ?
<i className={styles.spinner} />
:
'Unlock'
}
</span> </span>
</div> </div>
</section> </section>

8
app/components/Onboarding/NewAezeedPassword.js

@ -12,8 +12,8 @@ const NewAezeedPassword = ({
<div className={styles.container}> <div className={styles.container}>
<section className={styles.input}> <section className={styles.input}>
<input <input
type='password' type="password"
placeholder='Password' placeholder="Password"
className={styles.password} className={styles.password}
value={aezeedPassword} value={aezeedPassword}
onChange={event => updateAezeedPassword(event.target.value)} onChange={event => updateAezeedPassword(event.target.value)}
@ -22,8 +22,8 @@ const NewAezeedPassword = ({
<section className={styles.input}> <section className={styles.input}>
<input <input
type='password' type="password"
placeholder='Confirm Password' placeholder="Confirm Password"
className={`${styles.password} ${showAezeedPasswordConfirmationError && styles.error}`} className={`${styles.password} ${showAezeedPasswordConfirmationError && styles.error}`}
value={aezeedPasswordConfirmation} value={aezeedPasswordConfirmation}
onChange={event => updateAezeedPasswordConfirmation(event.target.value)} onChange={event => updateAezeedPasswordConfirmation(event.target.value)}

8
app/components/Onboarding/NewWalletPassword.js

@ -12,8 +12,8 @@ const NewWalletPassword = ({
<div className={styles.container}> <div className={styles.container}>
<section className={styles.input}> <section className={styles.input}>
<input <input
type='password' type="password"
placeholder='Password' placeholder="Password"
className={styles.password} className={styles.password}
value={createWalletPassword} value={createWalletPassword}
onChange={event => updateCreateWalletPassword(event.target.value)} onChange={event => updateCreateWalletPassword(event.target.value)}
@ -22,8 +22,8 @@ const NewWalletPassword = ({
<section className={styles.input}> <section className={styles.input}>
<input <input
type='password' type="password"
placeholder='Confirm Password' placeholder="Confirm Password"
className={`${styles.password} ${showCreateWalletPasswordConfirmationError && styles.error}`} className={`${styles.password} ${showCreateWalletPasswordConfirmationError && styles.error}`}
value={createWalletPasswordConfirmation} value={createWalletPasswordConfirmation}
onChange={event => updateCreateWalletPasswordConfirmation(event.target.value)} onChange={event => updateCreateWalletPasswordConfirmation(event.target.value)}

22
app/components/Onboarding/NewWalletSeed.js

@ -5,18 +5,16 @@ import styles from './NewWalletSeed.scss'
const NewWalletSeed = ({ seed }) => ( const NewWalletSeed = ({ seed }) => (
<div className={styles.container}> <div className={styles.container}>
<ul className={styles.seedContainer}> <ul className={styles.seedContainer}>
{ {seed.map((word, index) => (
seed.map((word, index) => ( <li key={index}>
<li key={index}> <section>
<section> <label htmlFor={word}>{index + 1}</label>
<label htmlFor={word}>{index + 1}</label> </section>
</section> <section>
<section> <span>{word}</span>
<span>{word}</span> </section>
</section> </li>
</li> ))}
))
}
</ul> </ul>
</div> </div>
) )

54
app/components/Onboarding/Onboarding.js

@ -51,12 +51,12 @@ const Onboarding = ({
case 0.1: case 0.1:
return ( return (
<FormContainer <FormContainer
title='How do you want to connect to the Lightning Network?' title="How do you want to connect to the Lightning Network?"
description=' description="
By default Zap will spin up a node for you and handle all the nerdy stuff By default Zap will spin up a node for you and handle all the nerdy stuff
in the background. However you can also setup a custom node connection and in the background. However you can also setup a custom node connection and
use Zap to control a remote node if you desire (for advanced users). use Zap to control a remote node if you desire (for advanced users).
' "
back={null} back={null}
next={() => changeStep(connectionType === 'local' ? 1 : 0.2)} next={() => changeStep(connectionType === 'local' ? 1 : 0.2)}
> >
@ -67,8 +67,8 @@ const Onboarding = ({
case 0.2: case 0.2:
return ( return (
<FormContainer <FormContainer
title='Connection details' title="Connection details"
description='Enter the connection details for your Lightning node.' description="Enter the connection details for your Lightning node."
back={() => changeStep(0.1)} back={() => changeStep(0.1)}
next={() => next={() =>
startLnd({ startLnd({
@ -86,8 +86,8 @@ const Onboarding = ({
case 1: case 1:
return ( return (
<FormContainer <FormContainer
title='What should we call you?' title="What should we call you?"
description='Set your nickname to help others connect with you on the Lightning Network' description="Set your nickname to help others connect with you on the Lightning Network"
back={() => changeStep(0.1)} back={() => changeStep(0.1)}
next={() => changeStep(2)} next={() => changeStep(2)}
> >
@ -97,8 +97,8 @@ const Onboarding = ({
case 2: case 2:
return ( return (
<FormContainer <FormContainer
title='Autopilot' title="Autopilot"
description='Autopilot is an automatic network manager. Instead of manually adding people to build your network to make payments, enable autopilot to automatically connect you to the Lightning Network using 60% of your balance.' // eslint-disable-line description="Autopilot is an automatic network manager. Instead of manually adding people to build your network to make payments, enable autopilot to automatically connect you to the Lightning Network using 60% of your balance." // eslint-disable-line
back={() => changeStep(1)} back={() => changeStep(1)}
next={() => startLnd({ connectionType, alias, autopilot })} next={() => startLnd({ connectionType, alias, autopilot })}
> >
@ -108,8 +108,8 @@ const Onboarding = ({
case 3: case 3:
return ( return (
<FormContainer <FormContainer
title='Welcome back!' title="Welcome back!"
description='Enter your wallet password or create a new wallet' // eslint-disable-line description="Enter your wallet password or create a new wallet" // eslint-disable-line
back={null} back={null}
next={null} next={null}
> >
@ -119,8 +119,8 @@ const Onboarding = ({
case 4: case 4:
return ( return (
<FormContainer <FormContainer
title='Welcome!' title="Welcome!"
description='Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future' // eslint-disable-line description="Looks like you are new here. Set a password to encrypt your wallet. This password will be needed to unlock Zap in the future" // eslint-disable-line
back={null} back={null}
next={() => { next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password // dont allow the user to move on if the confirmation password doesnt match the original password
@ -137,8 +137,8 @@ const Onboarding = ({
case 5: case 5:
return ( return (
<FormContainer <FormContainer
title={'Alright, let\'s get set up'} title={"Alright, let's get set up"}
description='Would you like to create a new wallet or import an existing one?' // eslint-disable-line description="Would you like to create a new wallet or import an existing one?" // eslint-disable-line
back={() => changeStep(4)} back={() => changeStep(4)}
next={() => (initWalletProps.signupProps.signupForm.create ? changeStep(6) : changeStep(5.1))} next={() => (initWalletProps.signupProps.signupForm.create ? changeStep(6) : changeStep(5.1))}
> >
@ -148,8 +148,8 @@ const Onboarding = ({
case 5.1: case 5.1:
return ( return (
<FormContainer <FormContainer
title='Import your seed' title="Import your seed"
description={'Recovering a wallet, nice. You don\'t need anyone else, you got yourself :)'} // eslint-disable-line description={"Recovering a wallet, nice. You don't need anyone else, you got yourself :)"} // eslint-disable-line
back={() => changeStep(5)} back={() => changeStep(5)}
next={() => changeStep(5.2)} next={() => changeStep(5.2)}
> >
@ -159,8 +159,8 @@ const Onboarding = ({
case 5.2: case 5.2:
return ( return (
<FormContainer <FormContainer
title='Seed passphrase' title="Seed passphrase"
description={'Enter your cipherseed passphrase (or just submit if you don\'t have one)'} // eslint-disable-line description={"Enter your cipherseed passphrase (or just submit if you don't have one)"} // eslint-disable-line
back={() => changeStep(5)} back={() => changeStep(5)}
next={() => { next={() => {
const recoverySeed = recoverFormProps.seedInput.map(input => input.word) const recoverySeed = recoverFormProps.seedInput.map(input => input.word)
@ -174,8 +174,8 @@ const Onboarding = ({
case 6: case 6:
return ( return (
<FormContainer <FormContainer
title='Save your wallet seed' title="Save your wallet seed"
description='Please save these 24 words securely! This will allow you to recover your wallet in the future' // eslint-disable-line description="Please save these 24 words securely! This will allow you to recover your wallet in the future" // eslint-disable-line
back={() => changeStep(5)} back={() => changeStep(5)}
next={() => changeStep(7)} next={() => changeStep(7)}
> >
@ -185,8 +185,8 @@ const Onboarding = ({
case 7: case 7:
return ( return (
<FormContainer <FormContainer
title='Re-enter your seed' title="Re-enter your seed"
description='Yeah I know, might be annoying, but just to be safe!' // eslint-disable-line description="Yeah I know, might be annoying, but just to be safe!" // eslint-disable-line
back={() => changeStep(6)} back={() => changeStep(6)}
next={() => { next={() => {
// don't allow them to move on if they havent re-entered the seed correctly // don't allow them to move on if they havent re-entered the seed correctly
@ -203,12 +203,14 @@ const Onboarding = ({
case 8: case 8:
return ( return (
<FormContainer <FormContainer
title='Encrypt your seed' title="Encrypt your seed"
description='Totally optional, but we encourage it. Set a password that will be used to encrypt your wallet seed' // eslint-disable-line description="Totally optional, but we encourage it. Set a password that will be used to encrypt your wallet seed" // eslint-disable-line
back={() => changeStep(6)} back={() => changeStep(6)}
next={() => { next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password // dont allow the user to move on if the confirmation password doesnt match the original password
if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) { return } if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) {
return
}
submitNewWallet(createWalletPassword, seed, aezeedPassword) submitNewWallet(createWalletPassword, seed, aezeedPassword)
}} }}

37
app/components/Onboarding/ReEnterSeed.js

@ -5,30 +5,27 @@ import styles from './ReEnterSeed.scss'
const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => ( const ReEnterSeed = ({ seed, seedInput, updateSeedInput }) => (
<div className={styles.container}> <div className={styles.container}>
<ul className={styles.seedContainer}> <ul className={styles.seedContainer}>
{ {seed.map((word, index) => (
seed.map((word, index) => ( <li key={index}>
<li key={index}> <section>
<section> <label htmlFor={word}>{index + 1}</label>
<label htmlFor={word}>{index + 1}</label> </section>
</section> <section>
<section> <input
<input type="text"
type='text' id={word}
id={word} placeholder="word"
placeholder='word' value={seedInput[index] ? seedInput[index].word : ''}
value={seedInput[index] ? seedInput[index].word : ''} onChange={event => updateSeedInput({ word: event.target.value, index })}
onChange={event => updateSeedInput({ word: event.target.value, index })} className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`}
className={`${styles.word} ${seedInput[index] && word === seedInput[index].word ? styles.valid : styles.invalid}`} />
/> </section>
</section> </li>
</li> ))}
))
}
</ul> </ul>
</div> </div>
) )
ReEnterSeed.propTypes = { ReEnterSeed.propTypes = {
seed: PropTypes.array.isRequired, seed: PropTypes.array.isRequired,
seedInput: PropTypes.array.isRequired, seedInput: PropTypes.array.isRequired,

13
app/components/Onboarding/RecoverForm.js

@ -5,30 +5,29 @@ import styles from './RecoverForm.scss'
const RecoverForm = ({ seedInput, updateSeedInput }) => ( const RecoverForm = ({ seedInput, updateSeedInput }) => (
<div className={styles.container}> <div className={styles.container}>
<ul className={styles.seedContainer}> <ul className={styles.seedContainer}>
{ {Array(24)
Array(24).fill('').map((word, index) => ( .fill('')
.map((word, index) => (
<li key={index}> <li key={index}>
<section> <section>
<label htmlFor={index}>{index + 1}</label> <label htmlFor={index}>{index + 1}</label>
</section> </section>
<section> <section>
<input <input
type='text' type="text"
id={index} id={index}
placeholder='word' placeholder="word"
value={seedInput[index] ? seedInput[index].word : ''} value={seedInput[index] ? seedInput[index].word : ''}
onChange={event => updateSeedInput({ word: event.target.value, index })} onChange={event => updateSeedInput({ word: event.target.value, index })}
className={styles.word} className={styles.word}
/> />
</section> </section>
</li> </li>
)) ))}
}
</ul> </ul>
</div> </div>
) )
RecoverForm.propTypes = { RecoverForm.propTypes = {
seedInput: PropTypes.array.isRequired, seedInput: PropTypes.array.isRequired,
updateSeedInput: PropTypes.func.isRequired updateSeedInput: PropTypes.func.isRequired

22
app/components/Onboarding/Signup.js

@ -7,28 +7,14 @@ const Signup = ({ signupForm, setSignupCreate, setSignupImport }) => (
<div className={styles.container}> <div className={styles.container}>
<section className={`${styles.enable} ${signupForm.create && styles.active}`}> <section className={`${styles.enable} ${signupForm.create && styles.active}`}>
<div onClick={setSignupCreate}> <div onClick={setSignupCreate}>
{ {signupForm.create ? <FaCircle /> : <FaCircleThin />}
signupForm.create ? <span className={styles.label}>Create new wallet</span>
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Create new wallet
</span>
</div> </div>
</section> </section>
<section className={`${styles.disable} ${signupForm.import && styles.active}`}> <section className={`${styles.disable} ${signupForm.import && styles.active}`}>
<div onClick={setSignupImport}> <div onClick={setSignupImport}>
{ {signupForm.import ? <FaCircle /> : <FaCircleThin />}
signupForm.import ? <span className={styles.label}>Import existing wallet</span>
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Import existing wallet
</span>
</div> </div>
</section> </section>
</div> </div>

6
app/components/Onboarding/Syncing.js

@ -4,7 +4,6 @@ import Isvg from 'react-inlinesvg'
import zapLogo from 'icons/zap_logo.svg' import zapLogo from 'icons/zap_logo.svg'
import styles from './Syncing.scss' import styles from './Syncing.scss'
class Syncing extends Component { class Syncing extends Component {
componentWillMount() { componentWillMount() {
this.props.fetchBlockHeight() this.props.fetchBlockHeight()
@ -36,10 +35,7 @@ class Syncing extends Component {
Syncing.propTypes = { Syncing.propTypes = {
fetchBlockHeight: PropTypes.func.isRequired, fetchBlockHeight: PropTypes.func.isRequired,
syncPercentage: PropTypes.oneOfType([ syncPercentage: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
PropTypes.number,
PropTypes.string
]).isRequired
} }
export default Syncing export default Syncing

13
app/components/Value/Value.js

@ -3,18 +3,15 @@ import PropTypes from 'prop-types'
import { btc } from 'utils' import { btc } from 'utils'
const Value = ({ value, currency, currentTicker }) => { const Value = ({ value, currency, currentTicker }) => {
if (currency === 'sats') { return <i>{value > 0 ? value : value * -1}</i> } if (currency === 'sats') {
return <i>{value > 0 ? value : value * -1}</i>
}
return ( return <i>{btc.convert('sats', currency, value, currentTicker.price_usd)}</i>
<i>{btc.convert('sats', currency, value, currentTicker.price_usd)}</i>
)
} }
Value.propTypes = { Value.propTypes = {
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
PropTypes.string,
PropTypes.number
]),
currency: PropTypes.string.isRequired, currency: PropTypes.string.isRequired,
currentTicker: PropTypes.object.isRequired currentTicker: PropTypes.object.isRequired
} }

36
app/components/Wallet/ReceiveModal.js

@ -20,7 +20,7 @@ class ReceiveModal extends React.Component {
} }
render() { render() {
const copyOnClick = (data) => { const copyOnClick = data => {
copy(data) copy(data)
showNotification('Noice', 'Successfully copied to clipboard') showNotification('Noice', 'Successfully copied to clipboard')
} }
@ -33,18 +33,13 @@ class ReceiveModal extends React.Component {
} }
} }
const { const { isOpen, pubkey, address, alias, closeReceiveModal, network } = this.props
isOpen,
pubkey,
address,
alias,
closeReceiveModal,
network
} = this.props
const { qrCodeType } = this.state const { qrCodeType } = this.state
if (!isOpen) { return null } if (!isOpen) {
return null
}
return ( return (
<div className={styles.container}> <div className={styles.container}>
@ -60,20 +55,17 @@ class ReceiveModal extends React.Component {
<h2>{alias && alias.length ? alias : pubkey.substring(0, 10)}</h2> <h2>{alias && alias.length ? alias : pubkey.substring(0, 10)}</h2>
<div className={styles.qrCodeOptions}> <div className={styles.qrCodeOptions}>
<div className={qrCodeType === 1 && styles.active} onClick={changeQrCode}>Node Pubkey</div> <div className={qrCodeType === 1 && styles.active} onClick={changeQrCode}>
<div className={qrCodeType === 2 && styles.active} onClick={changeQrCode}>Bitcoin Address</div> Node Pubkey
</div>
<div className={qrCodeType === 2 && styles.active} onClick={changeQrCode}>
Bitcoin Address
</div>
</div> </div>
</header> </header>
<div className={styles.qrCodeContainer}> <div className={styles.qrCodeContainer}>
<QRCode <QRCode value={qrCodeType === 1 ? pubkey : address} renderAs="svg" size={150} bgColor="transparent" fgColor="white" level="L" />
value={qrCodeType === 1 ? pubkey : address}
renderAs='svg'
size={150}
bgColor='transparent'
fgColor='white'
level='L'
/>
</div> </div>
</section> </section>
<section className={styles.right}> <section className={styles.right}>
@ -81,7 +73,7 @@ class ReceiveModal extends React.Component {
<h4>Node Public Key</h4> <h4>Node Public Key</h4>
<p> <p>
<span className={styles.data}>{pubkey}</span> <span className={styles.data}>{pubkey}</span>
<span onClick={() => copyOnClick(pubkey)} className={`${styles.copy} hint--left`} data-hint='Copy pubkey'> <span onClick={() => copyOnClick(pubkey)} className={`${styles.copy} hint--left`} data-hint="Copy pubkey">
<Isvg src={copyIcon} /> <Isvg src={copyIcon} />
</span> </span>
</p> </p>
@ -91,7 +83,7 @@ class ReceiveModal extends React.Component {
<h4>Bitcoin {network.name} Address</h4> <h4>Bitcoin {network.name} Address</h4>
<p> <p>
<span className={styles.data}>{address}</span> <span className={styles.data}>{address}</span>
<span onClick={() => copyOnClick(address)} className={`${styles.copy} hint--left`} data-hint='Copy address'> <span onClick={() => copyOnClick(address)} className={`${styles.copy} hint--left`} data-hint="Copy address">
<Isvg src={copyIcon} /> <Isvg src={copyIcon} />
</span> </span>
</p> </p>

72
app/components/Wallet/Wallet.js

@ -29,9 +29,9 @@ const Wallet = ({
setWalletCurrencyFilters, setWalletCurrencyFilters,
network network
}) => { }) => {
const usdAmount = btc.satoshisToUsd((parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10)), currentTicker.price_usd) const usdAmount = btc.satoshisToUsd(parseInt(balance.walletBalance, 10) + parseInt(balance.channelBalance, 10), currentTicker.price_usd)
const onCurrencyFilterClick = (currency) => { const onCurrencyFilterClick = currency => {
setCurrency(currency) setCurrency(currency)
setWalletCurrencyFilters(false) setWalletCurrencyFilters(false)
} }
@ -73,10 +73,11 @@ const Wallet = ({
</span> </span>
<ul className={info.showWalletCurrencyFilters && styles.active}> <ul className={info.showWalletCurrencyFilters && styles.active}>
{ {currentCurrencyFilters.map(filter => (
currentCurrencyFilters.map(filter => <li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) {filter.name}
} </li>
))}
</ul> </ul>
</section> </section>
</span> </span>
@ -87,36 +88,41 @@ const Wallet = ({
</div> </div>
<div className={styles.right}> <div className={styles.right}>
<div className={styles.rightContent}> <div className={styles.rightContent}>
<div className={styles.pay} onClick={openPayForm}>Pay</div> <div className={styles.pay} onClick={openPayForm}>
<div className={styles.request} onClick={openRequestForm}>Request</div> Pay
</div>
<div className={styles.request} onClick={openRequestForm}>
Request
</div>
</div> </div>
<div className={styles.notificationBox}> <div className={styles.notificationBox}>
{ {showPayLoadingScreen && (
showPayLoadingScreen && <span>
<span> <section className={`${styles.spinner} ${styles.icon}`} />
<section className={`${styles.spinner} ${styles.icon}`} /> <section>Sending your transaction...</section>
<section>Sending your transaction...</section> </span>
</span> )}
} {showSuccessPayScreen && (
{ <span>
showSuccessPayScreen && <section className={styles.icon}>
<span> <AnimatedCheckmark />
<section className={styles.icon}><AnimatedCheckmark /></section> </section>
<section>Successfully sent payment</section> <section>Successfully sent payment</section>
</span> </span>
} )}
{ {successTransactionScreen.show && (
successTransactionScreen.show && <span>
<span> <section className={styles.icon}>
<section className={styles.icon}><AnimatedCheckmark /></section> <AnimatedCheckmark />
<section> </section>
{ <section>
// TODO(jimmymow): remove this {
// eslint-disable-next-line // TODO(jimmymow): remove this
// eslint-disable-next-line
}Successfully <span className={styles.txLink} onClick={() => blockExplorer.showTransaction(network, successTransactionScreen.txid)}>sent</span> transaction }Successfully <span className={styles.txLink} onClick={() => blockExplorer.showTransaction(network, successTransactionScreen.txid)}>sent</span> transaction
</section> </section>
</span> </span>
} )}
</div> </div>
</div> </div>
</div> </div>

6
app/containers/Root.js

@ -219,4 +219,8 @@ Root.propTypes = {
syncingProps: PropTypes.object.isRequired syncingProps: PropTypes.object.isRequired
} }
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Root) export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(Root)

4
app/lnd/index.js

@ -7,7 +7,7 @@ import walletUnlockerMethods from './walletUnlockerMethods'
// use mainLog because lndLog is reserved for the lnd binary itself // use mainLog because lndLog is reserved for the lnd binary itself
import { mainLog } from '../utils/log' import { mainLog } from '../utils/log'
const initLnd = (callback) => { const initLnd = callback => {
const lndConfig = config.lnd() const lndConfig = config.lnd()
const lnd = lightning(lndConfig.lightningRpc, lndConfig.lightningHost) const lnd = lightning(lndConfig.lightningRpc, lndConfig.lightningHost)
@ -17,7 +17,7 @@ const initLnd = (callback) => {
callback(lndSubscribe, lndMethods) callback(lndSubscribe, lndMethods)
} }
const initWalletUnlocker = (callback) => { const initWalletUnlocker = callback => {
const lndConfig = config.lnd() const lndConfig = config.lnd()
const walletUnlockerObj = walletUnlocker(lndConfig.lightningRpc, lndConfig.lightningHost) const walletUnlockerObj = walletUnlocker(lndConfig.lightningRpc, lndConfig.lightningHost)

9
app/lnd/lib/lightning.js

@ -10,12 +10,9 @@ import config from '../config'
// //
// We order the suites by priority, based on the recommendations provided by SSL Labs here: // We order the suites by priority, based on the recommendations provided by SSL Labs here:
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites // https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites
process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [ process.env.GRPC_SSL_CIPHER_SUITES =
'ECDHE-ECDSA-AES128-GCM-SHA256', process.env.GRPC_SSL_CIPHER_SUITES ||
'ECDHE-ECDSA-AES256-GCM-SHA384', ['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES128-CBC-SHA256', 'ECDHE-ECDSA-CHACHA20-POLY1305'].join(':')
'ECDHE-ECDSA-AES128-CBC-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305'
].join(':')
const lightning = (rpcpath, host) => { const lightning = (rpcpath, host) => {
const lndConfig = config.lnd() const lndConfig = config.lnd()

9
app/lnd/lib/walletUnlocker.js

@ -10,12 +10,9 @@ import config from '../config'
// //
// We order the suites by priority, based on the recommendations provided by SSL Labs here: // We order the suites by priority, based on the recommendations provided by SSL Labs here:
// https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites // https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites
process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [ process.env.GRPC_SSL_CIPHER_SUITES =
'ECDHE-ECDSA-AES128-GCM-SHA256', process.env.GRPC_SSL_CIPHER_SUITES ||
'ECDHE-ECDSA-AES256-GCM-SHA384', ['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES128-CBC-SHA256', 'ECDHE-ECDSA-CHACHA20-POLY1305'].join(':')
'ECDHE-ECDSA-AES128-CBC-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305'
].join(':')
const walletUnlocker = (rpcpath, host) => { const walletUnlocker = (rpcpath, host) => {
const lndConfig = config.lnd() const lndConfig = config.lnd()

16
app/lnd/methods/channelController.js

@ -34,7 +34,7 @@ export function connectAndOpen(lnd, event, payload) {
return call return call
}) })
.catch((err) => { .catch(err => {
event.sender.send('pushchannelerror', { pubkey, error: err.toString() }) event.sender.send('pushchannelerror', { pubkey, error: err.toString() })
throw err throw err
}) })
@ -58,7 +58,8 @@ export function openChannel(lnd, event, payload) {
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
pushopenchannel(lnd, event, res) pushopenchannel(lnd, event, res)
.then(data => resolve(data)) .then(data => resolve(data))
.catch(error => reject(error))) .catch(error => reject(error))
)
} }
/** /**
@ -103,8 +104,15 @@ export function listChannels(lnd) {
* @return {[type]} [description] * @return {[type]} [description]
*/ */
export function closeChannel(lnd, event, payload) { export function closeChannel(lnd, event, payload) {
const { channel_point: { funding_txid, output_index }, chan_id, force } = payload const {
const tx = funding_txid.match(/.{2}/g).reverse().join('') channel_point: { funding_txid, output_index },
chan_id,
force
} = payload
const tx = funding_txid
.match(/.{2}/g)
.reverse()
.join('')
const res = { const res = {
channel_point: { channel_point: {

35
app/lnd/methods/index.js

@ -17,12 +17,12 @@ import * as networkController from './networkController'
// TODO - SendPayment // TODO - SendPayment
// TODO - DeleteAllPayments // TODO - DeleteAllPayments
export default function (lnd, log, event, msg, data) { export default function(lnd, log, event, msg, data) {
switch (msg) { switch (msg) {
case 'info': case 'info':
networkController networkController
.getInfo(lnd) .getInfo(lnd)
.then((infoData) => { .then(infoData => {
event.sender.send('receiveInfo', infoData) event.sender.send('receiveInfo', infoData)
event.sender.send('receiveCryptocurrency', infoData.chains[0]) event.sender.send('receiveCryptocurrency', infoData.chains[0])
return infoData return infoData
@ -50,7 +50,8 @@ export default function (lnd, log, event, msg, data) {
networkController.queryRoutes(lnd, { networkController.queryRoutes(lnd, {
pubkey: invoiceData.destination, pubkey: invoiceData.destination,
amount: invoiceData.num_satoshis amount: invoiceData.num_satoshis
})) })
)
.then(routes => event.sender.send('receiveInvoiceAndQueryRoutes', routes)) .then(routes => event.sender.send('receiveInvoiceAndQueryRoutes', routes))
.catch(error => log.error('getInvoiceAndQueryRoutes invoice:', error)) .catch(error => log.error('getInvoiceAndQueryRoutes invoice:', error))
break break
@ -113,7 +114,7 @@ export default function (lnd, log, event, msg, data) {
case 'balance': case 'balance':
// Balance looks like [ { balance: '129477456' }, { balance: '243914' } ] // Balance looks like [ { balance: '129477456' }, { balance: '243914' } ]
Promise.all([walletController.walletBalance, channelController.channelBalance].map(func => func(lnd))) Promise.all([walletController.walletBalance, channelController.channelBalance].map(func => func(lnd)))
.then((balance) => { .then(balance => {
event.sender.send('receiveBalance', { walletBalance: balance[0].total_balance, channelBalance: balance[1].balance }) event.sender.send('receiveBalance', { walletBalance: balance[0].total_balance, channelBalance: balance[1].balance })
return balance return balance
}) })
@ -137,12 +138,14 @@ export default function (lnd, log, event, msg, data) {
payment_request: newinvoice.payment_request, payment_request: newinvoice.payment_request,
creation_date: Date.now() / 1000 creation_date: Date.now() / 1000
}) })
)) )
.catch((error) => { )
.catch(error => {
log.error('decodedInvoice:', error) log.error('decodedInvoice:', error)
event.sender.send('invoiceFailed', { error: error.toString() }) event.sender.send('invoiceFailed', { error: error.toString() })
})) })
.catch((error) => { )
.catch(error => {
log.error('addInvoice:', error) log.error('addInvoice:', error)
event.sender.send('invoiceFailed', { error: error.toString() }) event.sender.send('invoiceFailed', { error: error.toString() })
}) })
@ -152,7 +155,7 @@ export default function (lnd, log, event, msg, data) {
// { paymentRequest } = data // { paymentRequest } = data
paymentsController paymentsController
.sendPaymentSync(lnd, data) .sendPaymentSync(lnd, data)
.then((payment) => { .then(payment => {
log.info('payment:', payment) log.info('payment:', payment)
const { payment_route } = payment const { payment_route } = payment
log.error('payinvoice success:', payment_route) log.error('payinvoice success:', payment_route)
@ -170,7 +173,7 @@ export default function (lnd, log, event, msg, data) {
walletController walletController
.sendCoins(lnd, data) .sendCoins(lnd, data)
.then(({ txid }) => event.sender.send('transactionSuccessful', { amount: data.amount, addr: data.addr, txid })) .then(({ txid }) => event.sender.send('transactionSuccessful', { amount: data.amount, addr: data.addr, txid }))
.catch((error) => { .catch(error => {
log.error('error: ', error) log.error('error: ', error)
event.sender.send('transactionError', { error: error.toString() }) event.sender.send('transactionError', { error: error.toString() })
}) })
@ -180,7 +183,7 @@ export default function (lnd, log, event, msg, data) {
// { pubkey, localamt, pushamt } = data // { pubkey, localamt, pushamt } = data
channelController channelController
.openChannel(lnd, event, data) .openChannel(lnd, event, data)
.then((channel) => { .then(channel => {
log.error('CHANNEL: ', channel) log.error('CHANNEL: ', channel)
event.sender.send('channelSuccessful', { channel }) event.sender.send('channelSuccessful', { channel })
return channel return channel
@ -192,7 +195,7 @@ export default function (lnd, log, event, msg, data) {
// { channel_point, force } = data // { channel_point, force } = data
channelController channelController
.closeChannel(lnd, event, data) .closeChannel(lnd, event, data)
.then((result) => { .then(result => {
log.error('CLOSE CHANNEL: ', result) log.error('CLOSE CHANNEL: ', result)
event.sender.send('closeChannelSuccessful') event.sender.send('closeChannelSuccessful')
return result return result
@ -204,13 +207,13 @@ export default function (lnd, log, event, msg, data) {
// { pubkey, host } = data // { pubkey, host } = data
peersController peersController
.connectPeer(lnd, data) .connectPeer(lnd, data)
.then((peer) => { .then(peer => {
const { peer_id } = peer const { peer_id } = peer
log.error('peer_id: ', peer_id) log.error('peer_id: ', peer_id)
event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id }) event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id })
return peer return peer
}) })
.catch((error) => { .catch(error => {
event.sender.send('connectFailure', { error: error.toString() }) event.sender.send('connectFailure', { error: error.toString() })
log.error('connectPeer:', error) log.error('connectPeer:', error)
}) })
@ -232,12 +235,12 @@ export default function (lnd, log, event, msg, data) {
// {} = data // {} = data
channelController channelController
.connectAndOpen(lnd, event, data) .connectAndOpen(lnd, event, data)
.then((channelData) => { .then(channelData => {
log.error('connectAndOpen data: ', channelData) log.error('connectAndOpen data: ', channelData)
// event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id }) // event.sender.send('connectSuccess', { pub_key: data.pubkey, address: data.host, peer_id })
return channelData return channelData
}) })
.catch((error) => { .catch(error => {
// event.sender.send('connectFailure', { error: error.toString() }) // event.sender.send('connectFailure', { error: error.toString() })
log.error('connectAndOpen:', error) log.error('connectAndOpen:', error)
}) })

18
app/lnd/methods/invoicesController.js

@ -10,7 +10,9 @@ import pushinvoices from '../push/subscribeinvoice'
export function addInvoice(lnd, { memo, value }) { export function addInvoice(lnd, { memo, value }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.addInvoice({ memo, value }, (err, data) => { lnd.addInvoice({ memo, value }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -25,7 +27,9 @@ export function addInvoice(lnd, { memo, value }) {
export function listInvoices(lnd) { export function listInvoices(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.listInvoices({}, (err, data) => { lnd.listInvoices({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -39,14 +43,15 @@ export function listInvoices(lnd) {
export function getInvoice(lnd, { pay_req }) { export function getInvoice(lnd, { pay_req }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.decodePayReq({ pay_req }, (err, data) => { lnd.decodePayReq({ pay_req }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Attemps to look up an invoice according to its payment hash * Attemps to look up an invoice according to its payment hash
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -56,14 +61,15 @@ export function getInvoice(lnd, { pay_req }) {
export function lookupInvoice(lnd, { rhash }) { export function lookupInvoice(lnd, { rhash }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.lookupInvoice({ r_hash: rhash }, (err, data) => { lnd.lookupInvoice({ r_hash: rhash }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns a uni-directional stream (server -> client) for notifying the client of newly added/settled invoices * Returns a uni-directional stream (server -> client) for notifying the client of newly added/settled invoices
* @param {[type]} lnd [description] * @param {[type]} lnd [description]

24
app/lnd/methods/networkController.js

@ -6,14 +6,15 @@
export function getInfo(lnd) { export function getInfo(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.getInfo({}, (err, data) => { lnd.getInfo({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns general information concerning the lightning node * Returns general information concerning the lightning node
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -23,14 +24,15 @@ export function getInfo(lnd) {
export function getNodeInfo(lnd, { pubkey }) { export function getNodeInfo(lnd, { pubkey }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.getNodeInfo({ pub_key: pubkey }, (err, data) => { lnd.getNodeInfo({ pub_key: pubkey }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns a description of the latest graph state from the point of view of the node * Returns a description of the latest graph state from the point of view of the node
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -39,14 +41,15 @@ export function getNodeInfo(lnd, { pubkey }) {
export function describeGraph(lnd) { export function describeGraph(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.describeGraph({}, (err, data) => { lnd.describeGraph({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Attempts to query the daemons Channel Router for a possible route to a target destination capable of carrying a specific amount of satoshis * Attempts to query the daemons Channel Router for a possible route to a target destination capable of carrying a specific amount of satoshis
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -57,14 +60,15 @@ export function describeGraph(lnd) {
export function queryRoutes(lnd, { pubkey, amount }) { export function queryRoutes(lnd, { pubkey, amount }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.queryRoutes({ pub_key: pubkey, amt: amount }, (err, data) => { lnd.queryRoutes({ pub_key: pubkey, amt: amount }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns some basic stats about the known channel graph from the point of view of the node * Returns some basic stats about the known channel graph from the point of view of the node
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -73,7 +77,9 @@ export function queryRoutes(lnd, { pubkey, amount }) {
export function getNetworkInfo(lnd) { export function getNetworkInfo(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.getNetworkInfo({}, (err, data) => { lnd.getNetworkInfo({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })

16
app/lnd/methods/paymentsController.js

@ -27,7 +27,9 @@ export function sendPaymentSync(lnd, { paymentRequest }) {
export function sendPayment(lnd, { paymentRequest }) { export function sendPayment(lnd, { paymentRequest }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.sendPayment({ payment_request: paymentRequest }, (err, data) => { lnd.sendPayment({ payment_request: paymentRequest }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -43,7 +45,9 @@ export function sendPayment(lnd, { paymentRequest }) {
export function decodePayReq(lnd, { payReq }) { export function decodePayReq(lnd, { payReq }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.decodePayReq({ pay_req: payReq }, (err, data) => { lnd.decodePayReq({ pay_req: payReq }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -58,7 +62,9 @@ export function decodePayReq(lnd, { payReq }) {
export function listPayments(lnd) { export function listPayments(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.listPayments({}, (err, data) => { lnd.listPayments({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -73,7 +79,9 @@ export function listPayments(lnd) {
export function deleteAllPayments(lnd) { export function deleteAllPayments(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.deleteAllPayments({}, (err, data) => { lnd.deleteAllPayments({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })

14
app/lnd/methods/peersController.js

@ -8,14 +8,15 @@
export function connectPeer(lnd, { pubkey, host }) { export function connectPeer(lnd, { pubkey, host }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.connectPeer({ addr: { pubkey, host } }, (err, data) => { lnd.connectPeer({ addr: { pubkey, host } }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Attempts to disconnect one peer from another * Attempts to disconnect one peer from another
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -25,14 +26,15 @@ export function connectPeer(lnd, { pubkey, host }) {
export function disconnectPeer(lnd, { pubkey }) { export function disconnectPeer(lnd, { pubkey }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.disconnectPeer({ pub_key: pubkey }, (err, data) => { lnd.disconnectPeer({ pub_key: pubkey }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns a verbose listing of all currently active peers * Returns a verbose listing of all currently active peers
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -41,7 +43,9 @@ export function disconnectPeer(lnd, { pubkey }) {
export function listPeers(lnd) { export function listPeers(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.listPeers({}, (err, data) => { lnd.listPeers({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })

60
app/lnd/methods/walletController.js

@ -6,14 +6,15 @@
export function walletBalance(lnd) { export function walletBalance(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.walletBalance({}, (err, data) => { lnd.walletBalance({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Creates a new address under control of the local wallet * Creates a new address under control of the local wallet
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -23,7 +24,9 @@ export function walletBalance(lnd) {
export function newAddress(lnd, type) { export function newAddress(lnd, type) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.newAddress({ type }, (err, data) => { lnd.newAddress({ type }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -38,14 +41,15 @@ export function newAddress(lnd, type) {
export function newWitnessAddress(lnd, { addr }) { export function newWitnessAddress(lnd, { addr }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.newWitnessAddress({ address: addr }, (err, data) => { lnd.newWitnessAddress({ address: addr }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Returns a list describing all the known transactions relevant to the wallet * Returns a list describing all the known transactions relevant to the wallet
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -54,14 +58,15 @@ export function newWitnessAddress(lnd, { addr }) {
export function getTransactions(lnd) { export function getTransactions(lnd) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.getTransactions({}, (err, data) => { lnd.getTransactions({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
}) })
} }
/** /**
* Executes a request to send coins to a particular address * Executes a request to send coins to a particular address
* @param {[type]} lnd [description] * @param {[type]} lnd [description]
@ -72,7 +77,9 @@ export function getTransactions(lnd) {
export function sendCoins(lnd, { addr, amount }) { export function sendCoins(lnd, { addr, amount }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.sendCoins({ addr, amount }, (err, data) => { lnd.sendCoins({ addr, amount }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -86,7 +93,9 @@ export function sendCoins(lnd, { addr, amount }) {
export function setAlias(lnd, { new_alias }) { export function setAlias(lnd, { new_alias }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lnd.setAlias({ new_alias }, (err, data) => { lnd.setAlias({ new_alias }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -99,7 +108,9 @@ export function setAlias(lnd, { new_alias }) {
export function genSeed(walletUnlocker) { export function genSeed(walletUnlocker) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
walletUnlocker.genSeed({}, (err, data) => { walletUnlocker.genSeed({}, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -113,7 +124,9 @@ export function genSeed(walletUnlocker) {
export function unlockWallet(walletUnlocker, { wallet_password }) { export function unlockWallet(walletUnlocker, { wallet_password }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
walletUnlocker.unlockWallet({ wallet_password: Buffer.from(wallet_password) }, (err, data) => { walletUnlocker.unlockWallet({ wallet_password: Buffer.from(wallet_password) }, (err, data) => {
if (err) { reject(err) } if (err) {
reject(err)
}
resolve(data) resolve(data)
}) })
@ -127,15 +140,20 @@ export function unlockWallet(walletUnlocker, { wallet_password }) {
*/ */
export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic, aezeed_passphrase }) { export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic, aezeed_passphrase }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
walletUnlocker.initWallet({ walletUnlocker.initWallet(
wallet_password: Buffer.from(wallet_password), {
cipher_seed_mnemonic, wallet_password: Buffer.from(wallet_password),
aezeed_passphrase: Buffer.from(aezeed_passphrase, 'hex'), cipher_seed_mnemonic,
recovery_window: 250 aezeed_passphrase: Buffer.from(aezeed_passphrase, 'hex'),
}, (err, data) => { recovery_window: 250
if (err) { reject(err) } },
(err, data) => {
resolve(data) if (err) {
}) reject(err)
}
resolve(data)
}
)
}) })
} }

2
app/lnd/subscribe/transactions.js

@ -1,6 +1,6 @@
export default function subscribeToTransactions(mainWindow, lnd, log) { export default function subscribeToTransactions(mainWindow, lnd, log) {
const call = lnd.subscribeTransactions({}) const call = lnd.subscribeTransactions({})
call.on('data', (transaction) => { call.on('data', transaction => {
lnd.log.info('TRANSACTION:', transaction) lnd.log.info('TRANSACTION:', transaction)
mainWindow.send('newTransaction', { transaction }) mainWindow.send('newTransaction', { transaction })
}) })

11
app/lnd/walletUnlockerMethods/index.js

@ -1,19 +1,22 @@
import * as walletController from '../methods/walletController' import * as walletController from '../methods/walletController'
export default function (walletUnlocker, log, event, msg, data) { export default function(walletUnlocker, log, event, msg, data) {
switch (msg) { switch (msg) {
case 'genSeed': case 'genSeed':
walletController.genSeed(walletUnlocker) walletController
.genSeed(walletUnlocker)
.then(genSeedData => event.sender.send('receiveSeed', genSeedData)) .then(genSeedData => event.sender.send('receiveSeed', genSeedData))
.catch(error => event.sender.send('receiveSeedError', error)) .catch(error => event.sender.send('receiveSeedError', error))
break break
case 'unlockWallet': case 'unlockWallet':
walletController.unlockWallet(walletUnlocker, data) walletController
.unlockWallet(walletUnlocker, data)
.then(() => event.sender.send('walletUnlocked')) .then(() => event.sender.send('walletUnlocked'))
.catch(() => event.sender.send('unlockWalletError')) .catch(() => event.sender.send('unlockWalletError'))
break break
case 'initWallet': case 'initWallet':
walletController.initWallet(walletUnlocker, data) walletController
.initWallet(walletUnlocker, data)
.then(() => event.sender.send('successfullyCreatedWallet')) .then(() => event.sender.send('successfullyCreatedWallet'))
.catch(error => log.error('initWallet:', error)) .catch(error => log.error('initWallet:', error))
break break

10
app/main.dev.js

@ -120,7 +120,7 @@ const startGrpc = () => {
// Create and subscribe the grpc object // Create and subscribe the grpc object
const startWalletUnlocker = () => { const startWalletUnlocker = () => {
lnd.initWalletUnlocker((walletUnlockerMethods) => { lnd.initWalletUnlocker(walletUnlockerMethods => {
// Listen for all gRPC restful methods // Listen for all gRPC restful methods
ipcMain.on('walletUnlocker', (event, { msg, data }) => { ipcMain.on('walletUnlocker', (event, { msg, data }) => {
walletUnlockerMethods(event, msg, data) walletUnlockerMethods(event, msg, data)
@ -166,27 +166,27 @@ const startLnd = (alias, autopilot) => {
] ]
const neutrino = spawn(lndConfig.lndPath, neutrinoArgs) const neutrino = spawn(lndConfig.lndPath, neutrinoArgs)
.on('error', (error) => { .on('error', error => {
lndLog.error(`lnd error: ${error}`) lndLog.error(`lnd error: ${error}`)
dialog.showMessageBox({ dialog.showMessageBox({
type: 'error', type: 'error',
message: `lnd error: ${error}` message: `lnd error: ${error}`
}) })
}) })
.on('close', (code) => { .on('close', code => {
lndLog.info(`lnd shutting down ${code}`) lndLog.info(`lnd shutting down ${code}`)
app.quit() app.quit()
}) })
// Listen for when neutrino prints odata to stderr. // Listen for when neutrino prints odata to stderr.
neutrino.stderr.pipe(split2()).on('data', (line) => { neutrino.stderr.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line) lndLog[lndLogGetLevel(line)](line)
} }
}) })
// Listen for when neutrino prints data to stdout. // Listen for when neutrino prints data to stdout.
neutrino.stdout.pipe(split2()).on('data', (line) => { neutrino.stdout.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line) lndLog[lndLogGetLevel(line)](line)
} }

228
app/menu.js

@ -2,7 +2,7 @@
import { app, Menu, shell, BrowserWindow } from 'electron' import { app, Menu, shell, BrowserWindow } from 'electron'
export default class MenuBuilder { export default class MenuBuilder {
mainWindow: BrowserWindow; mainWindow: BrowserWindow
constructor(mainWindow: BrowserWindow) { constructor(mainWindow: BrowserWindow) {
this.mainWindow = mainWindow this.mainWindow = mainWindow
@ -26,16 +26,11 @@ export default class MenuBuilder {
const menu = Menu.buildFromTemplate(template) const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu) Menu.setApplicationMenu(menu)
return menu return menu
} }
setupInputTemplate() { setupInputTemplate() {
const selectionMenu = Menu.buildFromTemplate([ const selectionMenu = Menu.buildFromTemplate([{ role: 'copy' }, { type: 'separator' }, { role: 'selectall' }])
{ role: 'copy' },
{ type: 'separator' },
{ role: 'selectall' }
])
const inputMenu = Menu.buildFromTemplate([ const inputMenu = Menu.buildFromTemplate([
{ role: 'undo' }, { role: 'undo' },
@ -73,7 +68,13 @@ export default class MenuBuilder {
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }, { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' },
{ label: 'Show All', selector: 'unhideAllApplications:' }, { label: 'Show All', selector: 'unhideAllApplications:' },
{ type: 'separator' }, { type: 'separator' },
{ label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit() } } {
label: 'Quit',
accelerator: 'Command+Q',
click: () => {
app.quit()
}
}
] ]
} }
const subMenuEdit = { const subMenuEdit = {
@ -91,13 +92,27 @@ export default class MenuBuilder {
const subMenuViewDev = { const subMenuViewDev = {
label: 'View', label: 'View',
submenu: [ submenu: [
{ label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload() } }, {
label: 'Reload',
accelerator: 'Command+R',
click: () => {
this.mainWindow.webContents.reload()
}
},
{ {
label: 'Toggle Full Screen', label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F', accelerator: 'Ctrl+Command+F',
click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) } click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
}, },
{ label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools() } } {
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
click: () => {
this.mainWindow.toggleDevTools()
}
}
] ]
} }
const subMenuViewProd = { const subMenuViewProd = {
@ -106,7 +121,9 @@ export default class MenuBuilder {
{ {
label: 'Toggle Full Screen', label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F', accelerator: 'Ctrl+Command+F',
click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) } click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
}
} }
] ]
} }
@ -122,90 +139,123 @@ export default class MenuBuilder {
const subMenuHelp = { const subMenuHelp = {
label: 'Help', label: 'Help',
submenu: [ submenu: [
{ label: 'Learn More', click() { shell.openExternal('https://zap.jackmallers.com/') } }, {
{ label: 'Documentation', click() { shell.openExternal('https://github.com/LN-Zap/zap-desktop') } }, label: 'Learn More',
{ label: 'Community Discussions', click() { shell.openExternal('zaphq.slack.com') } }, click() {
{ label: 'Search Issues', click() { shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues') } } shell.openExternal('https://zap.jackmallers.com/')
}
},
{
label: 'Documentation',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop')
}
},
{
label: 'Community Discussions',
click() {
shell.openExternal('zaphq.slack.com')
}
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues')
}
}
] ]
} }
const subMenuView = process.env.NODE_ENV === 'development' const subMenuView = process.env.NODE_ENV === 'development' ? subMenuViewDev : subMenuViewProd
? subMenuViewDev
: subMenuViewProd
return [ return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]
subMenuAbout,
subMenuEdit,
subMenuView,
subMenuWindow,
subMenuHelp
]
} }
buildDefaultTemplate() { buildDefaultTemplate() {
const templateDefault = [{ const templateDefault = [
label: '&File', {
submenu: [{ label: '&File',
label: '&Open', submenu: [
accelerator: 'Ctrl+O' {
}, { label: '&Open',
label: '&Close', accelerator: 'Ctrl+O'
accelerator: 'Ctrl+W', },
click: () => { {
this.mainWindow.close() label: '&Close',
} accelerator: 'Ctrl+W',
}] click: () => {
}, { this.mainWindow.close()
label: '&View', }
submenu: (process.env.NODE_ENV === 'development') ? [{ }
label: '&Reload', ]
accelerator: 'Ctrl+R', },
click: () => { {
this.mainWindow.webContents.reload() label: '&View',
} submenu:
}, { process.env.NODE_ENV === 'development'
label: 'Toggle &Full Screen', ? [
accelerator: 'F11', {
click: () => { label: '&Reload',
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) accelerator: 'Ctrl+R',
} click: () => {
}, { this.mainWindow.webContents.reload()
label: 'Toggle &Developer Tools', }
accelerator: 'Alt+Ctrl+I', },
click: () => { {
this.mainWindow.toggleDevTools() label: 'Toggle &Full Screen',
} accelerator: 'F11',
}] : [{ click: () => {
label: 'Toggle &Full Screen', this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
accelerator: 'F11', }
click: () => { },
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()) {
} label: 'Toggle &Developer Tools',
}] accelerator: 'Alt+Ctrl+I',
}, { click: () => {
label: 'Help', this.mainWindow.toggleDevTools()
submenu: [{ }
label: 'Learn More', }
click() { ]
shell.openExternal('https://zap.jackmallers.com/') : [
} {
}, { label: 'Toggle &Full Screen',
label: 'Documentation', accelerator: 'F11',
click() { click: () => {
shell.openExternal('https://github.com/LN-Zap/zap-desktop') this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
} }
}, { }
label: 'Community Discussions', ]
click() { },
shell.openExternal('zaphq.slack.com') {
} label: 'Help',
}, { submenu: [
label: 'Search Issues', {
click() { label: 'Learn More',
shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues') click() {
} shell.openExternal('https://zap.jackmallers.com/')
}] }
}] },
{
label: 'Documentation',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop')
}
},
{
label: 'Community Discussions',
click() {
shell.openExternal('zaphq.slack.com')
}
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/LN-Zap/zap-desktop/issues')
}
}
]
}
]
return templateDefault return templateDefault
} }

67
app/reducers/activity.js

@ -96,9 +96,10 @@ const ACTION_HANDLERS = {
[CHANGE_FILTER]: (state, { filter }) => ({ ...state, filter, filterPulldown: false }), [CHANGE_FILTER]: (state, { filter }) => ({ ...state, filter, filterPulldown: false }),
[TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }), [TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }),
[SET_ACTIVITY_MODAL_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ( [SET_ACTIVITY_MODAL_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({
{ ...state, modal: { modalType: state.modal.modalType, modalProps: state.modal.modalProps, showCurrencyFilters } } ...state,
), modal: { modalType: state.modal.modalType, modalProps: state.modal.modalProps, showCurrencyFilters }
}),
[UPDATE_SEARCH_ACTIVE]: (state, { searchActive }) => ({ ...state, searchActive }), [UPDATE_SEARCH_ACTIVE]: (state, { searchActive }) => ({ ...state, searchActive }),
[UPDATE_SEARCH_TEXT]: (state, { searchText }) => ({ ...state, searchText }) [UPDATE_SEARCH_TEXT]: (state, { searchText }) => ({ ...state, searchText })
@ -115,17 +116,21 @@ const paymentsSelector = state => state.payment.payments
const invoicesSelector = state => state.invoice.invoices const invoicesSelector = state => state.invoice.invoices
const transactionsSelector = state => state.transaction.transactions const transactionsSelector = state => state.transaction.transactions
const invoiceExpired = (invoice) => { const invoiceExpired = invoice => {
const expiresAt = (parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)) const expiresAt = parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10)
return expiresAt < (Date.now() / 1000) return expiresAt < Date.now() / 1000
} }
// helper function that returns invoice, payment or transaction timestamp // helper function that returns invoice, payment or transaction timestamp
function returnTimestamp(transaction) { function returnTimestamp(transaction) {
// if on-chain txn // if on-chain txn
if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp } if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) {
return transaction.time_stamp
}
// if invoice that has been paid // if invoice that has been paid
if (transaction.settled) { return transaction.settle_date } if (transaction.settled) {
return transaction.settle_date
}
// if invoice that has not been paid or an LN payment // if invoice that has not been paid or an LN payment
return transaction.creation_date return transaction.creation_date
} }
@ -141,7 +146,9 @@ function groupData(data) {
const date = d.getDate() const date = d.getDate()
const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}` const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}`
if (!arr[title]) { arr[title] = [] } if (!arr[title]) {
arr[title] = []
}
arr[title].push({ el }) arr[title].push({ el })
@ -175,36 +182,34 @@ const allActivity = createSelector(
invoicesSelector, invoicesSelector,
transactionsSelector, transactionsSelector,
(searchText, payments, invoices, transactions) => { (searchText, payments, invoices, transactions) => {
const searchedArr = [...payments, ...invoices, ...transactions].filter((tx) => { const searchedArr = [...payments, ...invoices, ...transactions].filter(tx => {
if ((tx.tx_hash && tx.tx_hash.includes(searchText)) || if (
(tx.payment_hash && tx.payment_hash.includes(searchText)) || (tx.tx_hash && tx.tx_hash.includes(searchText)) ||
(tx.payment_request && tx.payment_request.includes(searchText))) { (tx.payment_hash && tx.payment_hash.includes(searchText)) ||
(tx.payment_request && tx.payment_request.includes(searchText))
) {
return true return true
} }
return false return false
}) })
if (!searchedArr.length) { return [] } if (!searchedArr.length) {
return []
}
return groupAll(searchedArr) return groupAll(searchedArr)
} }
) )
const invoiceActivity = createSelector( const invoiceActivity = createSelector(invoicesSelector, invoices => groupAll(invoices))
invoicesSelector,
invoices => groupAll(invoices)
)
const sentActivity = createSelector( const sentActivity = createSelector(transactionsSelector, paymentsSelector, (transactions, payments) =>
transactionsSelector, groupAll([...transactions.filter(transaction => transaction.amount < 0), ...payments])
paymentsSelector,
(transactions, payments) => groupAll([...transactions.filter(transaction => transaction.amount < 0), ...payments])
) )
const pendingActivity = createSelector( const pendingActivity = createSelector(invoicesSelector, invoices =>
invoicesSelector, groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)))
invoices => groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice)))
) )
const FILTERS = { const FILTERS = {
@ -214,20 +219,12 @@ const FILTERS = {
PENDING_ACTIVITY: pendingActivity PENDING_ACTIVITY: pendingActivity
} }
activitySelectors.currentActivity = createSelector( activitySelectors.currentActivity = createSelector(filterSelector, filter => FILTERS[filter.key])
filterSelector,
filter => FILTERS[filter.key]
)
activitySelectors.nonActiveFilters = createSelector( activitySelectors.nonActiveFilters = createSelector(filtersSelector, filterSelector, (filters, filter) => filters.filter(f => f.key !== filter.key))
filtersSelector,
filterSelector,
(filters, filter) => filters.filter(f => f.key !== filter.key)
)
export { activitySelectors } export { activitySelectors }
// ------------------------------------ // ------------------------------------
// Reducer // Reducer
// ------------------------------------ // ------------------------------------

2
app/reducers/address.js

@ -37,7 +37,7 @@ export function closeWalletModal() {
} }
// Send IPC event for getinfo // Send IPC event for getinfo
export const newAddress = type => async (dispatch) => { export const newAddress = type => async dispatch => {
dispatch(getAddress()) dispatch(getAddress())
ipcRenderer.send('lnd', { msg: 'newaddress', data: { type: addressTypes[type] } }) ipcRenderer.send('lnd', { msg: 'newaddress', data: { type: addressTypes[type] } })
} }

15
app/reducers/balance.js

@ -15,13 +15,13 @@ export function getBalance() {
} }
// Send IPC event for balance // Send IPC event for balance
export const fetchBalance = () => async (dispatch) => { export const fetchBalance = () => async dispatch => {
dispatch(getBalance()) dispatch(getBalance())
ipcRenderer.send('lnd', { msg: 'balance' }) ipcRenderer.send('lnd', { msg: 'balance' })
} }
// Receive IPC event for balance // Receive IPC event for balance
export const receiveBalance = (event, { walletBalance, channelBalance }) => (dispatch) => { export const receiveBalance = (event, { walletBalance, channelBalance }) => dispatch => {
dispatch({ type: RECEIVE_BALANCE, walletBalance, channelBalance }) dispatch({ type: RECEIVE_BALANCE, walletBalance, channelBalance })
} }
@ -30,11 +30,12 @@ export const receiveBalance = (event, { walletBalance, channelBalance }) => (dis
// ------------------------------------ // ------------------------------------
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[GET_BALANCE]: state => ({ ...state, balanceLoading: true }), [GET_BALANCE]: state => ({ ...state, balanceLoading: true }),
[RECEIVE_BALANCE]: (state, { walletBalance, channelBalance }) => ( [RECEIVE_BALANCE]: (state, { walletBalance, channelBalance }) => ({
{ ...state,
...state, balanceLoading: false, walletBalance, channelBalance balanceLoading: false,
} walletBalance,
) channelBalance
})
} }
// ------------------------------------ // ------------------------------------

151
app/reducers/channels.js

@ -54,7 +54,6 @@ export function setChannelForm(form) {
} }
} }
export function setChannel(channel) { export function setChannel(channel) {
return { return {
type: SET_CHANNEL, type: SET_CHANNEL,
@ -167,7 +166,7 @@ export function receiveSuggestedNodes(suggestedNodes) {
} }
} }
export const fetchSuggestedNodes = () => async (dispatch) => { export const fetchSuggestedNodes = () => async dispatch => {
dispatch(getSuggestedNodes()) dispatch(getSuggestedNodes())
const suggestedNodes = await requestSuggestedNodes() const suggestedNodes = await requestSuggestedNodes()
@ -175,7 +174,7 @@ export const fetchSuggestedNodes = () => async (dispatch) => {
} }
// Send IPC event for peers // Send IPC event for peers
export const fetchChannels = () => async (dispatch) => { export const fetchChannels = () => async dispatch => {
dispatch(getChannels()) dispatch(getChannels())
ipcRenderer.send('lnd', { msg: 'channels' }) ipcRenderer.send('lnd', { msg: 'channels' })
} }
@ -184,9 +183,7 @@ export const fetchChannels = () => async (dispatch) => {
export const receiveChannels = (event, { channels, pendingChannels }) => dispatch => dispatch({ type: RECEIVE_CHANNELS, channels, pendingChannels }) export const receiveChannels = (event, { channels, pendingChannels }) => dispatch => dispatch({ type: RECEIVE_CHANNELS, channels, pendingChannels })
// Send IPC event for opening a channel // Send IPC event for opening a channel
export const openChannel = ({ export const openChannel = ({ pubkey, host, local_amt }) => dispatch => {
pubkey, host, local_amt
}) => (dispatch) => {
const localamt = btc.btcToSatoshis(local_amt) const localamt = btc.btcToSatoshis(local_amt)
dispatch(openingChannel()) dispatch(openingChannel())
@ -197,87 +194,88 @@ export const openChannel = ({
// TODO: Decide how to handle streamed updates for channels // TODO: Decide how to handle streamed updates for channels
// Receive IPC event for openChannel // Receive IPC event for openChannel
export const channelSuccessful = () => (dispatch) => { export const channelSuccessful = () => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// Receive IPC event for updated channel // Receive IPC event for updated channel
export const pushchannelupdated = (event, { pubkey }) => (dispatch) => { export const pushchannelupdated = (event, { pubkey }) => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
dispatch(removeLoadingPubkey(pubkey)) dispatch(removeLoadingPubkey(pubkey))
} }
// Receive IPC event for channel end // Receive IPC event for channel end
export const pushchannelend = event => (dispatch) => { // eslint-disable-line no-unused-vars // eslint-disable-next-line no-unused-vars
export const pushchannelend = event => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// Receive IPC event for channel error // Receive IPC event for channel error
export const pushchannelerror = (event, { pubkey, error }) => (dispatch) => { export const pushchannelerror = (event, { pubkey, error }) => dispatch => {
dispatch(openingFailure()) dispatch(openingFailure())
dispatch(setError(error)) dispatch(setError(error))
dispatch(removeLoadingPubkey(pubkey)) dispatch(removeLoadingPubkey(pubkey))
} }
// Receive IPC event for channel status // Receive IPC event for channel status
export const pushchannelstatus = (event, data) => (dispatch) => { // eslint-disable-line no-unused-vars // eslint-disable-next-line no-unused-vars
export const pushchannelstatus = (event, data) => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// Send IPC event for opening a channel // Send IPC event for opening a channel
export const closeChannel = ({ channel_point, chan_id, force }) => (dispatch) => { export const closeChannel = ({ channel_point, chan_id, force }) => dispatch => {
dispatch(closingChannel()) dispatch(closingChannel())
dispatch(addClosingChanId(chan_id)) dispatch(addClosingChanId(chan_id))
const [funding_txid, output_index] = channel_point.split(':') const [funding_txid, output_index] = channel_point.split(':')
ipcRenderer.send( ipcRenderer.send('lnd', {
'lnd', msg: 'closeChannel',
{ data: {
msg: 'closeChannel', channel_point: {
data: { funding_txid,
channel_point: { output_index
funding_txid, },
output_index force
},
force
}
} }
) })
} }
// TODO: Decide how to handle streamed updates for closing channels // TODO: Decide how to handle streamed updates for closing channels
// Receive IPC event for closeChannel // Receive IPC event for closeChannel
export const closeChannelSuccessful = () => (dispatch) => { export const closeChannelSuccessful = () => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// Receive IPC event for updated closing channel // Receive IPC event for updated closing channel
export const pushclosechannelupdated = (event, { chan_id }) => (dispatch) => { export const pushclosechannelupdated = (event, { chan_id }) => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
dispatch(removeClosingChanId(chan_id)) dispatch(removeClosingChanId(chan_id))
dispatch(closeContactModal()) dispatch(closeContactModal())
} }
// Receive IPC event for closing channel end // Receive IPC event for closing channel end
export const pushclosechannelend = () => (dispatch) => { export const pushclosechannelend = () => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// Receive IPC event for closing channel error // Receive IPC event for closing channel error
export const pushclosechannelerror = (event, { error, chan_id }) => (dispatch) => { export const pushclosechannelerror = (event, { error, chan_id }) => dispatch => {
dispatch(setError(error)) dispatch(setError(error))
dispatch(removeClosingChanId(chan_id)) dispatch(removeClosingChanId(chan_id))
} }
// Receive IPC event for closing channel status // Receive IPC event for closing channel status
export const pushclosechannelstatus = () => (dispatch) => { export const pushclosechannelstatus = () => dispatch => {
dispatch(fetchChannels()) dispatch(fetchChannels())
} }
// IPC event for channel graph data // IPC event for channel graph data
export const channelGraphData = (event, data) => (dispatch, getState) => { export const channelGraphData = (event, data) => (dispatch, getState) => {
const { info } = getState() const { info } = getState()
const { channelGraphData: { channel_updates } } = data const {
channelGraphData: { channel_updates }
} = data
// if there are any new channel updates // if there are any new channel updates
if (channel_updates.length) { if (channel_updates.length) {
@ -328,18 +326,17 @@ export function changeFilter(channelFilter) {
// Action Handlers // Action Handlers
// ------------------------------------ // ------------------------------------
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[SET_CHANNEL_FORM]: (state, { form }) => ( [SET_CHANNEL_FORM]: (state, { form }) => ({ ...state, channelForm: Object.assign({}, state.channelForm, form) }),
{ ...state, channelForm: Object.assign({}, state.channelForm, form) }
),
[SET_CHANNEL]: (state, { channel }) => ({ ...state, channel }), [SET_CHANNEL]: (state, { channel }) => ({ ...state, channel }),
[GET_CHANNELS]: state => ({ ...state, channelsLoading: true }), [GET_CHANNELS]: state => ({ ...state, channelsLoading: true }),
[RECEIVE_CHANNELS]: (state, { channels, pendingChannels }) => ( [RECEIVE_CHANNELS]: (state, { channels, pendingChannels }) => ({
{ ...state,
...state, channelsLoading: false, channels, pendingChannels channelsLoading: false,
} channels,
), pendingChannels
}),
[OPENING_CHANNEL]: state => ({ ...state, openingChannel: true }), [OPENING_CHANNEL]: state => ({ ...state, openingChannel: true }),
[OPENING_FAILURE]: state => ({ ...state, openingChannel: false }), [OPENING_FAILURE]: state => ({ ...state, openingChannel: false }),
@ -351,19 +348,19 @@ const ACTION_HANDLERS = {
[SET_VIEW_TYPE]: (state, { viewType }) => ({ ...state, viewType }), [SET_VIEW_TYPE]: (state, { viewType }) => ({ ...state, viewType }),
[TOGGLE_CHANNEL_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }), [TOGGLE_CHANNEL_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }),
[CHANGE_CHANNEL_FILTER]: (state, { channelFilter }) => ( [CHANGE_CHANNEL_FILTER]: (state, { channelFilter }) => ({ ...state, filterPulldown: false, filter: channelFilter }),
{ ...state, filterPulldown: false, filter: channelFilter }
),
[ADD_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: [pubkey, ...state.loadingChannelPubkeys] }), [ADD_LOADING_PUBKEY]: (state, { pubkey }) => ({ ...state, loadingChannelPubkeys: [pubkey, ...state.loadingChannelPubkeys] }),
[REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => ( [REMOVE_LOADING_PUBKEY]: (state, { pubkey }) => ({
{ ...state, loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey) } ...state,
), loadingChannelPubkeys: state.loadingChannelPubkeys.filter(loadingPubkey => loadingPubkey !== pubkey)
}),
[ADD_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: [chanId, ...state.closingChannelIds] }), [ADD_ClOSING_CHAN_ID]: (state, { chanId }) => ({ ...state, closingChannelIds: [chanId, ...state.closingChannelIds] }),
[REMOVE_ClOSING_CHAN_ID]: (state, { chanId }) => ( [REMOVE_ClOSING_CHAN_ID]: (state, { chanId }) => ({
{ ...state, closingChannelIds: state.closingChannelIds.filter(closingChanId => closingChanId !== chanId) } ...state,
), closingChannelIds: state.closingChannelIds.filter(closingChanId => closingChanId !== chanId)
}),
[OPEN_CONTACT_MODAL]: (state, { channel }) => ({ ...state, contactModal: { isOpen: true, channel } }), [OPEN_CONTACT_MODAL]: (state, { channel }) => ({ ...state, contactModal: { isOpen: true, channel } }),
[CLOSE_CONTACT_MODAL]: state => ({ ...state, contactModal: { isOpen: false, channel: null } }), [CLOSE_CONTACT_MODAL]: state => ({ ...state, contactModal: { isOpen: false, channel: null } }),
@ -397,39 +394,24 @@ const channelMatchesQuery = (channel, nodes, searchQuery) => {
return remoteNodePub.includes(query) || remotePubkey.includes(query) || displayName.includes(query) return remoteNodePub.includes(query) || remotePubkey.includes(query) || displayName.includes(query)
} }
channelsSelectors.channelModalOpen = createSelector( channelsSelectors.channelModalOpen = createSelector(channelSelector, channel => !!channel)
channelSelector,
channel => (!!channel)
)
channelsSelectors.activeChannels = createSelector( channelsSelectors.activeChannels = createSelector(channelsSelector, openChannels => openChannels.filter(channel => channel.active))
channelsSelector,
openChannels => openChannels.filter(channel => channel.active)
)
channelsSelectors.activeChannelPubkeys = createSelector( channelsSelectors.activeChannelPubkeys = createSelector(channelsSelector, openChannels =>
channelsSelector, openChannels.filter(channel => channel.active).map(c => c.remote_pubkey)
openChannels => openChannels.filter(channel => channel.active).map(c => c.remote_pubkey)
) )
channelsSelectors.nonActiveChannels = createSelector( channelsSelectors.nonActiveChannels = createSelector(channelsSelector, openChannels => openChannels.filter(channel => !channel.active))
channelsSelector,
openChannels => openChannels.filter(channel => !channel.active)
)
channelsSelectors.nonActiveChannelPubkeys = createSelector( channelsSelectors.nonActiveChannelPubkeys = createSelector(channelsSelector, openChannels =>
channelsSelector, openChannels.filter(channel => !channel.active).map(c => c.remote_pubkey)
openChannels => openChannels.filter(channel => !channel.active).map(c => c.remote_pubkey)
) )
channelsSelectors.pendingOpenChannels = createSelector( channelsSelectors.pendingOpenChannels = createSelector(pendingOpenChannelsSelector, pendingOpenChannels => pendingOpenChannels)
pendingOpenChannelsSelector,
pendingOpenChannels => pendingOpenChannels
)
channelsSelectors.pendingOpenChannelPubkeys = createSelector( channelsSelectors.pendingOpenChannelPubkeys = createSelector(pendingOpenChannelsSelector, pendingOpenChannels =>
pendingOpenChannelsSelector, pendingOpenChannels.map(pendingChannel => pendingChannel.channel.remote_node_pub)
pendingOpenChannels => pendingOpenChannels.map(pendingChannel => pendingChannel.channel.remote_node_pub)
) )
channelsSelectors.closingPendingChannels = createSelector( channelsSelectors.closingPendingChannels = createSelector(
@ -438,26 +420,17 @@ channelsSelectors.closingPendingChannels = createSelector(
(pendingClosedChannels, pendingForcedClosedChannels) => [...pendingClosedChannels, ...pendingForcedClosedChannels] (pendingClosedChannels, pendingForcedClosedChannels) => [...pendingClosedChannels, ...pendingForcedClosedChannels]
) )
channelsSelectors.activeChanIds = createSelector( channelsSelectors.activeChanIds = createSelector(channelsSelector, channels => channels.map(channel => channel.chan_id))
channelsSelector,
channels => channels.map(channel => channel.chan_id)
)
channelsSelectors.nonActiveFilters = createSelector( channelsSelectors.nonActiveFilters = createSelector(filtersSelector, filterSelector, (filters, channelFilter) =>
filtersSelector, filters.filter(f => f.key !== channelFilter.key)
filterSelector,
(filters, channelFilter) => filters.filter(f => f.key !== channelFilter.key)
) )
channelsSelectors.channelNodes = createSelector( channelsSelectors.channelNodes = createSelector(channelsSelector, nodesSelector, (channels, nodes) => {
channelsSelector, const chanPubkeys = channels.map(channel => channel.remote_pubkey)
nodesSelector,
(channels, nodes) => {
const chanPubkeys = channels.map(channel => channel.remote_pubkey)
return filter(nodes, node => chanPubkeys.includes(node.pub_key)) return filter(nodes, node => chanPubkeys.includes(node.pub_key))
} })
)
const allChannels = createSelector( const allChannels = createSelector(
channelsSelectors.activeChannels, channelsSelectors.activeChannels,
@ -523,7 +496,7 @@ export const currentChannels = createSelector(
nodes nodes
) => { ) => {
// Helper function to deliver correct channel array based on filter // Helper function to deliver correct channel array based on filter
const filteredArray = (filterKey) => { const filteredArray = filterKey => {
switch (filterKey) { switch (filterKey) {
case 'ALL_CHANNELS': case 'ALL_CHANNELS':
return allChannelsArr return allChannelsArr

74
app/reducers/contactsform.js

@ -195,9 +195,7 @@ const manualSearchQuerySelector = state => state.contactsform.manualSearchQuery
const contactCapacitySelector = state => state.contactsform.contactCapacity const contactCapacitySelector = state => state.contactsform.contactCapacity
const currencySelector = state => state.ticker.currency const currencySelector = state => state.ticker.currency
const contactable = node => ( const contactable = node => node.addresses.length > 0
node.addresses.length > 0
)
// comparator to sort the contacts list with contactable contacts first // comparator to sort the contacts list with contactable contacts first
const contactableFirst = (a, b) => { const contactableFirst = (a, b) => {
@ -209,70 +207,70 @@ const contactableFirst = (a, b) => {
return 0 return 0
} }
contactFormSelectors.filteredNetworkNodes = createSelector( contactFormSelectors.filteredNetworkNodes = createSelector(networkNodesSelector, searchQuerySelector, (nodes, searchQuery) => {
networkNodesSelector, // If there is no search query default to showing the first 20 nodes from the nodes array
searchQuerySelector, // (performance hit to render the entire thing by default)
(nodes, searchQuery) => { // if (!searchQuery.length) { return nodes.sort(contactableFirst).slice(0, 20) }
// If there is no search query default to showing the first 20 nodes from the nodes array
// (performance hit to render the entire thing by default)
// if (!searchQuery.length) { return nodes.sort(contactableFirst).slice(0, 20) }
// return an empty array if there is no search query // return an empty array if there is no search query
if (!searchQuery.length) { return [] } if (!searchQuery.length) {
return []
}
// if there is an '@' in the search query we are assuming they are using the format pubkey@host // if there is an '@' in the search query we are assuming they are using the format pubkey@host
// we can ignore the '@' and the host and just grab the pubkey for our search // we can ignore the '@' and the host and just grab the pubkey for our search
const query = searchQuery.includes('@') ? searchQuery.split('@')[0] : searchQuery const query = searchQuery.includes('@') ? searchQuery.split('@')[0] : searchQuery
// list of the nodes // list of the nodes
const list = filter(nodes, node => node.alias.includes(query) || node.pub_key.includes(query)).sort(contactableFirst) const list = filter(nodes, node => node.alias.includes(query) || node.pub_key.includes(query)).sort(contactableFirst)
// if we don't limit the nodes returned then we take a huge performance hit // if we don't limit the nodes returned then we take a huge performance hit
// rendering thousands of nodes potentially, so we just render 20 for the time being // rendering thousands of nodes potentially, so we just render 20 for the time being
return list.slice(0, 20) return list.slice(0, 20)
} })
)
contactFormSelectors.showManualForm = createSelector( contactFormSelectors.showManualForm = createSelector(
searchQuerySelector, searchQuerySelector,
contactFormSelectors.filteredNetworkNodes, contactFormSelectors.filteredNetworkNodes,
(searchQuery, filteredNetworkNodes) => { (searchQuery, filteredNetworkNodes) => {
if (!searchQuery.length) { return false } if (!searchQuery.length) {
return false
}
const connectableNodes = filteredNetworkNodes.filter(node => node.addresses.length > 0) const connectableNodes = filteredNetworkNodes.filter(node => node.addresses.length > 0)
if (!filteredNetworkNodes.length || !connectableNodes.length) { return true } if (!filteredNetworkNodes.length || !connectableNodes.length) {
return true
}
return false return false
} }
) )
contactFormSelectors.manualFormIsValid = createSelector( contactFormSelectors.manualFormIsValid = createSelector(manualSearchQuerySelector, input => {
manualSearchQuerySelector, const errors = {}
(input) => { if (!input.length || !input.includes('@')) {
const errors = {} errors.manualInput = 'Invalid format'
if (!input.length || !input.includes('@')) {
errors.manualInput = 'Invalid format'
}
return {
errors,
isValid: isEmpty(errors)
}
} }
) return {
errors,
isValid: isEmpty(errors)
}
})
contactFormSelectors.contactFormUsdAmount = createSelector( contactFormSelectors.contactFormUsdAmount = createSelector(
contactCapacitySelector, contactCapacitySelector,
currencySelector, currencySelector,
tickerSelectors.currentTicker, tickerSelectors.currentTicker,
(amount, currency, ticker) => { (amount, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false } if (!ticker || !ticker.price_usd) {
return false
}
return btc.convert(currency, 'usd', amount, ticker.price_usd) return btc.convert(currency, 'usd', amount, ticker.price_usd)
} }
) )
export { contactFormSelectors } export { contactFormSelectors }
// ------------------------------------ // ------------------------------------

2
app/reducers/error.js

@ -32,7 +32,7 @@ export function clearError() {
// ------------------------------------ // ------------------------------------
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[SET_ERROR]: (state, { error }) => ({ ...state, error }), [SET_ERROR]: (state, { error }) => ({ ...state, error }),
[CLEAR_ERROR]: () => (initialState) [CLEAR_ERROR]: () => initialState
} }
// ------------------------------------ // ------------------------------------

6
app/reducers/info.js

@ -26,13 +26,13 @@ export function setWalletCurrencyFilters(showWalletCurrencyFilters) {
} }
// Send IPC event for getinfo // Send IPC event for getinfo
export const fetchInfo = () => async (dispatch) => { export const fetchInfo = () => async dispatch => {
dispatch(getInfo()) dispatch(getInfo())
ipcRenderer.send('lnd', { msg: 'info' }) ipcRenderer.send('lnd', { msg: 'info' })
} }
// Receive IPC event for info // Receive IPC event for info
export const receiveInfo = (event, data) => (dispatch) => { export const receiveInfo = (event, data) => dispatch => {
dispatch({ type: RECEIVE_INFO, data }) dispatch({ type: RECEIVE_INFO, data })
} }
@ -61,7 +61,7 @@ const ACTION_HANDLERS = {
[RECEIVE_INFO]: (state, { data }) => ({ [RECEIVE_INFO]: (state, { data }) => ({
...state, ...state,
infoLoading: false, infoLoading: false,
network: (data.testnet ? networks.testnet : networks.mainnet), network: data.testnet ? networks.testnet : networks.mainnet,
data data
}), }),
[SET_WALLET_CURRENCY_FILTERS]: (state, { showWalletCurrencyFilters }) => ({ ...state, showWalletCurrencyFilters }) [SET_WALLET_CURRENCY_FILTERS]: (state, { showWalletCurrencyFilters }) => ({ ...state, showWalletCurrencyFilters })

43
app/reducers/invoice.js

@ -75,19 +75,19 @@ export function sendInvoice() {
} }
// Send IPC event for a specific invoice // Send IPC event for a specific invoice
export const fetchInvoice = payreq => (dispatch) => { export const fetchInvoice = payreq => dispatch => {
dispatch(getInvoice()) dispatch(getInvoice())
ipcRenderer.send('lnd', { msg: 'invoice', data: { payreq } }) ipcRenderer.send('lnd', { msg: 'invoice', data: { payreq } })
} }
// Receive IPC event for form invoice // Receive IPC event for form invoice
export const receiveFormInvoice = (event, invoice) => (dispatch) => { export const receiveFormInvoice = (event, invoice) => dispatch => {
dispatch(setPayInvoice(invoice)) dispatch(setPayInvoice(invoice))
dispatch({ type: RECEIVE_FORM_INVOICE }) dispatch({ type: RECEIVE_FORM_INVOICE })
} }
// Send IPC event for invoices // Send IPC event for invoices
export const fetchInvoices = () => (dispatch) => { export const fetchInvoices = () => dispatch => {
dispatch(getInvoices()) dispatch(getInvoices())
ipcRenderer.send('lnd', { msg: 'invoices' }) ipcRenderer.send('lnd', { msg: 'invoices' })
} }
@ -96,7 +96,7 @@ export const fetchInvoices = () => (dispatch) => {
export const receiveInvoices = (event, { invoices }) => dispatch => dispatch({ type: RECEIVE_INVOICES, invoices }) export const receiveInvoices = (event, { invoices }) => dispatch => dispatch({ type: RECEIVE_INVOICES, invoices })
// Send IPC event for creating an invoice // Send IPC event for creating an invoice
export const createInvoice = (amount, memo, currency) => (dispatch) => { export const createInvoice = (amount, memo, currency) => dispatch => {
// backend needs value in satoshis no matter what currency we are using // backend needs value in satoshis no matter what currency we are using
const value = btc.convert(currency, 'sats', amount) const value = btc.convert(currency, 'sats', amount)
@ -105,7 +105,7 @@ export const createInvoice = (amount, memo, currency) => (dispatch) => {
} }
// Receive IPC event for newly created invoice // Receive IPC event for newly created invoice
export const createdInvoice = (event, invoice) => (dispatch) => { export const createdInvoice = (event, invoice) => dispatch => {
// Close the form modal once the payment was succesful // Close the form modal once the payment was succesful
dispatch(setFormType(null)) dispatch(setFormType(null))
@ -122,20 +122,20 @@ export const createdInvoice = (event, invoice) => (dispatch) => {
dispatch(showActivityModal('INVOICE', { invoice })) dispatch(showActivityModal('INVOICE', { invoice }))
} }
export const invoiceFailed = (event, { error }) => (dispatch) => { export const invoiceFailed = (event, { error }) => dispatch => {
dispatch({ type: INVOICE_FAILED }) dispatch({ type: INVOICE_FAILED })
dispatch(setError(error)) dispatch(setError(error))
} }
// Listen for invoice updates pushed from backend from subscribeToInvoices // Listen for invoice updates pushed from backend from subscribeToInvoices
export const invoiceUpdate = (event, { invoice }) => (dispatch) => { export const invoiceUpdate = (event, { invoice }) => dispatch => {
dispatch({ type: UPDATE_INVOICE, invoice }) dispatch({ type: UPDATE_INVOICE, invoice })
// Fetch new balance // Fetch new balance
dispatch(fetchBalance()) dispatch(fetchBalance())
// HTML 5 desktop notification for the invoice update // HTML 5 desktop notification for the invoice update
const notifTitle = 'You\'ve been Zapped' const notifTitle = "You've been Zapped"
const notifBody = 'Congrats, someone just paid an invoice of yours' const notifBody = 'Congrats, someone just paid an invoice of yours'
showNotification(notifTitle, notifBody) showNotification(notifTitle, notifBody)
@ -156,14 +156,14 @@ const ACTION_HANDLERS = {
[RECEIVE_INVOICES]: (state, { invoices }) => ({ ...state, invoiceLoading: false, invoices }), [RECEIVE_INVOICES]: (state, { invoices }) => ({ ...state, invoiceLoading: false, invoices }),
[SEND_INVOICE]: state => ({ ...state, invoiceLoading: true }), [SEND_INVOICE]: state => ({ ...state, invoiceLoading: true }),
[INVOICE_SUCCESSFUL]: (state, { invoice }) => ( [INVOICE_SUCCESSFUL]: (state, { invoice }) => ({ ...state, invoiceLoading: false, invoices: [invoice, ...state.invoices] }),
{ ...state, invoiceLoading: false, invoices: [invoice, ...state.invoices] }
),
[INVOICE_FAILED]: state => ({ ...state, invoiceLoading: false, data: null }), [INVOICE_FAILED]: state => ({ ...state, invoiceLoading: false, data: null }),
[UPDATE_INVOICE]: (state, action) => { [UPDATE_INVOICE]: (state, action) => {
const updatedInvoices = state.invoices.map((invoice) => { const updatedInvoices = state.invoices.map(invoice => {
if (invoice.r_hash.toString('hex') !== action.invoice.r_hash.toString('hex')) { return invoice } if (invoice.r_hash.toString('hex') !== action.invoice.r_hash.toString('hex')) {
return invoice
}
return { return {
...invoice, ...invoice,
@ -180,21 +180,14 @@ const invoiceSelector = state => state.invoice.invoice
const invoicesSelector = state => state.invoice.invoices const invoicesSelector = state => state.invoice.invoices
const invoicesSearchTextSelector = state => state.invoice.invoicesSearchText const invoicesSearchTextSelector = state => state.invoice.invoicesSearchText
invoiceSelectors.invoiceModalOpen = createSelector( invoiceSelectors.invoiceModalOpen = createSelector(invoiceSelector, invoice => !!invoice)
invoiceSelector,
invoice => (!!invoice)
)
invoiceSelectors.invoices = createSelector( invoiceSelectors.invoices = createSelector(invoicesSelector, invoicesSearchTextSelector, (invoices, invoicesSearchText) =>
invoicesSelector, invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
invoicesSearchTextSelector,
(invoices, invoicesSearchText) => invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
) )
invoiceSelectors.invoices = createSelector( invoiceSelectors.invoices = createSelector(invoicesSelector, invoicesSearchTextSelector, (invoices, invoicesSearchText) =>
invoicesSelector, invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
invoicesSearchTextSelector,
(invoices, invoicesSearchText) => invoices.filter(invoice => invoice.memo.includes(invoicesSearchText))
) )
export { invoiceSelectors } export { invoiceSelectors }

10
app/reducers/ipc.js

@ -6,19 +6,16 @@ import { receiveCryptocurrency } from './ticker'
import { receivePeers, connectSuccess, disconnectSuccess, connectFailure } from './peers' import { receivePeers, connectSuccess, disconnectSuccess, connectFailure } from './peers'
import { import {
receiveChannels, receiveChannels,
channelSuccessful, channelSuccessful,
pushchannelupdated, pushchannelupdated,
pushchannelend, pushchannelend,
pushchannelerror, pushchannelerror,
pushchannelstatus, pushchannelstatus,
closeChannelSuccessful, closeChannelSuccessful,
pushclosechannelupdated, pushclosechannelupdated,
pushclosechannelend, pushclosechannelend,
pushclosechannelerror, pushclosechannelerror,
pushclosechannelstatus, pushclosechannelstatus,
channelGraphData, channelGraphData,
channelGraphStatus channelGraphStatus
} from './channels' } from './channels'
@ -26,12 +23,7 @@ import { lightningPaymentUri } from './payform'
import { receivePayments, paymentSuccessful, paymentFailed } from './payment' import { receivePayments, paymentSuccessful, paymentFailed } from './payment'
import { receiveInvoices, createdInvoice, receiveFormInvoice, invoiceUpdate, invoiceFailed } from './invoice' import { receiveInvoices, createdInvoice, receiveFormInvoice, invoiceUpdate, invoiceFailed } from './invoice'
import { receiveBalance } from './balance' import { receiveBalance } from './balance'
import { import { receiveTransactions, transactionSuccessful, transactionError, newTransaction } from './transaction'
receiveTransactions,
transactionSuccessful,
transactionError,
newTransaction
} from './transaction'
import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network' import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network'

25
app/reducers/lnd.js

@ -26,7 +26,7 @@ export const GRPC_CONNECTED = 'GRPC_CONNECTED'
export const lndSyncing = () => dispatch => dispatch({ type: START_SYNCING }) export const lndSyncing = () => dispatch => dispatch({ type: START_SYNCING })
// Receive IPC event for LND stoping sync // Receive IPC event for LND stoping sync
export const lndSynced = () => (dispatch) => { export const lndSynced = () => dispatch => {
// Fetch data now that we know LND is synced // Fetch data now that we know LND is synced
dispatch(fetchTicker()) dispatch(fetchTicker())
dispatch(fetchBalance()) dispatch(fetchBalance())
@ -36,7 +36,7 @@ export const lndSynced = () => (dispatch) => {
// HTML 5 desktop notification for the new transaction // HTML 5 desktop notification for the new transaction
const notifTitle = 'Lightning Node Synced' const notifTitle = 'Lightning Node Synced'
const notifBody = 'Visa who? You\'re your own payment processor now!' const notifBody = "Visa who? You're your own payment processor now!"
showNotification(notifTitle, notifBody) showNotification(notifTitle, notifBody)
} }
@ -46,7 +46,7 @@ export const grpcDisconnected = () => dispatch => dispatch({ type: GRPC_DISCONNE
export const grpcConnected = () => dispatch => dispatch({ type: GRPC_CONNECTED }) export const grpcConnected = () => dispatch => dispatch({ type: GRPC_CONNECTED })
// Receive IPC event for LND streaming a line // Receive IPC event for LND streaming a line
export const lndStdout = (event, line) => (dispatch) => { export const lndStdout = (event, line) => dispatch => {
let height let height
let trimmed let trimmed
@ -63,7 +63,6 @@ export const lndStdout = (event, line) => (dispatch) => {
dispatch({ type: RECEIVE_LINE, lndBlockHeight: height }) dispatch({ type: RECEIVE_LINE, lndBlockHeight: height })
} }
export function getBlockHeight() { export function getBlockHeight() {
return { return {
type: GET_BLOCK_HEIGHT type: GET_BLOCK_HEIGHT
@ -78,7 +77,7 @@ export function receiveBlockHeight(blockHeight) {
} }
// Fetch current block height // Fetch current block height
export const fetchBlockHeight = () => async (dispatch) => { export const fetchBlockHeight = () => async dispatch => {
dispatch(getBlockHeight()) dispatch(getBlockHeight())
const blockData = await requestBlockHeight() const blockData = await requestBlockHeight()
dispatch(receiveBlockHeight(blockData.blocks[0].height)) dispatch(receiveBlockHeight(blockData.blocks[0].height))
@ -119,17 +118,15 @@ const lndSelectors = {}
const blockHeightSelector = state => state.lnd.blockHeight const blockHeightSelector = state => state.lnd.blockHeight
const lndBlockHeightSelector = state => state.lnd.lndBlockHeight const lndBlockHeightSelector = state => state.lnd.lndBlockHeight
lndSelectors.syncPercentage = createSelector( lndSelectors.syncPercentage = createSelector(blockHeightSelector, lndBlockHeightSelector, (blockHeight, lndBlockHeight) => {
blockHeightSelector, const percentage = Math.floor((lndBlockHeight / blockHeight) * 100)
lndBlockHeightSelector,
(blockHeight, lndBlockHeight) => {
const percentage = Math.floor((lndBlockHeight / blockHeight) * 100)
if (percentage === Infinity) { return '' }
return percentage if (percentage === Infinity) {
return ''
} }
)
return percentage
})
export { lndSelectors } export { lndSelectors }

84
app/reducers/network.js

@ -128,16 +128,15 @@ export function clearSelectedChannels() {
} }
// Send IPC event for describeNetwork // Send IPC event for describeNetwork
export const fetchDescribeNetwork = () => (dispatch) => { export const fetchDescribeNetwork = () => dispatch => {
dispatch(getDescribeNetwork()) dispatch(getDescribeNetwork())
ipcRenderer.send('lnd', { msg: 'describeNetwork' }) ipcRenderer.send('lnd', { msg: 'describeNetwork' })
} }
// Receive IPC event for describeNetwork // Receive IPC event for describeNetwork
export const receiveDescribeNetwork = (event, { nodes, edges }) => dispatch => export const receiveDescribeNetwork = (event, { nodes, edges }) => dispatch => dispatch({ type: RECEIVE_DESCRIBE_NETWORK, nodes, edges })
dispatch({ type: RECEIVE_DESCRIBE_NETWORK, nodes, edges })
export const queryRoutes = (pubkey, amount) => (dispatch) => { export const queryRoutes = (pubkey, amount) => dispatch => {
dispatch(getQueryRoutes(pubkey)) dispatch(getQueryRoutes(pubkey))
ipcRenderer.send('lnd', { msg: 'queryRoutes', data: { pubkey, amount } }) ipcRenderer.send('lnd', { msg: 'queryRoutes', data: { pubkey, amount } })
} }
@ -145,13 +144,12 @@ export const queryRoutes = (pubkey, amount) => (dispatch) => {
export const receiveQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_QUERY_ROUTES, routes }) export const receiveQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_QUERY_ROUTES, routes })
// take a payreq and query routes for it // take a payreq and query routes for it
export const fetchInvoiceAndQueryRoutes = payreq => (dispatch) => { export const fetchInvoiceAndQueryRoutes = payreq => dispatch => {
dispatch(getInvoiceAndQueryRoutes()) dispatch(getInvoiceAndQueryRoutes())
ipcRenderer.send('lnd', { msg: 'getInvoiceAndQueryRoutes', data: { payreq } }) ipcRenderer.send('lnd', { msg: 'getInvoiceAndQueryRoutes', data: { payreq } })
} }
export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch => export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch => dispatch({ type: RECEIVE_INFO_AND_QUERY_ROUTES, routes })
dispatch({ type: RECEIVE_INFO_AND_QUERY_ROUTES, routes })
// ------------------------------------ // ------------------------------------
// Action Handlers // Action Handlers
@ -159,17 +157,18 @@ export const receiveInvoiceAndQueryRoutes = (event, { routes }) => dispatch =>
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[GET_DESCRIBE_NETWORK]: state => ({ ...state, networkLoading: true }), [GET_DESCRIBE_NETWORK]: state => ({ ...state, networkLoading: true }),
[RECEIVE_DESCRIBE_NETWORK]: (state, { nodes, edges }) => ({ [RECEIVE_DESCRIBE_NETWORK]: (state, { nodes, edges }) => ({
...state, networkLoading: false, nodes, edges ...state,
networkLoading: false,
nodes,
edges
}), }),
[GET_QUERY_ROUTES]: (state, { pubkey }) => ({ ...state, networkLoading: true, selectedNode: { pubkey, routes: [], currentRoute: {} } }), [GET_QUERY_ROUTES]: (state, { pubkey }) => ({ ...state, networkLoading: true, selectedNode: { pubkey, routes: [], currentRoute: {} } }),
[RECEIVE_QUERY_ROUTES]: (state, { routes }) => ( [RECEIVE_QUERY_ROUTES]: (state, { routes }) => ({
{ ...state,
...state, networkLoading: false,
networkLoading: false, selectedNode: { pubkey: state.selectedNode.pubkey, routes, currentRoute: routes[0] }
selectedNode: { pubkey: state.selectedNode.pubkey, routes, currentRoute: routes[0] } }),
}
),
[SET_CURRENT_ROUTE]: (state, { route }) => ({ ...state, currentRoute: route }), [SET_CURRENT_ROUTE]: (state, { route }) => ({ ...state, currentRoute: route }),
@ -198,7 +197,8 @@ const ACTION_HANDLERS = {
} }
return { return {
...state, selectedPeers ...state,
selectedPeers
} }
}, },
[CLEAR_SELECTED_PEERS]: state => ({ ...state, selectedPeers: [] }), [CLEAR_SELECTED_PEERS]: state => ({ ...state, selectedPeers: [] }),
@ -215,7 +215,8 @@ const ACTION_HANDLERS = {
} }
return { return {
...state, selectedChannels ...state,
selectedChannels
} }
}, },
[CLEAR_SELECTED_CHANNELS]: state => ({ ...state, selectedChannels: [] }) [CLEAR_SELECTED_CHANNELS]: state => ({ ...state, selectedChannels: [] })
@ -239,38 +240,30 @@ const currentRouteSelector = state => state.network.currentRoute
// } // }
// ) // )
networkSelectors.selectedPeerPubkeys = createSelector( networkSelectors.selectedPeerPubkeys = createSelector(selectedPeersSelector, peers => peers.map(peer => peer.pub_key))
selectedPeersSelector,
peers => peers.map(peer => peer.pub_key) networkSelectors.selectedChannelIds = createSelector(selectedChannelsSelector, channels => channels.map(channel => channel.chan_id))
)
networkSelectors.payReqIsLn = createSelector(payReqSelector, input => {
networkSelectors.selectedChannelIds = createSelector( if (!input.startsWith('ln')) {
selectedChannelsSelector, return false
channels => channels.map(channel => channel.chan_id)
)
networkSelectors.payReqIsLn = createSelector(
payReqSelector,
(input) => {
if (!input.startsWith('ln')) { return false }
try {
bech32.decode(input)
return true
} catch (e) {
return false
}
} }
)
networkSelectors.currentRouteChanIds = createSelector( try {
currentRouteSelector, bech32.decode(input)
(route) => { return true
if (!route.hops || !route.hops.length) { return [] } } catch (e) {
return false
}
})
return route.hops.map(hop => hop.chan_id) networkSelectors.currentRouteChanIds = createSelector(currentRouteSelector, route => {
if (!route.hops || !route.hops.length) {
return []
} }
)
return route.hops.map(hop => hop.chan_id)
})
export { networkSelectors } export { networkSelectors }
@ -295,7 +288,6 @@ const initialState = {
selectedChannels: [] selectedChannels: []
} }
// ------------------------------------ // ------------------------------------
// Reducer // Reducer
// ------------------------------------ // ------------------------------------

18
app/reducers/onboarding.js

@ -155,23 +155,23 @@ export function startLnd(options) {
} }
} }
export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => (dispatch) => { export const submitNewWallet = (wallet_password, cipher_seed_mnemonic, aezeed_passphrase) => dispatch => {
// once the user submits the data needed to start LND we will alert the app that it should start LND // once the user submits the data needed to start LND we will alert the app that it should start LND
ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } }) ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } })
dispatch({ type: CREATING_NEW_WALLET }) dispatch({ type: CREATING_NEW_WALLET })
} }
export const startOnboarding = () => (dispatch) => { export const startOnboarding = () => dispatch => {
dispatch({ type: ONBOARDING_STARTED }) dispatch({ type: ONBOARDING_STARTED })
} }
// Listener from after the LND walletUnlocker has started // Listener from after the LND walletUnlocker has started
export const walletUnlockerStarted = () => (dispatch) => { export const walletUnlockerStarted = () => dispatch => {
dispatch({ type: LND_STARTED }) dispatch({ type: LND_STARTED })
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' }) ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
} }
export const createWallet = () => (dispatch) => { export const createWallet = () => dispatch => {
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' }) ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: CHANGE_STEP, step: 4 }) dispatch({ type: CHANGE_STEP, step: 4 })
} }
@ -179,31 +179,31 @@ export const createWallet = () => (dispatch) => {
export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED }) export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED })
// Listener for when LND creates and sends us a generated seed // Listener for when LND creates and sends us a generated seed
export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => { export const receiveSeed = (event, { cipher_seed_mnemonic }) => dispatch => {
dispatch({ type: CHANGE_STEP, step: 4 }) dispatch({ type: CHANGE_STEP, step: 4 })
// there was no seed and we just generated a new one, send user to the login component // there was no seed and we just generated a new one, send user to the login component
dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic }) dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic })
} }
// Listener for when LND throws an error on seed creation // Listener for when LND throws an error on seed creation
export const receiveSeedError = () => (dispatch) => { export const receiveSeedError = () => dispatch => {
dispatch({ type: SET_HAS_SEED, hasSeed: true }) dispatch({ type: SET_HAS_SEED, hasSeed: true })
// there is already a seed, send user to the login component // there is already a seed, send user to the login component
dispatch({ type: CHANGE_STEP, step: 3 }) dispatch({ type: CHANGE_STEP, step: 3 })
} }
// Unlock an existing wallet with a wallet password // Unlock an existing wallet with a wallet password
export const unlockWallet = wallet_password => (dispatch) => { export const unlockWallet = wallet_password => dispatch => {
ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } }) ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } })
dispatch({ type: UNLOCKING_WALLET }) dispatch({ type: UNLOCKING_WALLET })
} }
export const walletUnlocked = () => (dispatch) => { export const walletUnlocked = () => dispatch => {
dispatch({ type: WALLET_UNLOCKED }) dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED }) dispatch({ type: ONBOARDING_FINISHED })
} }
export const unlockWalletError = () => (dispatch) => { export const unlockWalletError = () => dispatch => {
dispatch({ type: SET_UNLOCK_WALLET_ERROR }) dispatch({ type: SET_UNLOCK_WALLET_ERROR })
} }

115
app/reducers/payform.js

@ -79,7 +79,7 @@ export function updatePayErrors(errorsObject) {
} }
} }
export const lightningPaymentUri = (event, { payreq }) => (dispatch) => { export const lightningPaymentUri = (event, { payreq }) => dispatch => {
// Open pay form // Open pay form
dispatch(setFormType('PAY_FORM')) dispatch(setFormType('PAY_FORM'))
// Set payreq // Set payreq
@ -103,7 +103,7 @@ const ACTION_HANDLERS = {
[UPDATE_PAY_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }), [UPDATE_PAY_ERRORS]: (state, { errorsObject }) => ({ ...state, showErrors: Object.assign(state.showErrors, errorsObject) }),
[RESET_FORM]: () => (initialState) [RESET_FORM]: () => initialState
} }
// ------------------------------------ // ------------------------------------
@ -123,32 +123,27 @@ const sendingPaymentSelector = state => state.payment.sendingPayment
// ticker // ticker
const currencySelector = state => state.ticker.currency const currencySelector = state => state.ticker.currency
payFormSelectors.isOnchain = createSelector( payFormSelectors.isOnchain = createSelector(payInputSelector, infoSelectors.networkSelector, (input, network) => {
payInputSelector, try {
infoSelectors.networkSelector, bitcoin.address.toOutputScript(input, network.bitcoinJsNetwork)
(input, network) => { return true
try { } catch (e) {
bitcoin.address.toOutputScript(input, network.bitcoinJsNetwork) return false
return true
} catch (e) {
return false
}
} }
) })
payFormSelectors.isLn = createSelector( payFormSelectors.isLn = createSelector(payInputSelector, input => {
payInputSelector, if (!input.startsWith('ln')) {
(input) => { return false
if (!input.startsWith('ln')) { return false } }
try { try {
bech32.decode(input) bech32.decode(input)
return true return true
} catch (e) { } catch (e) {
return false return false
}
} }
) })
payFormSelectors.currentAmount = createSelector( payFormSelectors.currentAmount = createSelector(
payFormSelectors.isLn, payFormSelectors.isLn,
@ -159,9 +154,9 @@ payFormSelectors.currentAmount = createSelector(
if (isLn) { if (isLn) {
switch (currency) { switch (currency) {
case 'btc': case 'btc':
return btc.satoshisToBtc((invoice.num_satoshis || 0)) return btc.satoshisToBtc(invoice.num_satoshis || 0)
case 'bits': case 'bits':
return btc.satoshisToBits((invoice.num_satoshis || 0)) return btc.satoshisToBits(invoice.num_satoshis || 0)
case 'sats': case 'sats':
return invoice.num_satoshis return invoice.num_satoshis
default: default:
@ -180,31 +175,30 @@ payFormSelectors.usdAmount = createSelector(
currencySelector, currencySelector,
tickerSelectors.currentTicker, tickerSelectors.currentTicker,
(isLn, amount, invoice, currency, ticker) => { (isLn, amount, invoice, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false } if (!ticker || !ticker.price_usd) {
return false
}
if (isLn) { if (isLn) {
return btc.satoshisToUsd((invoice.num_satoshis || 0), ticker.price_usd) return btc.satoshisToUsd(invoice.num_satoshis || 0, ticker.price_usd)
} }
return btc.convert(currency, 'usd', amount, ticker.price_usd) return btc.convert(currency, 'usd', amount, ticker.price_usd)
} }
) )
payFormSelectors.payInputMin = createSelector( payFormSelectors.payInputMin = createSelector(currencySelector, currency => {
currencySelector, switch (currency) {
(currency) => { case 'btc':
switch (currency) { return '0.00000001'
case 'btc': case 'bits':
return '0.00000001' return '0.01'
case 'bits': case 'sats':
return '0.01' return '1'
case 'sats': default:
return '1' return '0'
default:
return '0'
}
} }
) })
payFormSelectors.inputCaption = createSelector( payFormSelectors.inputCaption = createSelector(
payFormSelectors.isOnchain, payFormSelectors.isOnchain,
@ -212,7 +206,9 @@ payFormSelectors.inputCaption = createSelector(
payFormSelectors.currentAmount, payFormSelectors.currentAmount,
currencySelector, currencySelector,
(isOnchain, isLn, amount, currency) => { (isOnchain, isLn, amount, currency) => {
if (!isOnchain && !isLn) { return '' } if (!isOnchain && !isLn) {
return ''
}
if (isOnchain) { if (isOnchain) {
return `You're about to send ${amount} ${currency.toUpperCase()} on-chain which should take around 10 minutes` return `You're about to send ${amount} ${currency.toUpperCase()} on-chain which should take around 10 minutes`
@ -232,29 +228,24 @@ payFormSelectors.showPayLoadingScreen = createSelector(
(sendingTransaction, sendingPayment) => sendingTransaction || sendingPayment (sendingTransaction, sendingPayment) => sendingTransaction || sendingPayment
) )
payFormSelectors.payFormIsValid = createSelector( payFormSelectors.payFormIsValid = createSelector(payFormSelectors.isOnchain, payFormSelectors.isLn, payAmountSelector, (isOnchain, isLn, amount) => {
payFormSelectors.isOnchain, const errors = {}
payFormSelectors.isLn,
payAmountSelector,
(isOnchain, isLn, amount) => {
const errors = {}
if (!isLn && amount <= 0) { if (!isLn && amount <= 0) {
errors.amount = 'Amount must be more than 0' errors.amount = 'Amount must be more than 0'
} }
if (!isOnchain && !isLn) { if (!isOnchain && !isLn) {
errors.payInput = 'Must be a valid BTC address or Lightning Network request' errors.payInput = 'Must be a valid BTC address or Lightning Network request'
} }
return { return {
errors, errors,
amountIsValid: isEmpty(errors.amount), amountIsValid: isEmpty(errors.amount),
payInputIsValid: isEmpty(errors.payInput), payInputIsValid: isEmpty(errors.payInput),
isValid: isEmpty(errors) isValid: isEmpty(errors)
}
} }
) })
export { payFormSelectors } export { payFormSelectors }

14
app/reducers/payment.js

@ -63,7 +63,7 @@ export function hideSuccessScreen() {
} }
// Send IPC event for payments // Send IPC event for payments
export const fetchPayments = () => (dispatch) => { export const fetchPayments = () => dispatch => {
dispatch(getPayments()) dispatch(getPayments())
ipcRenderer.send('lnd', { msg: 'payments' }) ipcRenderer.send('lnd', { msg: 'payments' })
} }
@ -73,7 +73,7 @@ export const receivePayments = (event, { payments }) => dispatch => dispatch({ t
// Receive IPC event for successful payment // Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch // TODO: Add payment to state, not a total re-fetch
export const paymentSuccessful = () => (dispatch) => { export const paymentSuccessful = () => dispatch => {
// Dispatch successful payment to stop loading screen // Dispatch successful payment to stop loading screen
dispatch(paymentSuccessfull()) dispatch(paymentSuccessfull())
@ -90,12 +90,12 @@ export const paymentSuccessful = () => (dispatch) => {
dispatch(fetchBalance()) dispatch(fetchBalance())
} }
export const paymentFailed = (event, { error }) => (dispatch) => { export const paymentFailed = (event, { error }) => dispatch => {
dispatch({ type: PAYMENT_FAILED }) dispatch({ type: PAYMENT_FAILED })
dispatch(setError(error)) dispatch(setError(error))
} }
export const payInvoice = paymentRequest => (dispatch) => { export const payInvoice = paymentRequest => dispatch => {
dispatch(sendPayment()) dispatch(sendPayment())
ipcRenderer.send('lnd', { msg: 'sendPayment', data: { paymentRequest } }) ipcRenderer.send('lnd', { msg: 'sendPayment', data: { paymentRequest } })
@ -112,7 +112,6 @@ export const payInvoice = paymentRequest => (dispatch) => {
// }, 10000) // }, 10000)
} }
// ------------------------------------ // ------------------------------------
// Action Handlers // Action Handlers
// ------------------------------------ // ------------------------------------
@ -132,10 +131,7 @@ const ACTION_HANDLERS = {
const paymentSelectors = {} const paymentSelectors = {}
const modalPaymentSelector = state => state.payment.payment const modalPaymentSelector = state => state.payment.payment
paymentSelectors.paymentModalOpen = createSelector( paymentSelectors.paymentModalOpen = createSelector(modalPaymentSelector, payment => !!payment)
modalPaymentSelector,
payment => (!!payment)
)
export { paymentSelectors } export { paymentSelectors }

41
app/reducers/peers.js

@ -70,7 +70,7 @@ export function updateSearchQuery(searchQuery) {
} }
// Send IPC event for peers // Send IPC event for peers
export const fetchPeers = () => async (dispatch) => { export const fetchPeers = () => async dispatch => {
dispatch(getPeers()) dispatch(getPeers())
ipcRenderer.send('lnd', { msg: 'peers' }) ipcRenderer.send('lnd', { msg: 'peers' })
} }
@ -79,7 +79,7 @@ export const fetchPeers = () => async (dispatch) => {
export const receivePeers = (event, { peers }) => dispatch => dispatch({ type: RECEIVE_PEERS, peers }) export const receivePeers = (event, { peers }) => dispatch => dispatch({ type: RECEIVE_PEERS, peers })
// Send IPC event for connecting to a peer // Send IPC event for connecting to a peer
export const connectRequest = ({ pubkey, host }) => (dispatch) => { export const connectRequest = ({ pubkey, host }) => dispatch => {
dispatch(connectPeer()) dispatch(connectPeer())
ipcRenderer.send('lnd', { msg: 'connectPeer', data: { pubkey, host } }) ipcRenderer.send('lnd', { msg: 'connectPeer', data: { pubkey, host } })
} }
@ -88,13 +88,13 @@ export const connectRequest = ({ pubkey, host }) => (dispatch) => {
export const connectSuccess = (event, peer) => dispatch => dispatch({ type: CONNECT_SUCCESS, peer }) export const connectSuccess = (event, peer) => dispatch => dispatch({ type: CONNECT_SUCCESS, peer })
// Send IPC receive for unsuccessfully connecting to a peer // Send IPC receive for unsuccessfully connecting to a peer
export const connectFailure = (event, { error }) => (dispatch) => { export const connectFailure = (event, { error }) => dispatch => {
dispatch({ type: CONNECT_FAILURE }) dispatch({ type: CONNECT_FAILURE })
dispatch(setError(error)) dispatch(setError(error))
} }
// Send IPC send for disconnecting from a peer // Send IPC send for disconnecting from a peer
export const disconnectRequest = ({ pubkey }) => (dispatch) => { export const disconnectRequest = ({ pubkey }) => dispatch => {
dispatch(disconnectPeer()) dispatch(disconnectPeer())
ipcRenderer.send('lnd', { msg: 'disconnectPeer', data: { pubkey } }) ipcRenderer.send('lnd', { msg: 'disconnectPeer', data: { pubkey } })
} }
@ -107,19 +107,21 @@ export const disconnectSuccess = (event, { pubkey }) => dispatch => dispatch({ t
// ------------------------------------ // ------------------------------------
const ACTION_HANDLERS = { const ACTION_HANDLERS = {
[DISCONNECT_PEER]: state => ({ ...state, disconnecting: true }), [DISCONNECT_PEER]: state => ({ ...state, disconnecting: true }),
[DISCONNECT_SUCCESS]: (state, { pubkey }) => ( [DISCONNECT_SUCCESS]: (state, { pubkey }) => ({
{ ...state,
...state, disconnecting: false, peer: null, peers: state.peers.filter(peer => peer.pub_key !== pubkey) disconnecting: false,
} peer: null,
), peers: state.peers.filter(peer => peer.pub_key !== pubkey)
}),
[DISCONNECT_FAILURE]: state => ({ ...state, disconnecting: false }), [DISCONNECT_FAILURE]: state => ({ ...state, disconnecting: false }),
[CONNECT_PEER]: state => ({ ...state, connecting: true }), [CONNECT_PEER]: state => ({ ...state, connecting: true }),
[CONNECT_SUCCESS]: (state, { peer }) => ( [CONNECT_SUCCESS]: (state, { peer }) => ({
{ ...state,
...state, connecting: false, peerForm: { pubkey: '', host: '', isOpen: false }, peers: [...state.peers, peer] connecting: false,
} peerForm: { pubkey: '', host: '', isOpen: false },
), peers: [...state.peers, peer]
}),
[CONNECT_FAILURE]: state => ({ ...state, connecting: false }), [CONNECT_FAILURE]: state => ({ ...state, connecting: false }),
[SET_PEER_FORM]: (state, { form }) => ({ ...state, peerForm: Object.assign({}, state.peerForm, form) }), [SET_PEER_FORM]: (state, { form }) => ({ ...state, peerForm: Object.assign({}, state.peerForm, form) }),
@ -137,15 +139,10 @@ const peerSelector = state => state.peers.peer
const peersSelector = state => state.peers.peers const peersSelector = state => state.peers.peers
const peersSearchQuerySelector = state => state.peers.searchQuery const peersSearchQuerySelector = state => state.peers.searchQuery
peersSelectors.peerModalOpen = createSelector( peersSelectors.peerModalOpen = createSelector(peerSelector, peer => !!peer)
peerSelector,
peer => (!!peer)
)
peersSelectors.filteredPeers = createSelector( peersSelectors.filteredPeers = createSelector(peersSelector, peersSearchQuerySelector, (peers, query) =>
peersSelector, peers.filter(peer => peer.pub_key.includes(query) || peer.address.includes(query))
peersSearchQuerySelector,
(peers, query) => peers.filter(peer => peer.pub_key.includes(query) || peer.address.includes(query))
) )
export { peersSelectors } export { peersSelectors }

6
app/reducers/requestform.js

@ -57,7 +57,7 @@ const ACTION_HANDLERS = {
[SET_REQUEST_MEMO]: (state, { memo }) => ({ ...state, memo }), [SET_REQUEST_MEMO]: (state, { memo }) => ({ ...state, memo }),
[SET_REQUEST_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, showCurrencyFilters }), [SET_REQUEST_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, showCurrencyFilters }),
[RESET_FORM]: () => (initialState) [RESET_FORM]: () => initialState
} }
const requestFormSelectors = {} const requestFormSelectors = {}
@ -70,7 +70,9 @@ requestFormSelectors.usdAmount = createSelector(
tickerSelectors.currentTicker, tickerSelectors.currentTicker,
(amount, currency, ticker) => { (amount, currency, ticker) => {
if (!ticker || !ticker.price_usd) { return false } if (!ticker || !ticker.price_usd) {
return false
}
return btc.convert(currency, 'usd', amount, ticker.price_usd) return btc.convert(currency, 'usd', amount, ticker.price_usd)
} }

42
app/reducers/ticker.js

@ -47,7 +47,7 @@ export function recieveTickers({ btcTicker, ltcTicker }) {
} }
} }
export const fetchTicker = () => async (dispatch) => { export const fetchTicker = () => async dispatch => {
dispatch(getTickers()) dispatch(getTickers())
const tickers = await requestTickers(['bitcoin', 'litecoin']) const tickers = await requestTickers(['bitcoin', 'litecoin'])
dispatch(recieveTickers(tickers)) dispatch(recieveTickers(tickers))
@ -56,12 +56,11 @@ export const fetchTicker = () => async (dispatch) => {
} }
// Receive IPC event for receiveCryptocurrency // Receive IPC event for receiveCryptocurrency
export const receiveCryptocurrency = (event, currency) => (dispatch) => { export const receiveCryptocurrency = (event, currency) => dispatch => {
dispatch({ type: SET_CURRENCY, currency: cryptoTickers[currency] }) dispatch({ type: SET_CURRENCY, currency: cryptoTickers[currency] })
dispatch({ type: SET_CRYPTO, crypto: cryptoTickers[currency] }) dispatch({ type: SET_CRYPTO, crypto: cryptoTickers[currency] })
} }
// ------------------------------------ // ------------------------------------
// Action Handlers // Action Handlers
// ------------------------------------ // ------------------------------------
@ -69,11 +68,12 @@ const ACTION_HANDLERS = {
[SET_CURRENCY]: (state, { currency }) => ({ ...state, fromCurrency: state.currency, currency }), [SET_CURRENCY]: (state, { currency }) => ({ ...state, fromCurrency: state.currency, currency }),
[SET_CRYPTO]: (state, { crypto }) => ({ ...state, crypto }), [SET_CRYPTO]: (state, { crypto }) => ({ ...state, crypto }),
[GET_TICKERS]: state => ({ ...state, tickerLoading: true }), [GET_TICKERS]: state => ({ ...state, tickerLoading: true }),
[RECIEVE_TICKERS]: (state, { btcTicker, ltcTicker }) => ( [RECIEVE_TICKERS]: (state, { btcTicker, ltcTicker }) => ({
{ ...state,
...state, tickerLoading: false, btcTicker, ltcTicker tickerLoading: false,
} btcTicker,
) ltcTicker
})
} }
// Selectors // Selectors
@ -91,23 +91,21 @@ tickerSelectors.currentTicker = createSelector(
(crypto, btcTicker, ltcTicker) => (crypto === 'btc' ? btcTicker : ltcTicker) (crypto, btcTicker, ltcTicker) => (crypto === 'btc' ? btcTicker : ltcTicker)
) )
tickerSelectors.currentCurrencyFilters = createSelector( tickerSelectors.currentCurrencyFilters = createSelector(currencySelector, currencyFiltersSelector, (currency, filters) =>
currencySelector, filters.filter(f => f.key !== currency)
currencyFiltersSelector,
(currency, filters) => filters.filter(f => f.key !== currency)
) )
tickerSelectors.currencyName = createSelector( tickerSelectors.currencyName = createSelector(currencySelector, infoSelectors.networkSelector, (currency, network) => {
currencySelector, let unit = currency
infoSelectors.networkSelector, if (currency === 'btc') {
(currency, network) => { unit = 'BTC'
let unit = currency
if (currency === 'btc') { unit = 'BTC' }
if (currency === 'sats') { unit = 'satoshis' }
return `${network.unitPrefix}${unit}`
} }
) if (currency === 'sats') {
unit = 'satoshis'
}
return `${network.unitPrefix}${unit}`
})
export { tickerSelectors } export { tickerSelectors }

33
app/reducers/transaction.js

@ -52,7 +52,7 @@ export function hideSuccessTransactionScreen() {
} }
// Send IPC event for payments // Send IPC event for payments
export const fetchTransactions = () => (dispatch) => { export const fetchTransactions = () => dispatch => {
dispatch(getTransactions()) dispatch(getTransactions())
ipcRenderer.send('lnd', { msg: 'transactions' }) ipcRenderer.send('lnd', { msg: 'transactions' })
} }
@ -60,9 +60,7 @@ export const fetchTransactions = () => (dispatch) => {
// Receive IPC event for payments // Receive IPC event for payments
export const receiveTransactions = (event, { transactions }) => dispatch => dispatch({ type: RECEIVE_TRANSACTIONS, transactions }) export const receiveTransactions = (event, { transactions }) => dispatch => dispatch({ type: RECEIVE_TRANSACTIONS, transactions })
export const sendCoins = ({ export const sendCoins = ({ value, addr, currency }) => dispatch => {
value, addr, currency
}) => (dispatch) => {
// backend needs amount in satoshis no matter what currency we are using // backend needs amount in satoshis no matter what currency we are using
const amount = btc.convert(currency, 'sats', value) const amount = btc.convert(currency, 'sats', value)
@ -78,7 +76,7 @@ export const sendCoins = ({
// Receive IPC event for successful payment // Receive IPC event for successful payment
// TODO: Add payment to state, not a total re-fetch // TODO: Add payment to state, not a total re-fetch
export const transactionSuccessful = (event, { txid }) => (dispatch) => { export const transactionSuccessful = (event, { txid }) => dispatch => {
// Get the new list of transactions (TODO dont do an entire new fetch) // Get the new list of transactions (TODO dont do an entire new fetch)
dispatch(fetchTransactions()) dispatch(fetchTransactions())
// Show successful payment state // Show successful payment state
@ -93,13 +91,13 @@ export const transactionSuccessful = (event, { txid }) => (dispatch) => {
dispatch(resetPayForm()) dispatch(resetPayForm())
} }
export const transactionError = (event, { error }) => (dispatch) => { export const transactionError = (event, { error }) => dispatch => {
dispatch({ type: TRANSACTION_FAILED }) dispatch({ type: TRANSACTION_FAILED })
dispatch(setError(error)) dispatch(setError(error))
} }
// Listener for when a new transaction is pushed from the subscriber // Listener for when a new transaction is pushed from the subscriber
export const newTransaction = (event, { transaction }) => (dispatch) => { export const newTransaction = (event, { transaction }) => dispatch => {
// Fetch new balance // Fetch new balance
dispatch(fetchBalance()) dispatch(fetchBalance())
@ -107,7 +105,10 @@ export const newTransaction = (event, { transaction }) => (dispatch) => {
// HTML 5 desktop notification for the new transaction // HTML 5 desktop notification for the new transaction
const notifTitle = transaction.amount > 0 ? 'On-chain Transaction Received!' : 'On-chain Transaction Sent!' const notifTitle = transaction.amount > 0 ? 'On-chain Transaction Received!' : 'On-chain Transaction Sent!'
const notifBody = transaction.amount > 0 ? 'Lucky you, you just received a new on-chain transaction. I\'m jealous.' : 'Hate to see \'em go but love to watch \'em leave. Your on-chain transaction successfully sent.' // eslint-disable-line max-len const notifBody =
transaction.amount > 0
? "Lucky you, you just received a new on-chain transaction. I'm jealous."
: "Hate to see 'em go but love to watch 'em leave. Your on-chain transaction successfully sent." // eslint-disable-line max-len
showNotification(notifTitle, notifBody) showNotification(notifTitle, notifBody)
@ -115,7 +116,6 @@ export const newTransaction = (event, { transaction }) => (dispatch) => {
dispatch(newAddress('p2pkh')) dispatch(newAddress('p2pkh'))
} }
// ------------------------------------ // ------------------------------------
// Action Handlers // Action Handlers
// ------------------------------------ // ------------------------------------
@ -125,14 +125,15 @@ const ACTION_HANDLERS = {
[RECEIVE_TRANSACTIONS]: (state, { transactions }) => ({ ...state, transactionLoading: false, transactions }), [RECEIVE_TRANSACTIONS]: (state, { transactions }) => ({ ...state, transactionLoading: false, transactions }),
[TRANSACTION_SUCCESSFULL]: state => ({ ...state, sendingTransaction: false }), [TRANSACTION_SUCCESSFULL]: state => ({ ...state, sendingTransaction: false }),
[TRANSACTION_FAILED]: state => ({ ...state, sendingTransaction: false }), [TRANSACTION_FAILED]: state => ({ ...state, sendingTransaction: false }),
[ADD_TRANSACTION]: (state, { transaction }) => ( [ADD_TRANSACTION]: (state, { transaction }) => {
// add the transaction only if we are not already aware of it // add the transaction only if we are not already aware of it
state.transactions.find(tx => (tx.tx_hash === transaction.tx_hash)) ? state : { return state.transactions.find(tx => tx.tx_hash === transaction.tx_hash)
...state, ? state
transactions: [transaction, ...state.transactions] : {
} ...state,
), transactions: [transaction, ...state.transactions]
}
},
[SHOW_SUCCESS_TRANSACTION_SCREEN]: (state, { txid }) => ({ ...state, successTransactionScreen: { show: true, txid } }), [SHOW_SUCCESS_TRANSACTION_SCREEN]: (state, { txid }) => ({ ...state, successTransactionScreen: { show: true, txid } }),
[HIDE_SUCCESS_TRANSACTION_SCREEN]: state => ({ ...state, successTransactionScreen: { show: false, txid: '' } }) [HIDE_SUCCESS_TRANSACTION_SCREEN]: state => ({ ...state, successTransactionScreen: { show: false, txid: '' } })
} }

2
app/routes.js

@ -7,7 +7,7 @@ import Activity from './routes/activity'
const routes = () => ( const routes = () => (
<App> <App>
<Switch> <Switch>
<Route path='/' component={Activity} /> <Route path="/" component={Activity} />
</Switch> </Switch>
</App> </App>
) )

120
app/routes/activity/components/Activity.js

@ -19,9 +19,7 @@ class Activity extends Component {
} }
componentWillMount() { componentWillMount() {
const { const { fetchPayments, fetchInvoices, fetchTransactions, fetchBalance } = this.props
fetchPayments, fetchInvoices, fetchTransactions, fetchBalance
} = this.props
fetchBalance() fetchBalance()
fetchPayments() fetchPayments()
@ -30,13 +28,7 @@ class Activity extends Component {
} }
renderActivity(activity) { renderActivity(activity) {
const { const { ticker, currentTicker, showActivityModal, network, currencyName } = this.props
ticker,
currentTicker,
showActivityModal,
network,
currencyName
} = this.props
if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) { if (Object.prototype.hasOwnProperty.call(activity, 'block_hash')) {
// activity is an on-chain tx // activity is an on-chain tx
@ -52,13 +44,7 @@ class Activity extends Component {
} else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) { } else if (Object.prototype.hasOwnProperty.call(activity, 'payment_request')) {
// activity is an LN invoice // activity is an LN invoice
return ( return (
<Invoice <Invoice invoice={activity} ticker={ticker} currentTicker={currentTicker} showActivityModal={showActivityModal} currencyName={currencyName} />
invoice={activity}
ticker={ticker}
currentTicker={currentTicker}
showActivityModal={showActivityModal}
currencyName={currencyName}
/>
) )
} }
// activity is an LN payment // activity is an LN payment
@ -77,13 +63,7 @@ class Activity extends Component {
render() { render() {
const { const {
balance, balance,
activity: { activity: { filters, filter, filterPulldown, searchActive, searchText },
filters,
filter,
filterPulldown,
searchActive,
searchText
},
changeFilter, changeFilter,
currentActivity, currentActivity,
@ -93,62 +73,56 @@ class Activity extends Component {
walletProps walletProps
} = this.props } = this.props
if (!balance.channelBalance || !balance.walletBalance) { return <LoadingBolt /> } if (!balance.channelBalance || !balance.walletBalance) {
return <LoadingBolt />
}
return ( return (
<div> <div>
<Wallet {...walletProps} /> <Wallet {...walletProps} />
<div className={styles.activities}> <div className={styles.activities}>
{ {searchActive ? (
searchActive ? <header className={`${styles.header} ${styles.search}`}>
<header className={`${styles.header} ${styles.search}`}> <section>
<section> <input placeholder="Search" value={searchText} onChange={event => updateSearchText(event.target.value)} />
<input </section>
placeholder='Search' <section
value={searchText} onClick={() => {
onChange={event => updateSearchText(event.target.value)} updateSearchActive(false)
/> updateSearchText('')
</section> }}
<section onClick={() => { updateSearchActive(false); updateSearchText('') }}> >
<span className={styles.xIcon}> <span className={styles.xIcon}>
<Isvg src={xIcon} /> <Isvg src={xIcon} />
</span> </span>
</section> </section>
</header> </header>
: ) : (
<header className={styles.header}> <header className={styles.header}>
<section> <section>
<ul className={styles.filters}> <ul className={styles.filters}>
{ {filters.map(f => (
filters.map(f => ( <li key={f.key} className={f.key === filter.key && styles.activeFilter} onClick={() => changeFilter(f)}>
<li key={f.key} className={f.key === filter.key && styles.activeFilter} onClick={() => changeFilter(f)}> <span>{f.name}</span>
<span>{f.name}</span>
<div className={f.key === filter.key && styles.activeBorder} />
<div className={f.key === filter.key && styles.activeBorder} /> </li>
</li> ))}
)) </ul>
} </section>
</ul> <section onClick={() => updateSearchActive(true)}>
</section> <Isvg src={searchIcon} />
<section onClick={() => updateSearchActive(true)}> </section>
<Isvg src={searchIcon} /> </header>
</section> )}
</header>
}
<ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}> <ul className={`${styles.activityContainer} ${filterPulldown && styles.pulldown}`}>
{ {currentActivity.map((activityBlock, index) => (
currentActivity.map((activityBlock, index) => ( <li className={styles.activity} key={index}>
<li className={styles.activity} key={index}> <h2>{activityBlock.title}</h2>
<h2>{activityBlock.title}</h2> <ul>{activityBlock.activity.map((activity, i) => <li key={i}>{this.renderActivity(activity.el)}</li>)}</ul>
<ul> </li>
{ ))}
activityBlock.activity.map((activity, i) => <li key={i}>{this.renderActivity(activity.el)}</li>)
}
</ul>
</li>
))
}
</ul> </ul>
</div> </div>
</div> </div>

34
app/routes/activity/components/components/Invoice/Invoice.js

@ -9,42 +9,30 @@ import Value from 'components/Value'
import checkmarkIcon from 'icons/check_circle.svg' import checkmarkIcon from 'icons/check_circle.svg'
import styles from '../Activity.scss' import styles from '../Activity.scss'
const Invoice = ({ const Invoice = ({ invoice, ticker, currentTicker, showActivityModal, currencyName }) => (
invoice, ticker, currentTicker, showActivityModal, currencyName
}) => (
<div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}> <div className={`${styles.container} ${!invoice.settled && styles.unpaid}`} onClick={() => showActivityModal('INVOICE', { invoice })}>
{ {!invoice.settled && (
!invoice.settled && ( <div className={styles.pendingIcon}>
<div className={styles.pendingIcon}> <Isvg src={checkmarkIcon} />
<Isvg src={checkmarkIcon} /> </div>
</div> )}
)
}
<div className={styles.data}> <div className={styles.data}>
<div className={styles.title}> <div className={styles.title}>
<h3> <h3>{invoice.settled ? 'Received payment' : 'Requested payment'}</h3>
{ invoice.settled ? 'Received payment' : 'Requested payment' }
</h3>
</div> </div>
<div className={styles.subtitle}> <div className={styles.subtitle}>
<Moment format='h:mm a'>{invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}</Moment> <Moment format="h:mm a">{invoice.settled ? invoice.settled_date * 1000 : invoice.creation_date * 1000}</Moment>
</div> </div>
</div> </div>
<div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}> <div className={`${styles.amount} ${invoice.settled ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Invoice amount'> <span className="hint--top" data-hint="Invoice amount">
<i className={styles.plus}>+</i> <i className={styles.plus}>+</i>
<Value <Value value={invoice.value} currency={ticker.currency} currentTicker={currentTicker} />
value={invoice.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i> {currencyName}</i> <i> {currencyName}</i>
</span> </span>
<span> <span>
<span> <span>${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}</span>
${btc.convert('sats', 'usd', invoice.value, currentTicker.price_usd)}
</span>
</span> </span>
</div> </div>
</div> </div>

26
app/routes/activity/components/components/Payment/Payment.js

@ -8,13 +8,13 @@ import { btc } from 'utils'
import Value from 'components/Value' import Value from 'components/Value'
import styles from '../Activity.scss' import styles from '../Activity.scss'
const Payment = ({ const Payment = ({ payment, ticker, currentTicker, showActivityModal, nodes, currencyName }) => {
payment, ticker, currentTicker, showActivityModal, nodes, currencyName const displayNodeName = pubkey => {
}) => {
const displayNodeName = (pubkey) => {
const node = find(nodes, n => pubkey === n.pub_key) const node = find(nodes, n => pubkey === n.pub_key)
if (node && node.alias.length) { return node.alias } if (node && node.alias.length) {
return node.alias
}
return pubkey.substring(0, 10) return pubkey.substring(0, 10)
} }
@ -23,25 +23,19 @@ const Payment = ({
<div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}> <div className={styles.container} onClick={() => showActivityModal('PAYMENT', { payment })}>
<div className={styles.data}> <div className={styles.data}>
<div className={styles.title}> <div className={styles.title}>
<h3> <h3>{displayNodeName(payment.path[payment.path.length - 1])}</h3>
{displayNodeName(payment.path[payment.path.length - 1])}
</h3>
</div> </div>
<div className={styles.subtitle}> <div className={styles.subtitle}>
<Moment format='h:mm a'>{payment.creation_date * 1000}</Moment> <Moment format="h:mm a">{payment.creation_date * 1000}</Moment>
</div> </div>
</div> </div>
<div className={styles.amount}> <div className={styles.amount}>
<span className='hint--top' data-hint='Payment amount'> <span className="hint--top" data-hint="Payment amount">
<i className={styles.minus}>-</i> <i className={styles.minus}>-</i>
<Value <Value value={payment.value} currency={ticker.currency} currentTicker={currentTicker} />
value={payment.value}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i> {currencyName}</i> <i> {currencyName}</i>
</span> </span>
<span className='hint--bottom' data-hint='Payment fee'> <span className="hint--bottom" data-hint="Payment fee">
${btc.convert('sats', 'usd', payment.value, currentTicker.price_usd)} ${btc.convert('sats', 'usd', payment.value, currentTicker.price_usd)}
</span> </span>
</div> </div>

22
app/routes/activity/components/components/Transaction/Transaction.js

@ -7,31 +7,23 @@ import { btc } from 'utils'
import Value from 'components/Value' import Value from 'components/Value'
import styles from '../Activity.scss' import styles from '../Activity.scss'
const Transaction = ({ const Transaction = ({ transaction, ticker, currentTicker, showActivityModal, currencyName }) => (
transaction, ticker, currentTicker, showActivityModal, currencyName
}) => (
<div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}> <div className={styles.container} onClick={() => showActivityModal('TRANSACTION', { transaction })}>
<div className={styles.data}> <div className={styles.data}>
<div className={styles.title}> <div className={styles.title}>
<h3> <h3>{transaction.amount > 0 ? 'Received' : 'Sent'}</h3>
{ transaction.amount > 0 ? 'Received' : 'Sent' }
</h3>
</div> </div>
<div className={styles.subtitle}> <div className={styles.subtitle}>
<Moment format='h:mm a'>{transaction.time_stamp * 1000}</Moment> <Moment format="h:mm a">{transaction.time_stamp * 1000}</Moment>
</div> </div>
</div> </div>
<div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}> <div className={`${styles.amount} ${transaction.amount > 0 ? styles.positive : styles.negative}`}>
<span className='hint--top' data-hint='Transaction amount'> <span className="hint--top" data-hint="Transaction amount">
<i className={transaction.amount > 0 ? styles.plus : styles.minus}>{ transaction.amount > 0 ? '+' : '-' }</i> <i className={transaction.amount > 0 ? styles.plus : styles.minus}>{transaction.amount > 0 ? '+' : '-'}</i>
<Value <Value value={transaction.amount} currency={ticker.currency} currentTicker={currentTicker} />
value={transaction.amount}
currency={ticker.currency}
currentTicker={currentTicker}
/>
<i> {currencyName}</i> <i> {currencyName}</i>
</span> </span>
<span className='hint--bottom' data-hint='Transaction fee'> <span className="hint--bottom" data-hint="Transaction fee">
${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)} ${btc.convert('sats', 'usd', transaction.amount, currentTicker.price_usd)}
</span> </span>
</div> </div>

18
app/routes/activity/containers/ActivityContainer.js

@ -1,16 +1,8 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { setCurrency, tickerSelectors } from 'reducers/ticker' import { setCurrency, tickerSelectors } from 'reducers/ticker'
import { fetchBalance } from 'reducers/balance' import { fetchBalance } from 'reducers/balance'
import { import { fetchInvoices, setInvoice, invoiceSelectors } from 'reducers/invoice'
fetchInvoices, import { setPayment, fetchPayments, paymentSelectors } from 'reducers/payment'
setInvoice,
invoiceSelectors
} from 'reducers/invoice'
import {
setPayment,
fetchPayments,
paymentSelectors
} from 'reducers/payment'
import { fetchTransactions } from 'reducers/transaction' import { fetchTransactions } from 'reducers/transaction'
import { import {
showActivityModal, showActivityModal,
@ -108,4 +100,8 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => ({
} }
}) })
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Activity) export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(Activity)

25
app/routes/app/components/App.js

@ -18,15 +18,7 @@ import styles from './App.scss'
class App extends Component { class App extends Component {
componentWillMount() { componentWillMount() {
const { const { fetchTicker, fetchInfo, newAddress, fetchChannels, fetchSuggestedNodes, fetchBalance, fetchDescribeNetwork } = this.props
fetchTicker,
fetchInfo,
newAddress,
fetchChannels,
fetchSuggestedNodes,
fetchBalance,
fetchDescribeNetwork
} = this.props
// fetch price ticker // fetch price ticker
fetchTicker() fetchTicker()
@ -65,7 +57,9 @@ class App extends Component {
children children
} = this.props } = this.props
if (!currentTicker) { return <LoadingBolt /> } if (!currentTicker) {
return <LoadingBolt />
}
return ( return (
<div> <div>
@ -79,16 +73,9 @@ class App extends Component {
<ReceiveModal {...receiveModalProps} /> <ReceiveModal {...receiveModalProps} />
<ActivityModal {...activityModalProps} /> <ActivityModal {...activityModalProps} />
<div className={styles.content}> <div className={styles.content}>{children}</div>
{children}
</div>
{ {contactsFormProps.contactsform.isOpen ? <AddChannel {...contactsFormProps} /> : <Network {...networkTabProps} />}
contactsFormProps.contactsform.isOpen ?
<AddChannel {...contactsFormProps} />
:
<Network {...networkTabProps} />
}
<Form formType={form.formType} formProps={formProps} closeForm={closeForm} /> <Form formType={form.formType} formProps={formProps} closeForm={closeForm} />
</div> </div>

48
app/routes/app/containers/AppContainer.js

@ -40,23 +40,17 @@ import {
import { import {
openContactsForm, openContactsForm,
closeContactsForm, closeContactsForm,
setChannelFormType, setChannelFormType,
openManualForm, openManualForm,
closeManualForm, closeManualForm,
openSubmitChannelForm, openSubmitChannelForm,
closeSubmitChannelForm, closeSubmitChannelForm,
updateContactFormSearchQuery, updateContactFormSearchQuery,
updateManualFormSearchQuery, updateManualFormSearchQuery,
updateContactCapacity, updateContactCapacity,
setNode, setNode,
contactFormSelectors, contactFormSelectors,
updateManualFormErrors, updateManualFormErrors,
setContactsCurrencyFilters setContactsCurrencyFilters
} from 'reducers/contactsform' } from 'reducers/contactsform'
@ -271,21 +265,26 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setCurrency: dispatchProps.setCurrency, setCurrency: dispatchProps.setCurrency,
setRequestCurrencyFilters: dispatchProps.setRequestCurrencyFilters, setRequestCurrencyFilters: dispatchProps.setRequestCurrencyFilters,
onRequestSubmit: () => ( onRequestSubmit: () =>
dispatchProps.createInvoice( dispatchProps.createInvoice(
stateProps.requestform.amount, stateProps.requestform.amount,
stateProps.requestform.memo, stateProps.requestform.memo,
stateProps.ticker.currency, stateProps.ticker.currency,
stateProps.currentTicker.price_usd stateProps.currentTicker.price_usd
) )
)
} }
const formProps = (formType) => { const formProps = formType => {
if (!formType) { return {} } if (!formType) {
return {}
}
if (formType === 'PAY_FORM') { return payFormProps } if (formType === 'PAY_FORM') {
if (formType === 'REQUEST_FORM') { return requestFormProps } return payFormProps
}
if (formType === 'REQUEST_FORM') {
return requestFormProps
}
return {} return {}
} }
@ -367,7 +366,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setActivityModalCurrencyFilters: dispatchProps.setActivityModalCurrencyFilters, setActivityModalCurrencyFilters: dispatchProps.setActivityModalCurrencyFilters,
setCurrencyFilters: dispatchProps.setCurrencyFilters, setCurrencyFilters: dispatchProps.setCurrencyFilters,
onCurrencyFilterClick: (currency) => { onCurrencyFilterClick: currency => {
dispatchProps.setCurrency(currency) dispatchProps.setCurrency(currency)
dispatchProps.setActivityModalCurrencyFilters(false) dispatchProps.setActivityModalCurrencyFilters(false)
} }
@ -404,7 +403,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setContactsCurrencyFilters: dispatchProps.setContactsCurrencyFilters, setContactsCurrencyFilters: dispatchProps.setContactsCurrencyFilters,
setCurrencyFilters: dispatchProps.setCurrencyFilters, setCurrencyFilters: dispatchProps.setCurrencyFilters,
onCurrencyFilterClick: (currency) => { onCurrencyFilterClick: currency => {
dispatchProps.updateContactCapacity(btc.convert(stateProps.ticker.currency, currency, stateProps.contactsform.contactCapacity)) dispatchProps.updateContactCapacity(btc.convert(stateProps.ticker.currency, currency, stateProps.contactsform.contactCapacity))
dispatchProps.setCurrency(currency) dispatchProps.setCurrency(currency)
dispatchProps.setContactsCurrencyFilters(false) dispatchProps.setContactsCurrencyFilters(false)
@ -425,9 +424,13 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
showErrors: stateProps.contactsform.showErrors showErrors: stateProps.contactsform.showErrors
} }
const calcChannelFormProps = (formType) => { const calcChannelFormProps = formType => {
if (formType === 'MANUAL_FORM') { return connectManuallyProps } if (formType === 'MANUAL_FORM') {
if (formType === 'SUBMIT_CHANNEL_FORM') { return submitChannelFormProps } return connectManuallyProps
}
if (formType === 'SUBMIT_CHANNEL_FORM') {
return submitChannelFormProps
}
return {} return {}
} }
@ -438,7 +441,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
closeForm: () => dispatchProps.setChannelFormType(null) closeForm: () => dispatchProps.setChannelFormType(null)
} }
return { return {
...stateProps, ...stateProps,
...dispatchProps, ...dispatchProps,
@ -464,9 +466,13 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
formProps: formProps(stateProps.form.formType), formProps: formProps(stateProps.form.formType),
// action to close form // action to close form
closeForm: () => dispatchProps.setFormType(null) closeForm: () => dispatchProps.setFormType(null)
} }
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(App)) export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(App)
)

11
app/store/configureStore.dev.js

@ -8,7 +8,7 @@ import ipc from '../reducers/ipc'
const history = createHashHistory() const history = createHashHistory()
const configureStore = (initialState) => { const configureStore = initialState => {
// Redux Configuration // Redux Configuration
const middleware = [] const middleware = []
const enhancers = [] const enhancers = []
@ -35,9 +35,9 @@ const configureStore = (initialState) => {
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html
actionCreators actionCreators
}) })
: compose : compose
/* eslint-enable no-underscore-dangle */ /* eslint-enable no-underscore-dangle */
@ -49,8 +49,7 @@ const configureStore = (initialState) => {
const store = createStore(rootReducer, initialState, enhancer) const store = createStore(rootReducer, initialState, enhancer)
if (module.hot) { if (module.hot) {
module.hot.accept('../reducers', () => module.hot.accept('../reducers', () => store.replaceReducer(require('../reducers'))) // eslint-disable-line global-require
store.replaceReducer(require('../reducers'))) // eslint-disable-line global-require
} }
return store return store

12
app/utils/blockExplorer.js

@ -1,16 +1,12 @@
import { shell } from 'electron' import { shell } from 'electron'
const showTransaction = (network, txid) => const showTransaction = (network, txid) => shell.openExternal(`${network.explorerUrl}/tx/${txid}`)
shell.openExternal(`${network.explorerUrl}/tx/${txid}`)
const showBlock = (network, blockHash) => const showBlock = (network, blockHash) => shell.openExternal(`${network.explorerUrl}/block/${blockHash}`)
shell.openExternal(`${network.explorerUrl}/block/${blockHash}`)
const showChannelClosing = channel => const showChannelClosing = channel => showTransaction(channel.closing_txid)
showTransaction(channel.closing_txid)
const showChannelPoint = channel => const showChannelPoint = channel => showTransaction(channel.channel.channel_point.split(':')[0])
showTransaction(channel.channel.channel_point.split(':')[0])
export default { export default {
showTransaction, showTransaction,

3
app/utils/log.js

@ -47,13 +47,12 @@ const logConfig = name => ({
} }
}) })
// Create 2 logs for use in the app. // Create 2 logs for use in the app.
export const mainLog = debugLogger.config(logConfig('main'))('zap') export const mainLog = debugLogger.config(logConfig('main'))('zap')
export const lndLog = debugLogger.config(logConfig('lnd '))('zap') export const lndLog = debugLogger.config(logConfig('lnd '))('zap')
let lndLogLevel = null // stored most recent log level for continuity let lndLogLevel = null // stored most recent log level for continuity
export const lndLogGetLevel = (msg) => { export const lndLogGetLevel = msg => {
// Define a mapping between log level prefixes and log level names. // Define a mapping between log level prefixes and log level names.
const levelMap = { const levelMap = {
TRC: 'trace', TRC: 'trace',

4
app/utils/usd.js

@ -3,7 +3,9 @@ export function formatUsd(usd) {
} }
export function usdToBtc(usd, rate) { export function usdToBtc(usd, rate) {
if (usd === undefined || usd === null || usd === '') return null if (usd === undefined || usd === null || usd === '') {
return null
}
return (usd / rate).toFixed(8) return (usd / rate).toFixed(8)
} }

22
package.json

@ -11,11 +11,15 @@
"dev": "cross-env START_HOT=1 npm run start-renderer-dev", "dev": "cross-env START_HOT=1 npm run start-renderer-dev",
"flow": "flow", "flow": "flow",
"flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true", "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true",
"lint": "cross-env NODE_ENV=development eslint --cache --format=node_modules/eslint-formatter-pretty .", "lint-base": "eslint --cache --format=node_modules/eslint-formatter-pretty",
"lint": "npm run lint-base -- .",
"lint-fix-base": "npm run lint-base -- --fix",
"lint-fix": "npm run lint-fix-base -- \"./**/*.{js,json,md}\"",
"lint-styles-base": "stylelint --syntax scss",
"lint-styles": "npm run lint-styles-base -- app/*.scss app/components/*.scss",
"lint-styles-fix-base": "npm run lint-styles-base -- --fix",
"lint-styles-fix": "npm run lint-styles-fix-base -- app/*.scss app/components/*.scss",
"lint-ci": "npm run lint && npm run lint-styles && npm run flow", "lint-ci": "npm run lint && npm run lint-styles && npm run flow",
"lint-fix": "npm run lint -- --fix",
"lint-styles": "stylelint app/*.scss app/components/*.scss --syntax scss",
"lint-styles-fix": "stylelint --fix app/*.scss app/components/*.scss --syntax scss",
"package": "npm run build && build --publish never", "package": "npm run build && build --publish never",
"package-all": "npm run build && build -mwl", "package-all": "npm run build && build -mwl",
"package-linux": "npm run build && build --linux", "package-linux": "npm run build && build --linux",
@ -136,6 +140,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-eslint": "^8.2.3", "babel-eslint": "^8.2.3",
"babel-jest": "^23.0.1", "babel-jest": "^23.0.1",
@ -164,13 +170,17 @@
"enzyme-to-json": "^1.5.1", "enzyme-to-json": "^1.5.1",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-formatter-pretty": "^1.3.0", "eslint-formatter-pretty": "^1.3.0",
"eslint-import-resolver-webpack": "^0.10.0", "eslint-import-resolver-webpack": "^0.10.0",
"eslint-plugin-compat": "^2.4.0", "eslint-plugin-compat": "^2.4.0",
"eslint-plugin-flowtype": "^2.49.3", "eslint-plugin-flowtype": "^2.49.3",
"eslint-plugin-import": "^2.12.0", "eslint-plugin-import": "^2.12.0",
"eslint-plugin-jest": "^21.17.0", "eslint-plugin-jest": "^21.17.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "6.0.3", "eslint-plugin-jsx-a11y": "6.0.3",
"eslint-plugin-markdown": "^1.0.0-beta.6",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "^3.8.0", "eslint-plugin-promise": "^3.8.0",
"eslint-plugin-react": "^7.9.1", "eslint-plugin-react": "^7.9.1",
"express": "^4.15.3", "express": "^4.15.3",
@ -181,11 +191,15 @@
"flow-runtime": "^0.17.0", "flow-runtime": "^0.17.0",
"flow-typed": "^2.1.2", "flow-typed": "^2.1.2",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.0.0-rc.9",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^23.1.0", "jest": "^23.1.0",
"jsdom": "^11.0.0", "jsdom": "^11.0.0",
"lint-staged": "^7.2.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"node-sass": "^4.9.0", "node-sass": "^4.9.0",
"opt-cli": "^1.6.0",
"prettier": "^1.13.5",
"ps-node": "^0.1.6", "ps-node": "^0.1.6",
"react-addons-test-utils": "^15.6.0", "react-addons-test-utils": "^15.6.0",
"react-test-renderer": "^15.6.1", "react-test-renderer": "^15.6.1",

2
test/e2e/e2e.spec.js

@ -27,7 +27,7 @@ describe('main window', function spec() {
expect(title).toBe('Zap') expect(title).toBe('Zap')
}) })
it('should haven\'t any logs in console of main window', async () => { it("should haven't any logs in console of main window", async () => {
const { client } = this.app const { client } = this.app
const logs = await client.getRenderProcessLogs() const logs = await client.getRenderProcessLogs()
expect(logs).toHaveLength(0) expect(logs).toHaveLength(0)

5
test/reducers/balance.spec.js

@ -1,7 +1,4 @@
import balanceReducer, { import balanceReducer, { GET_BALANCE, RECEIVE_BALANCE } from '../../app/reducers/balance'
GET_BALANCE,
RECEIVE_BALANCE
} from '../../app/reducers/balance'
describe('reducers', () => { describe('reducers', () => {
describe('balanceReducer', () => { describe('balanceReducer', () => {

5
test/reducers/info.spec.js

@ -1,7 +1,4 @@
import infoReducer, { import infoReducer, { GET_INFO, RECEIVE_INFO } from '../../app/reducers/info'
GET_INFO,
RECEIVE_INFO
} from '../../app/reducers/info'
describe('reducers', () => { describe('reducers', () => {
describe('infoReducer', () => { describe('infoReducer', () => {

7
test/reducers/ticker.spec.js

@ -1,9 +1,4 @@
import tickerReducer, { import tickerReducer, { SET_CURRENCY, SET_CRYPTO, GET_TICKERS, RECIEVE_TICKERS } from '../../app/reducers/ticker'
SET_CURRENCY,
SET_CRYPTO,
GET_TICKERS,
RECIEVE_TICKERS
} from '../../app/reducers/ticker'
describe('reducers', () => { describe('reducers', () => {
describe('tickerReducer', () => { describe('tickerReducer', () => {

4
test/runTests.js

@ -2,9 +2,7 @@ const spawn = require('cross-spawn')
const path = require('path') const path = require('path')
const s = `\\${path.sep}` const s = `\\${path.sep}`
const pattern = process.argv[2] === 'e2e' const pattern = process.argv[2] === 'e2e' ? `test${s}e2e${s}.+\\.spec\\.js` : `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`
? `test${s}e2e${s}.+\\.spec\\.js`
: `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`
const result = spawn.sync(path.normalize('./node_modules/.bin/jest'), [pattern], { stdio: 'inherit' }) const result = spawn.sync(path.normalize('./node_modules/.bin/jest'), [pattern], { stdio: 'inherit' })

5
test/utils/usd.spec.js

@ -1,7 +1,4 @@
import { import { formatUsd, usdToBtc } from '../../app/utils/usd'
formatUsd,
usdToBtc
} from '../../app/utils/usd'
describe('usd', () => { describe('usd', () => {
describe('formatUsd', () => { describe('formatUsd', () => {

23
webpack.config.base.js

@ -10,16 +10,18 @@ export default {
externals: Object.keys(externals || {}), externals: Object.keys(externals || {}),
module: { module: {
rules: [{ rules: [
test: /\.jsx?$/, {
exclude: /node_modules/, test: /\.jsx?$/,
use: { exclude: /node_modules/,
loader: 'babel-loader', use: {
options: { loader: 'babel-loader',
cacheDirectory: true options: {
cacheDirectory: true
}
} }
} }
}] ]
}, },
output: { output: {
@ -34,10 +36,7 @@ export default {
*/ */
resolve: { resolve: {
extensions: ['.js', '.jsx', '.json'], extensions: ['.js', '.jsx', '.json'],
modules: [ modules: [path.join(__dirname, 'app'), 'node_modules']
path.join(__dirname, 'app'),
'node_modules'
]
}, },
plugins: [ plugins: [

10
webpack.config.renderer.dev.dll.js

@ -158,17 +158,11 @@ export default merge.smart(baseConfig, {
}, },
resolve: { resolve: {
modules: [ modules: ['app']
'app'
]
}, },
entry: { entry: {
renderer: ( renderer: Object.keys(dependencies || {}).filter(dependency => dependency !== 'font-awesome')
Object
.keys(dependencies || {})
.filter(dependency => dependency !== 'font-awesome')
)
}, },
output: { output: {

6
webpack.config.renderer.dev.js

@ -264,11 +264,7 @@ export default merge.smart(baseConfig, {
}, },
before() { before() {
if (process.env.START_HOT) { if (process.env.START_HOT) {
spawn( spawn('npm', ['run', 'start-main-dev'], { shell: true, env: process.env, stdio: 'inherit' })
'npm',
['run', 'start-main-dev'],
{ shell: true, env: process.env, stdio: 'inherit' }
)
.on('close', code => process.exit(code)) .on('close', code => process.exit(code))
.on('error', spawnError => mainLog.error(spawnError)) .on('error', spawnError => mainLog.error(spawnError))
} }

22
webpack.config.renderer.prod.js

@ -70,17 +70,19 @@ export default merge.smart(baseConfig, {
{ {
test: /^((?!\.global).)*\.scss$/, test: /^((?!\.global).)*\.scss$/,
use: ExtractTextPlugin.extract({ use: ExtractTextPlugin.extract({
use: [{ use: [
loader: 'css-loader', {
options: { loader: 'css-loader',
modules: true, options: {
importLoaders: 1, modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]' importLoaders: 1,
localIdentName: '[name]__[local]__[hash:base64:5]'
}
},
{
loader: 'sass-loader'
} }
}, ]
{
loader: 'sass-loader'
}]
}) })
}, },
// WOFF Font // WOFF Font

774
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save