diff --git a/app/components/App/App.js b/app/components/App/App.js
index 1270a238..a52b5f08 100644
--- a/app/components/App/App.js
+++ b/app/components/App/App.js
@@ -25,14 +25,20 @@ class App extends React.Component {
activityModalProps: PropTypes.object,
receiveModalProps: PropTypes.object,
channelFormProps: PropTypes.object,
+ setIsWalletOpen: PropTypes.func.isRequired,
fetchInfo: PropTypes.func.isRequired,
fetchDescribeNetwork: PropTypes.func.isRequired
}
componentDidMount() {
- const { fetchInfo, fetchDescribeNetwork } = this.props
+ const { fetchInfo, fetchDescribeNetwork, setIsWalletOpen } = this.props
+
+ // Set wallet open state.
+ setIsWalletOpen(true)
+
// fetch node info.
fetchInfo()
+
// fetch LN network from nodes POV.
fetchDescribeNetwork()
}
diff --git a/app/components/Home/CreateWalletButton.js b/app/components/Home/CreateWalletButton.js
new file mode 100644
index 00000000..76e07e2c
--- /dev/null
+++ b/app/components/Home/CreateWalletButton.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import { Flex } from 'rebass'
+import { Button, Text } from 'components/UI'
+import PlusCircle from 'components/Icon/PlusCircle'
+
+const CreateWalletButton = ({ ...rest }) => (
+
+)
+export default CreateWalletButton
diff --git a/app/components/Home/Home.js b/app/components/Home/Home.js
new file mode 100644
index 00000000..d946d6a8
--- /dev/null
+++ b/app/components/Home/Home.js
@@ -0,0 +1,126 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Route, Switch, withRouter } from 'react-router-dom'
+import { Box } from 'rebass'
+import { Bar, Heading, MainContent, Sidebar } from 'components/UI'
+import ZapLogo from 'components/Icon/ZapLogo'
+import { CreateWalletButton, WalletLauncher, WalletsMenu, WalletUnlocker } from '.'
+
+const NoMatch = () => (
+
+ Please select a wallet
+
+)
+
+class Home extends React.Component {
+ static propTypes = {
+ activeWallet: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ deleteWallet: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ lightningGrpcActive: PropTypes.bool.isRequired,
+ walletUnlockerGrpcActive: PropTypes.bool.isRequired,
+ wallets: PropTypes.array.isRequired,
+ startLnd: PropTypes.func.isRequired,
+ stopLnd: PropTypes.func.isRequired,
+ setActiveWallet: PropTypes.func.isRequired,
+ unlockWallet: PropTypes.func.isRequired,
+ setUnlockWalletError: PropTypes.func.isRequired,
+ unlockingWallet: PropTypes.bool,
+ unlockWalletError: PropTypes.string
+ }
+
+ /**
+ * Handle click event on the Create new wallet button,
+ */
+ handleCreateNewWalletClick = () => {
+ const { history } = this.props
+ history.push('/onboarding')
+ }
+
+ render() {
+ const {
+ activeWallet,
+ deleteWallet,
+ startLnd,
+ unlockWallet,
+ wallets,
+ setActiveWallet,
+ stopLnd,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
+ setUnlockWalletError,
+ unlockingWallet,
+ unlockWalletError
+ } = this.props
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ const wallet = wallets.find(wallet => wallet.id == params.walletId)
+ if (!wallet) {
+ return null
+ }
+ return (
+
+ )
+ }}
+ />
+ {
+ const wallet = wallets.find(wallet => wallet.id == params.walletId)
+ if (!wallet) {
+ return null
+ }
+ return (
+
+ )
+ }}
+ />
+
+
+
+
+ >
+ )
+ }
+}
+
+export default withRouter(Home)
diff --git a/app/components/Home/WalletHeader.js b/app/components/Home/WalletHeader.js
new file mode 100644
index 00000000..b7bcfb49
--- /dev/null
+++ b/app/components/Home/WalletHeader.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Box } from 'rebass'
+import { Heading, Truncate } from 'components/UI'
+
+const WalletHeader = ({ title }) => (
+
+
+
+
+
+)
+
+WalletHeader.propTypes = {
+ title: PropTypes.string.isRequired
+}
+
+export default WalletHeader
diff --git a/app/components/Home/WalletLauncher.js b/app/components/Home/WalletLauncher.js
new file mode 100644
index 00000000..49723afd
--- /dev/null
+++ b/app/components/Home/WalletLauncher.js
@@ -0,0 +1,117 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { withRouter } from 'react-router-dom'
+import { Box, Flex } from 'rebass'
+import { Bar, Button, Heading } from 'components/UI'
+import ArrowRight from 'components/Icon/ArrowRight'
+import { WalletSettingsFormLocal, WalletSettingsFormRemote, WalletHeader } from '.'
+
+class WalletLauncher extends React.Component {
+ static propTypes = {
+ wallet: PropTypes.object.isRequired,
+ deleteWallet: PropTypes.func.isRequired,
+ startLnd: PropTypes.func.isRequired,
+ lightningGrpcActive: PropTypes.bool.isRequired,
+ walletUnlockerGrpcActive: PropTypes.bool.isRequired,
+ stopLnd: PropTypes.func.isRequired,
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired
+ })
+ }
+
+ componentDidMount() {
+ const { stopLnd } = this.props
+ stopLnd()
+ }
+
+ /**
+ * Redirect to the login page when we establish a connection to lnd.
+ */
+ componentDidUpdate(prevProps) {
+ const { history, lightningGrpcActive, walletUnlockerGrpcActive, wallet } = this.props
+
+ // If the wallet unlocker became active, switch to the login screen
+ if (walletUnlockerGrpcActive && !prevProps.walletUnlockerGrpcActive) {
+ history.push(`/home/wallet/${wallet.id}/unlock`)
+ }
+
+ // If an active wallet connection has been established, switch to the app.
+ if (lightningGrpcActive && !prevProps.lightningGrpcActive) {
+ if (wallet.type === 'local') {
+ history.push('/syncing')
+ } else {
+ history.push('/app')
+ }
+ }
+ }
+
+ walletName = wallet => {
+ if (wallet.type === 'local') {
+ return wallet.alias || `Wallet #${wallet.id}`
+ }
+ return wallet.host.split(':')[0]
+ }
+
+ handleDelete = () => {
+ const { deleteWallet, wallet } = this.props
+ deleteWallet(wallet.id)
+ }
+
+ render() {
+ const { startLnd, wallet } = this.props
+ const walletName = this.walletName(wallet)
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {wallet.type === 'local' && (
+ <>
+ Settings
+
+
+ >
+ )}
+
+ {wallet.type !== 'local' && (
+ <>
+
+ >
+ )}
+
+ )
+ }
+}
+
+export default withRouter(WalletLauncher)
diff --git a/app/components/Home/WalletSettingsFormLocal.js b/app/components/Home/WalletSettingsFormLocal.js
new file mode 100644
index 00000000..1bbc8f64
--- /dev/null
+++ b/app/components/Home/WalletSettingsFormLocal.js
@@ -0,0 +1,221 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Flex } from 'rebass'
+import { DataRow, Form, Input, Label, Range, Toggle } from 'components/UI'
+import * as yup from 'yup'
+
+class WalletSettingsFormLocal extends React.Component {
+ static propTypes = {
+ wallet: PropTypes.object.isRequired,
+ startLnd: PropTypes.func.isRequired
+ }
+
+ validateAutopilot = value => {
+ try {
+ yup.boolean().validateSync(value)
+ } catch (error) {
+ return error.message
+ }
+ }
+
+ validateAutopilotAllocation = value => {
+ try {
+ yup
+ .number()
+ .required()
+ .positive()
+ .min(0)
+ .max(100)
+ .typeError('A number is required')
+ .validateSync(value)
+ } catch (error) {
+ return error.message
+ }
+ }
+
+ validateAutopilotMaxchannels = value => {
+ try {
+ yup
+ .number()
+ .required()
+ .positive()
+ .integer()
+ .max(100)
+ .typeError('A number is required')
+ .validateSync(value)
+ } catch (error) {
+ return error.message
+ }
+ }
+
+ validateAutopilotChansize = value => {
+ try {
+ yup
+ .number()
+ .required()
+ .positive()
+ .integer()
+ .max(100000000)
+ .typeError('A number is required')
+ .validateSync(value)
+ } catch (error) {
+ return error.message
+ }
+ }
+
+ preSubmit = values => {
+ if (values.autopilotAllocation) {
+ values.autopilotAllocation = values.autopilotAllocation / 100
+ }
+ return values
+ }
+
+ onSubmit = async values => {
+ const { startLnd } = this.props
+ return startLnd(values)
+ }
+
+ setFormApi = formApi => {
+ this.formApi = formApi
+ }
+
+ render() {
+ const { wallet, startLnd, ...rest } = this.props
+
+ return (
+
+ )
+ }
+}
+
+export default WalletSettingsFormLocal
diff --git a/app/components/Home/WalletSettingsFormRemote.js b/app/components/Home/WalletSettingsFormRemote.js
new file mode 100644
index 00000000..1bde799d
--- /dev/null
+++ b/app/components/Home/WalletSettingsFormRemote.js
@@ -0,0 +1,39 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Card } from 'rebass'
+import { Form } from 'components/UI'
+
+class WalletSettingsFormRemote extends React.Component {
+ static propTypes = {
+ wallet: PropTypes.object.isRequired,
+ startLnd: PropTypes.func.isRequired
+ }
+
+ onSubmit = async values => {
+ const { startLnd } = this.props
+ return startLnd(values)
+ }
+
+ setFormApi = formApi => {
+ this.formApi = formApi
+ }
+
+ render() {
+ const { wallet, startLnd, ...rest } = this.props
+ return (
+
+ )
+ }
+}
+
+export default WalletSettingsFormRemote
diff --git a/app/components/Home/WalletUnlocker.js b/app/components/Home/WalletUnlocker.js
new file mode 100644
index 00000000..650bb1df
--- /dev/null
+++ b/app/components/Home/WalletUnlocker.js
@@ -0,0 +1,130 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { withRouter } from 'react-router-dom'
+import { Form } from 'informed'
+import { Button, PasswordInput } from 'components/UI'
+import * as yup from 'yup'
+import { WalletHeader } from '.'
+
+/**
+ * @render react
+ * @name WalletUnlocker
+ * @example
+ * {}}
+ setError={() => {}} >
+ */
+class WalletUnlocker extends React.Component {
+ static displayName = 'WalletUnlocker'
+
+ static propTypes = {
+ wallet: PropTypes.object.isRequired,
+ lightningGrpcActive: PropTypes.bool.isRequired,
+ history: PropTypes.shape({
+ push: PropTypes.func.isRequired
+ }),
+ unlockingWallet: PropTypes.bool,
+ unlockWalletError: PropTypes.string,
+ unlockWallet: PropTypes.func.isRequired,
+ setUnlockWalletError: PropTypes.func.isRequired
+ }
+
+ componentDidUpdate(prevProps) {
+ const {
+ wallet,
+ lightningGrpcActive,
+ history,
+ setUnlockWalletError,
+ unlockingWallet,
+ unlockWalletError
+ } = this.props
+
+ // Set the form error if we got an error unlocking.
+ if (unlockWalletError && !prevProps.unlockWalletError) {
+ this.formApi.setError('password', unlockWalletError)
+ setUnlockWalletError(null)
+ }
+
+ // Redirect to the app if the wallet was successfully unlocked.
+ if (!unlockingWallet && prevProps.unlockingWallet && !unlockWalletError) {
+ if (wallet.type === 'local') {
+ history.push('/syncing')
+ } else {
+ history.push('/app')
+ }
+ }
+
+ // If an active wallet connection has been established, switch to the app.
+ if (lightningGrpcActive && !prevProps.lightningGrpcActive) {
+ if (wallet.type === 'local') {
+ history.push('/syncing')
+ } else {
+ history.push('/app')
+ }
+ }
+ }
+
+ setFormApi = formApi => {
+ this.formApi = formApi
+ }
+
+ onSubmit = values => {
+ const { unlockWallet } = this.props
+ unlockWallet(values.password)
+ }
+
+ walletName = wallet => {
+ if (wallet.type === 'local') {
+ return wallet.alias || `Wallet #${wallet.id}`
+ }
+ return wallet.host.split(':')[0]
+ }
+
+ validatePassword = value => {
+ try {
+ yup
+ .string()
+ .required()
+ .min(8)
+ .validateSync(value)
+ } catch (error) {
+ return error.message
+ }
+ }
+
+ render = () => {
+ const { wallet } = this.props
+ const walletName = this.walletName(wallet)
+
+ return (
+
+ )
+ }
+}
+
+export default withRouter(WalletUnlocker)
diff --git a/app/components/Home/WalletsMenu.js b/app/components/Home/WalletsMenu.js
new file mode 100644
index 00000000..1dfc9ab1
--- /dev/null
+++ b/app/components/Home/WalletsMenu.js
@@ -0,0 +1,60 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { NavLink } from 'react-router-dom'
+import { Box } from 'rebass'
+import { Text } from 'components/UI'
+
+const walletName = wallet => {
+ if (wallet.type === 'local') {
+ return wallet.alias || `Wallet #${wallet.id}`
+ }
+ return wallet.host.split(':')[0]
+}
+
+const WalletGroup = ({ setActiveWallet, title, wallets, ...rest }) => (
+
+ {title}
+ {wallets.map(wallet => (
+
+ setActiveWallet(wallet.id)}
+ >
+ {walletName(wallet)}
+
+
+ ))}
+
+)
+
+class WalletsMenu extends React.Component {
+ static displayName = 'WalletsMenu'
+
+ static propTypes = {
+ setActiveWallet: PropTypes.func.isRequired,
+ wallets: PropTypes.array.isRequired
+ }
+
+ render() {
+ const { setActiveWallet, wallets, ...rest } = this.props
+ const localWallets = wallets.filter(wallet => wallet.type === 'local')
+ const otherWallets = wallets.filter(wallet => wallet.type !== 'local')
+
+ return (
+
+
+ {otherWallets.length > 0 && (
+
+ )}
+
+ )
+ }
+}
+
+export default WalletsMenu
diff --git a/app/components/Home/index.js b/app/components/Home/index.js
new file mode 100644
index 00000000..cda94636
--- /dev/null
+++ b/app/components/Home/index.js
@@ -0,0 +1,8 @@
+export Home from './Home'
+export CreateWalletButton from './CreateWalletButton'
+export WalletsMenu from './WalletsMenu'
+export WalletLauncher from './WalletLauncher'
+export WalletUnlocker from './WalletUnlocker'
+export WalletSettingsFormLocal from './WalletSettingsFormLocal'
+export WalletSettingsFormRemote from './WalletSettingsFormRemote'
+export WalletHeader from './WalletHeader'
diff --git a/app/components/Onboarding/Onboarding.js b/app/components/Onboarding/Onboarding.js
index e3a5c54e..2808ac69 100644
--- a/app/components/Onboarding/Onboarding.js
+++ b/app/components/Onboarding/Onboarding.js
@@ -31,8 +31,8 @@ class Onboarding extends React.Component {
connectionCert: PropTypes.string,
connectionMacaroon: PropTypes.string,
connectionString: PropTypes.string,
- lndWalletStarted: PropTypes.bool,
- lndWalletUnlockerStarted: PropTypes.bool,
+ lightningGrpcActive: PropTypes.bool,
+ walletUnlockerGrpcActive: PropTypes.bool,
seed: PropTypes.array,
startLndHostError: PropTypes.string,
startLndCertError: PropTypes.string,
@@ -41,7 +41,7 @@ class Onboarding extends React.Component {
fetchingSeed: PropTypes.bool,
// DISPATCH
createNewWallet: PropTypes.func.isRequired,
- generateSeed: PropTypes.func.isRequired,
+ fetchSeed: PropTypes.func.isRequired,
recoverOldWallet: PropTypes.func.isRequired,
resetOnboarding: PropTypes.func.isRequired,
setAlias: PropTypes.func.isRequired,
@@ -75,8 +75,8 @@ class Onboarding extends React.Component {
connectionCert,
connectionMacaroon,
connectionString,
- lndWalletStarted,
- lndWalletUnlockerStarted,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
seed,
startLndHostError,
startLndCertError,
@@ -97,7 +97,7 @@ class Onboarding extends React.Component {
validateHost,
validateCert,
validateMacaroon,
- generateSeed,
+ fetchSeed,
resetOnboarding,
createNewWallet,
recoverOldWallet,
@@ -116,7 +116,7 @@ class Onboarding extends React.Component {
,
,
,
@@ -171,8 +171,8 @@ class Onboarding extends React.Component {
connectionHost,
connectionCert,
connectionMacaroon,
- lndWalletStarted,
- lndWalletUnlockerStarted,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
startLndHostError,
startLndCertError,
startLndMacaroonError,
@@ -207,8 +207,8 @@ class Onboarding extends React.Component {
{...{
connectionType,
connectionString,
- lndWalletStarted,
- lndWalletUnlockerStarted,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
startLndHostError,
startLndCertError,
startLndMacaroonError,
diff --git a/app/components/Onboarding/Steps/ConnectionConfirm.js b/app/components/Onboarding/Steps/ConnectionConfirm.js
index 0bf1ef78..2ed940ce 100644
--- a/app/components/Onboarding/Steps/ConnectionConfirm.js
+++ b/app/components/Onboarding/Steps/ConnectionConfirm.js
@@ -34,8 +34,8 @@ class ConnectionConfirm extends React.Component {
startLndCertError: PropTypes.string,
startLndMacaroonError: PropTypes.string,
startLnd: PropTypes.func.isRequired,
- lndWalletUnlockerStarted: PropTypes.bool,
- lndWalletStarted: PropTypes.bool
+ walletUnlockerGrpcActive: PropTypes.bool,
+ lightningGrpcActive: PropTypes.bool
}
static defaultProps = {
@@ -81,8 +81,8 @@ class ConnectionConfirm extends React.Component {
connectionCert,
connectionMacaroon,
connectionString,
- lndWalletStarted,
- lndWalletUnlockerStarted,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
startLndHostError,
startLndCertError,
startLndMacaroonError,
diff --git a/app/components/Onboarding/Steps/Login.js b/app/components/Onboarding/Steps/Login.js
index 92cd6bdc..027543bd 100644
--- a/app/components/Onboarding/Steps/Login.js
+++ b/app/components/Onboarding/Steps/Login.js
@@ -9,7 +9,6 @@ class Login extends React.Component {
static propTypes = {
wizardApi: PropTypes.object,
wizardState: PropTypes.object,
- walletDir: PropTypes.string.isRequired,
unlockWalletError: PropTypes.string,
setUnlockWalletError: PropTypes.func.isRequired,
unlockWallet: PropTypes.func.isRequired
@@ -43,7 +42,6 @@ class Login extends React.Component {
const {
wizardApi,
wizardState,
- walletDir,
unlockWallet,
unlockWalletError,
setUnlockWalletError,
diff --git a/app/components/Onboarding/Steps/SeedConfirm.js b/app/components/Onboarding/Steps/SeedConfirm.js
index bf1ee3f7..31356736 100644
--- a/app/components/Onboarding/Steps/SeedConfirm.js
+++ b/app/components/Onboarding/Steps/SeedConfirm.js
@@ -22,10 +22,10 @@ class SeedConfirm extends React.Component {
}
componentDidMount() {
- this.generateSeedWordIndexes()
+ this.fetchSeedWordIndexes()
}
- generateSeedWordIndexes = () => {
+ fetchSeedWordIndexes = () => {
const seedWordIndexes = []
while (seedWordIndexes.length < 3) {
const r = Math.floor(Math.random() * 24) + 1
diff --git a/app/components/Onboarding/Steps/SeedView.js b/app/components/Onboarding/Steps/SeedView.js
index fdcd5352..63827523 100644
--- a/app/components/Onboarding/Steps/SeedView.js
+++ b/app/components/Onboarding/Steps/SeedView.js
@@ -27,7 +27,7 @@ class SeedView extends React.Component {
wizardState: PropTypes.object,
seed: PropTypes.array,
fetchingSeed: PropTypes.bool,
- generateSeed: PropTypes.func.isRequired
+ fetchSeed: PropTypes.func.isRequired
}
static defaultProps = {
@@ -38,14 +38,14 @@ class SeedView extends React.Component {
}
async componentDidMount() {
- const { seed, generateSeed } = this.props
+ const { seed, fetchSeed } = this.props
if (seed.length === 0) {
- generateSeed()
+ fetchSeed()
}
}
render() {
- const { wizardApi, wizardState, seed, generateSeed, fetchingSeed, intl, ...rest } = this.props
+ const { wizardApi, wizardState, seed, fetchSeed, fetchingSeed, intl, ...rest } = this.props
const { getApi, preSubmit, onSubmit, onSubmitFailure } = wizardApi
return (
diff --git a/app/components/Onboarding/Steps/messages.js b/app/components/Onboarding/Steps/messages.js
index 7ea1865f..62ca62a7 100644
--- a/app/components/Onboarding/Steps/messages.js
+++ b/app/components/Onboarding/Steps/messages.js
@@ -41,8 +41,7 @@ export default defineMessages({
hostname_title: 'Host',
import_description: "Recovering a wallet, nice. You don't need anyone else, you got yourself :)",
import_title: 'Import your seed',
- login_description:
- 'It looks like you already have a wallet (wallet found at: `{walletDir}`). Please enter your wallet password to unlock it.',
+ login_description: 'Please enter your wallet password to unlock it.',
login_title: 'Welcome back!',
macaroon_description: 'Path to the lnd macaroon file. Example: /path/to/admin.macaroon',
next: 'Next',
diff --git a/app/containers/App.js b/app/containers/App.js
index 0604478a..d0e6481f 100644
--- a/app/containers/App.js
+++ b/app/containers/App.js
@@ -42,7 +42,7 @@ import { fetchBalance } from 'reducers/balance'
import { fetchDescribeNetwork } from 'reducers/network'
import { clearError } from 'reducers/error'
import { hideActivityModal, activitySelectors } from 'reducers/activity'
-
+import { setIsWalletOpen } from 'reducers/wallet'
import App from 'components/App'
import withLoading from 'components/withLoading'
@@ -77,7 +77,8 @@ const mapDispatchToProps = {
updateManualFormErrors,
setChannelFormType,
fetchDescribeNetwork,
- hideActivityModal
+ hideActivityModal,
+ setIsWalletOpen
}
const mapStateToProps = state => ({
@@ -98,6 +99,7 @@ const mapStateToProps = state => ({
error: state.error,
network: state.network,
settings: state.settings,
+ wallet: state.wallet,
isLoading:
infoSelectors.infoLoading(state) ||
diff --git a/app/containers/Home.js b/app/containers/Home.js
new file mode 100644
index 00000000..4882ccc5
--- /dev/null
+++ b/app/containers/Home.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import { setActiveWallet, walletSelectors, deleteWallet } from 'reducers/wallet'
+import { setUnlockWalletError, stopLnd, startLnd, unlockWallet } from 'reducers/lnd'
+import { Home } from 'components/Home'
+
+const mapStateToProps = state => ({
+ wallets: state.wallet.wallets,
+ activeWallet: walletSelectors.activeWallet(state),
+ activeWalletSettings: walletSelectors.activeWalletSettings(state),
+ lightningGrpcActive: state.lnd.lightningGrpcActive,
+ walletUnlockerGrpcActive: state.lnd.walletUnlockerGrpcActive,
+ unlockingWallet: state.lnd.unlockingWallet,
+ unlockWalletError: state.lnd.unlockWalletError
+})
+
+const mapDispatchToProps = {
+ setActiveWallet,
+ setUnlockWalletError,
+ stopLnd,
+ startLnd,
+ unlockWallet,
+ deleteWallet
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Home)
diff --git a/app/containers/Initializer.js b/app/containers/Initializer.js
new file mode 100644
index 00000000..f7473c0a
--- /dev/null
+++ b/app/containers/Initializer.js
@@ -0,0 +1,92 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router'
+import { walletSelectors } from 'reducers/wallet'
+import { startActiveWallet } from 'reducers/lnd'
+
+/**
+ * Root component that deals with mounting the app and managing top level routing.
+ */
+class Initializer extends React.Component {
+ static propTypes = {
+ onboarding: PropTypes.bool,
+ history: PropTypes.object.isRequired,
+ activeWallet: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ activeWalletSettings: PropTypes.object,
+ isWalletOpen: PropTypes.bool,
+ lightningGrpcActive: PropTypes.bool,
+ walletUnlockerGrpcActive: PropTypes.bool,
+ startActiveWallet: PropTypes.func.isRequired
+ }
+
+ /**
+ * Redirect to the correct page when we establish a connection to lnd.
+ */
+ componentDidUpdate(prevProps) {
+ const {
+ onboarding,
+ history,
+ activeWallet,
+ activeWalletSettings,
+ isWalletOpen,
+ lightningGrpcActive,
+ walletUnlockerGrpcActive,
+ startActiveWallet
+ } = this.props
+
+ // Wait unti we are onboarding before doing anything.
+ if (!onboarding) {
+ return
+ }
+
+ // If we have just determined that the user has an active wallet, attempt to start it.
+ if (typeof activeWallet !== 'undefined') {
+ if (activeWalletSettings) {
+ if (isWalletOpen) {
+ startActiveWallet()
+ } else {
+ history.push(`/home/wallet/${activeWallet}`)
+ }
+ } else {
+ history.push('/onboarding')
+ }
+ }
+
+ // If the wallet unlocker became active, switch to the login screen
+ if (walletUnlockerGrpcActive && !prevProps.walletUnlockerGrpcActive) {
+ history.push(`/home/wallet/${activeWallet}/unlock`)
+ }
+
+ // If an active wallet connection has been established, switch to the app.
+ if (lightningGrpcActive && !prevProps.lightningGrpcActive) {
+ if (activeWalletSettings.type === 'local') {
+ history.push('/syncing')
+ } else {
+ history.push('/app')
+ }
+ }
+ }
+
+ render() {
+ return null
+ }
+}
+
+const mapStateToProps = state => ({
+ onboarding: state.onboarding.onboarding,
+ activeWallet: walletSelectors.activeWallet(state),
+ activeWalletSettings: walletSelectors.activeWalletSettings(state),
+ lightningGrpcActive: state.lnd.lightningGrpcActive,
+ walletUnlockerGrpcActive: state.lnd.walletUnlockerGrpcActive,
+ isWalletOpen: state.wallet.isWalletOpen
+})
+
+const mapDispatchToProps = {
+ startActiveWallet
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(withRouter(Initializer))
diff --git a/app/containers/Logout.js b/app/containers/Logout.js
new file mode 100644
index 00000000..74d6a9eb
--- /dev/null
+++ b/app/containers/Logout.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router'
+import { restart } from 'reducers/lnd'
+import { setIsWalletOpen } from 'reducers/wallet'
+
+/**
+ * Root component that deals with mounting the app and managing top level routing.
+ */
+class Logout extends React.Component {
+ static propTypes = {
+ lightningGrpcActive: PropTypes.bool,
+ walletUnlockerGrpcActive: PropTypes.bool,
+ restart: PropTypes.func.isRequired
+ }
+
+ componentDidMount() {
+ const { lightningGrpcActive, walletUnlockerGrpcActive, restart } = this.props
+ if (lightningGrpcActive || walletUnlockerGrpcActive) {
+ setIsWalletOpen(false)
+ restart()
+ }
+ }
+
+ render() {
+ return null
+ }
+}
+
+const mapStateToProps = state => ({
+ lightningGrpcActive: state.lnd.lightningGrpcActive,
+ walletUnlockerGrpcActive: state.lnd.walletUnlockerGrpcActive
+})
+
+const mapDispatchToProps = {
+ restart,
+ setIsWalletOpen
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(withRouter(Logout))
diff --git a/app/containers/Onboarding.js b/app/containers/Onboarding.js
index 9fa115e6..ffec7b7e 100644
--- a/app/containers/Onboarding.js
+++ b/app/containers/Onboarding.js
@@ -9,18 +9,20 @@ import {
setConnectionMacaroon,
setConnectionString,
setPassword,
- setUnlockWalletError,
- startLnd,
- stopLnd,
validateHost,
validateCert,
validateMacaroon,
- generateSeed,
+ resetOnboarding
+} from 'reducers/onboarding'
+import {
+ setUnlockWalletError,
+ startLnd,
+ stopLnd,
+ fetchSeed,
createNewWallet,
recoverOldWallet,
- resetOnboarding,
unlockWallet
-} from 'reducers/onboarding'
+} from 'reducers/lnd'
const mapStateToProps = state => ({
alias: state.onboarding.alias,
@@ -30,16 +32,15 @@ const mapStateToProps = state => ({
connectionCert: state.onboarding.connectionCert,
connectionMacaroon: state.onboarding.connectionMacaroon,
connectionString: state.onboarding.connectionString,
- lndWalletStarted: state.onboarding.lndWalletStarted,
- lndWalletUnlockerStarted: state.onboarding.lndWalletUnlockerStarted,
- startLndHostError: state.onboarding.startLndHostError,
- startLndCertError: state.onboarding.startLndCertError,
- startLndMacaroonError: state.onboarding.startLndMacaroonError,
+ lightningGrpcActive: state.lnd.lightningGrpcActive,
+ walletUnlockerGrpcActive: state.lnd.walletUnlockerGrpcActive,
+ startLndHostError: state.lnd.startLndHostError,
+ startLndCertError: state.lnd.startLndCertError,
+ startLndMacaroonError: state.lnd.startLndMacaroonError,
seed: state.onboarding.seed,
- signupMode: state.onboarding.signupMode,
- unlockWalletError: state.onboarding.unlockWalletError,
+ unlockWalletError: state.lnd.unlockWalletError,
onboarded: state.onboarding.onboarded,
- fetchingSeed: state.onboarding.fetchingSeed
+ fetchingSeed: state.lnd.fetchingSeed
})
const mapDispatchToProps = {
@@ -57,7 +58,7 @@ const mapDispatchToProps = {
validateHost,
validateCert,
validateMacaroon,
- generateSeed,
+ fetchSeed,
createNewWallet,
recoverOldWallet,
resetOnboarding,
diff --git a/app/containers/Root.js b/app/containers/Root.js
index 34d8c2f3..6c56319a 100644
--- a/app/containers/Root.js
+++ b/app/containers/Root.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-import { Redirect, Route, Switch } from 'react-router-dom'
+import { Route, Switch } from 'react-router-dom'
import { ConnectedRouter } from 'connected-react-router'
import { ThemeProvider } from 'styled-components'
@@ -9,12 +9,16 @@ import { clearError, errorSelectors } from 'reducers/error'
import { loadingSelectors, setLoading, setMounted } from 'reducers/loading'
import { initCurrency, initLocale } from 'reducers/locale'
import { initTheme, themeSelectors } from 'reducers/theme'
+import { initWallets, walletSelectors } from 'reducers/wallet'
import { fetchTicker, tickerSelectors } from 'reducers/ticker'
import { fetchSuggestedNodes } from 'reducers/channels'
-import { Page, Titlebar, GlobalStyle } from 'components/UI'
+import { Page, Titlebar, GlobalStyle, Modal } from 'components/UI'
import GlobalError from 'components/GlobalError'
import withLoading from 'components/withLoading'
+import Initializer from './Initializer'
+import Logout from './Logout'
+import Home from './Home'
import Onboarding from './Onboarding'
import Syncing from './Syncing'
import App from './App'
@@ -30,6 +34,7 @@ const SPLASH_SCREEN_TIME = 1500
*/
class Root extends React.Component {
static propTypes = {
+ hasWallets: PropTypes.bool,
clearError: PropTypes.func.isRequired,
currentTicker: PropTypes.object,
theme: PropTypes.object,
@@ -40,6 +45,7 @@ class Root extends React.Component {
initLocale: PropTypes.func.isRequired,
initCurrency: PropTypes.func.isRequired,
initTheme: PropTypes.func.isRequired,
+ initWallets: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
isMounted: PropTypes.bool.isRequired,
setLoading: PropTypes.func.isRequired,
@@ -67,6 +73,7 @@ class Root extends React.Component {
initLocale,
initCurrency,
initTheme,
+ initWallets,
isLoading,
isMounted,
setLoading,
@@ -80,6 +87,7 @@ class Root extends React.Component {
initTheme()
initLocale()
initCurrency()
+ initWallets()
fetchTicker()
fetchSuggestedNodes()
}
@@ -100,9 +108,9 @@ class Root extends React.Component {
}
render() {
- const { clearError, theme, error, history, isLoading } = this.props
+ const { hasWallets, clearError, theme, error, history, isLoading } = this.props
- // Wait until we have loaded essential data before displaying anything..
+ // Wait until we have loaded essential data before displaying anything.
if (!theme) {
return null
}
@@ -116,10 +124,28 @@ class Root extends React.Component {
- } />
-
-
-
+
+
+ (
+ history.push('/home')}>
+
+
+ )}
+ />
+ (
+ history.push('/logout')} pb={0} px={0}>
+
+
+ )}
+ />
+
+
@@ -130,6 +156,7 @@ class Root extends React.Component {
}
const mapStateToProps = state => ({
+ hasWallets: walletSelectors.hasWallets(state),
currentTicker: tickerSelectors.currentTicker(state),
theme: themeSelectors.currentThemeSettings(state),
error: errorSelectors.getErrorState(state),
@@ -144,6 +171,7 @@ const mapDispatchToProps = {
initCurrency,
initLocale,
initTheme,
+ initWallets,
setLoading,
setMounted
}
diff --git a/app/lib/lnd/config.js b/app/lib/lnd/config.js
index 8529e6b9..c2b9b633 100644
--- a/app/lib/lnd/config.js
+++ b/app/lib/lnd/config.js
@@ -31,7 +31,13 @@ export const networks = {
// Type definition for for local connection settings.
type LndConfigSettingsLocalType = {|
alias?: string,
- autopilot?: boolean
+ autopilot?: boolean,
+ autopilotMaxchannels?: number,
+ autopilotAllocation?: number,
+ autopilotMinchansize?: number,
+ autopilotMaxchansize?: number,
+ autopilotPrivate?: boolean,
+ autopilotMinconfs?: number
|}
// Type definition for for custom connection settings.
@@ -83,11 +89,30 @@ const safeUntildify = (val: ?T): ?T => (typeof val === 'string' ? untildify(v
*/
class LndConfig {
static SETTINGS_PROPS = {
- local: ['alias', 'autopilot'],
+ local: [
+ 'alias',
+ 'autopilot',
+ 'autopilotMaxchannels',
+ 'autopilotAllocation',
+ 'autopilotMinchansize',
+ 'autopilotMaxchansize',
+ 'autopilotPrivate',
+ 'autopilotMinconfs'
+ ],
custom: ['host', 'cert', 'macaroon'],
btcpayserver: ['host', 'macaroon', 'string']
}
+ static SETTINGS_DEFAULTS = {
+ autopilot: true,
+ autopilotMaxchannels: 5,
+ autopilotMinchansize: 20000,
+ autopilotMaxchansize: 16777215,
+ autopilotAllocation: 0.6,
+ autopilotPrivate: true,
+ autopilotMinconfs: 0
+ }
+
// Type descriptor properties.
id: number
type: string
@@ -101,6 +126,12 @@ class LndConfig {
string: ?string
alias: ?string
autopilot: ?boolean
+ autopilotMaxchannels: ?number
+ autopilotMinchansize: ?number
+ autopilotMaxchansize: ?number
+ autopilotAllocation: ?number
+ autopilotPrivate: ?boolean
+ autopilotMinconfs: ?number
// Read only data properties.
+wallet: string
@@ -220,11 +251,30 @@ class LndConfig {
this.chain = options.chain
this.network = options.network
- // If settings were provided then clean them up and assign them to the instance for easy access.
- if (options.settings) {
- debug('Setting settings as: %o', options.settings)
- Object.assign(this, options.settings)
- }
+ // Merge in other whitelisted settings.
+ let settings = Object.assign({}, LndConfig.SETTINGS_DEFAULTS, options.settings)
+ const filteredSettings = Object.keys(settings)
+ .filter(key => LndConfig.SETTINGS_PROPS[this.type].includes(key))
+ .reduce((obj, key) => {
+ let value = settings[key]
+ if (
+ [
+ 'autopilotMaxchannels',
+ 'autopilotMinchansize',
+ 'autopilotMaxchansize',
+ 'autopilotAllocation',
+ 'autopilotMinconfs'
+ ].includes(key)
+ ) {
+ value = Number(settings[key])
+ }
+ return {
+ ...obj,
+ [key]: value
+ }
+ }, {})
+ debug('Setting settings as: %o', filteredSettings)
+ Object.assign(this, filteredSettings)
}
// For local configs host/cert/macaroon are auto-generated.
diff --git a/app/lib/lnd/neutrino.js b/app/lib/lnd/neutrino.js
index 59383c9b..34d09c94 100644
--- a/app/lib/lnd/neutrino.js
+++ b/app/lib/lnd/neutrino.js
@@ -91,14 +91,31 @@ class Neutrino extends EventEmitter {
port: [9735, 9734, 9733, 9732, 9731, 9736, 9737, 9738, 9739]
})
- //Configure lnd.
+ // Genreate autopilot config.
+ const autopilotArgMap: Object = {
+ autopilotAllocation: '--autopilot.allocation',
+ autopilotMaxchannels: '--autopilot.maxchannels',
+ autopilotMinchansize: '--autopilot.minchansize',
+ autopilotMaxchansize: '--autopilot.maxchansize',
+ autopilotMinconfs: '--autopilot.minconfs'
+ }
+ const autopilotConf = []
+ Object.entries(this.lndConfig).forEach(([key, value]) => {
+ if (Object.keys(autopilotArgMap).includes(key)) {
+ autopilotConf.push(`${autopilotArgMap[key]}=${String(value)}`)
+ }
+ })
+
+ // Configure lnd.
const neutrinoArgs = [
`--configfile=${this.lndConfig.configPath}`,
`--lnddir=${this.lndConfig.lndDir}`,
`--listen=0.0.0.0:${p2pListen}`,
`--rpclisten=localhost:${rpcListen}`,
+ `${this.lndConfig.alias ? `--alias=${this.lndConfig.alias}` : ''}`,
`${this.lndConfig.autopilot ? '--autopilot.active' : ''}`,
- `${this.lndConfig.alias ? `--alias=${this.lndConfig.alias}` : ''}`
+ `${this.lndConfig.autopilotPrivate ? '--autopilot.private' : ''}`,
+ ...autopilotConf
]
// Configure neutrino backend.
diff --git a/app/lib/lnd/walletUnlockerMethods/index.js b/app/lib/lnd/walletUnlockerMethods/index.js
index 913a79fa..d02147b7 100644
--- a/app/lib/lnd/walletUnlockerMethods/index.js
+++ b/app/lib/lnd/walletUnlockerMethods/index.js
@@ -18,8 +18,8 @@ export default function(walletUnlocker, log, event, msg, data, lndConfig) {
case 'genSeed':
walletController
.genSeed(walletUnlocker)
- .then(genSeedData => event.sender.send('receiveSeed', genSeedData))
- .catch(error => event.sender.send('receiveSeedError', decorateError(error)))
+ .then(genSeedData => event.sender.send('fetchSeedSuccess', genSeedData))
+ .catch(error => event.sender.send('fetchSeedError', decorateError(error)))
break
case 'unlockWallet':
walletController
diff --git a/app/lib/zap/controller.js b/app/lib/zap/controller.js
index 95058ee7..60e54a22 100644
--- a/app/lib/zap/controller.js
+++ b/app/lib/zap/controller.js
@@ -70,12 +70,16 @@ class ZapController {
{ name: 'startLocalLnd', from: 'onboarding', to: 'running' },
{ name: 'startRemoteLnd', from: 'onboarding', to: 'connected' },
{ name: 'stopLnd', from: '*', to: 'onboarding' },
+ { name: 'restart', from: '*', to: 'onboarding' },
{ name: 'terminate', from: '*', to: 'terminated' }
],
methods: {
onOnboarding: this.onOnboarding.bind(this),
+ onStartOnboarding: this.onStartOnboarding.bind(this),
onBeforeStartLocalLnd: this.onBeforeStartLocalLnd.bind(this),
onBeforeStartRemoteLnd: this.onBeforeStartRemoteLnd.bind(this),
+ onBeforeStopLnd: this.onBeforeStopLnd.bind(this),
+ onBeforeRestart: this.onBeforeRestart.bind(this),
onTerminated: this.onTerminated.bind(this),
onTerminate: this.onTerminate.bind(this)
}
@@ -102,7 +106,7 @@ class ZapController {
this.mainWindow.show()
this.mainWindow.focus()
- // Start the onboarding process.
+ // // Start the onboarding process.
this.startOnboarding()
})
@@ -137,6 +141,9 @@ class ZapController {
stopLnd(...args: any[]) {
return this.fsm.stopLnd(...args)
}
+ restart(...args: any[]) {
+ return this.fsm.restart(...args)
+ }
terminate(...args: any[]) {
return this.fsm.terminate(...args)
}
@@ -171,9 +178,18 @@ class ZapController {
}
// Give the grpc connections a chance to be properly closed out.
- return new Promise(resolve => setTimeout(resolve, 200)).then(() =>
- this.sendMessage('startOnboarding')
- )
+ await new Promise(resolve => setTimeout(resolve, 200))
+
+ if (lifecycle.transition === 'restart') {
+ this.mainWindow.reload()
+ }
+ }
+
+ onStartOnboarding() {
+ mainLog.debug('[FSM] onStartOnboarding...')
+
+ // Notify the app to start the onboarding process.
+ this.sendMessage('startOnboarding')
}
onBeforeStartLocalLnd() {
@@ -230,6 +246,15 @@ class ZapController {
})
}
+ onBeforeStopLnd() {
+ mainLog.debug('[FSM] onBeforeStopLnd...')
+ }
+
+ onBeforeRestart() {
+ mainLog.debug('[FSM] onBeforeRestart...')
+ // this.mainWindow.reload()
+ }
+
async onTerminated(lifecycle: any) {
mainLog.debug('[FSM] onTerminated...')
@@ -321,7 +346,7 @@ class ZapController {
* Starts the LND node and attach event listeners.
* @return {Neutrino} Neutrino instance.
*/
- startNeutrino() {
+ async startNeutrino() {
mainLog.info('Starting Neutrino...')
this.neutrino = new Neutrino(this.lndConfig)
@@ -335,7 +360,8 @@ class ZapController {
this.neutrino.on('exit', (code, signal, lastError) => {
mainLog.info(`Lnd process has shut down (code: ${code}, signal: ${signal})`)
- if (this.is('running') || this.is('connected')) {
+ this.sendMessage('lndStopped')
+ if (this.is('running') || (this.is('connected') && !this.is('onboarding'))) {
dialog.showMessageBox({
type: 'error',
message: `Lnd has unexpectedly quit:\n\nError code: ${code}\nExit signal: ${signal}\nLast error: ${lastError}`
@@ -376,7 +402,13 @@ class ZapController {
this.sendMessage('lndCfilterHeight', Number(height))
})
- return this.neutrino.start()
+ try {
+ const pid = await this.neutrino.start()
+ this.sendMessage('lndStarted', this.lndConfig)
+ return pid
+ } catch (e) {
+ // console.error(e)
+ }
}
/**
@@ -448,7 +480,13 @@ class ZapController {
* Add IPC event listeners...
*/
_registerIpcListeners() {
- ipcMain.on('startLnd', (event, options: onboardingOptions) => this.startLnd(options))
+ ipcMain.on('startLnd', async (event, options: onboardingOptions) => {
+ try {
+ await this.startLnd(options)
+ } catch (e) {
+ mainLog.error('Unable to start lnd: %s', e.message)
+ }
+ })
ipcMain.on('startLightningWallet', () =>
this.startLightningWallet().catch(e => {
// Notify the app of errors.
@@ -459,6 +497,7 @@ class ZapController {
})
)
ipcMain.on('stopLnd', () => this.stopLnd())
+ ipcMain.on('restart', () => this.restart())
}
/**
@@ -467,6 +506,7 @@ class ZapController {
_removeIpcListeners() {
ipcMain.removeAllListeners('startLnd')
ipcMain.removeAllListeners('stopLnd')
+ ipcMain.removeAllListeners('restart')
ipcMain.removeAllListeners('startLightningWallet')
ipcMain.removeAllListeners('walletUnlocker')
ipcMain.removeAllListeners('lnd')
diff --git a/app/reducers/index.js b/app/reducers/index.js
index c3604e21..77068248 100644
--- a/app/reducers/index.js
+++ b/app/reducers/index.js
@@ -22,6 +22,7 @@ import network from './network'
import error from './error'
import loading from './loading'
import settings from './settings'
+import wallet from './wallet'
export default history =>
combineReducers({
@@ -50,5 +51,6 @@ export default history =>
network,
error,
loading,
- settings
+ settings,
+ wallet
})
diff --git a/app/reducers/ipc.js b/app/reducers/ipc.js
index e6a1dee3..7863b138 100644
--- a/app/reducers/ipc.js
+++ b/app/reducers/ipc.js
@@ -1,11 +1,19 @@
import createIpc from 'redux-electron-ipc'
import { receiveLocale } from './locale'
import {
- lndSyncStatus,
currentBlockHeight,
+ fetchSeedSuccess,
+ fetchSeedError,
+ lightningGrpcActive,
+ lndSyncStatus,
+ lndStopped,
+ lndStarted,
lndBlockHeight,
lndCfilterHeight,
- lightningGrpcActive,
+ setUnlockWalletError,
+ startLndError,
+ walletCreated,
+ walletUnlocked,
walletUnlockerGrpcActive
} from './lnd'
import { receiveInfo } from './info'
@@ -40,15 +48,7 @@ import {
import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network'
-import {
- startOnboarding,
- startLndError,
- receiveSeed,
- receiveSeedError,
- walletCreated,
- walletUnlocked,
- setUnlockWalletError
-} from './onboarding'
+import { startOnboarding } from './onboarding'
// Import all receiving IPC event handlers and pass them into createIpc
const ipc = createIpc({
@@ -113,9 +113,11 @@ const ipc = createIpc({
startOnboarding,
startLndError,
+ lndStopped,
+ lndStarted,
walletUnlockerGrpcActive,
- receiveSeed,
- receiveSeedError,
+ fetchSeedSuccess,
+ fetchSeedError,
walletCreated,
walletUnlocked,
setUnlockWalletError
diff --git a/app/reducers/lnd.js b/app/reducers/lnd.js
index 52c7e932..f2796e8f 100644
--- a/app/reducers/lnd.js
+++ b/app/reducers/lnd.js
@@ -1,9 +1,11 @@
+import { ipcRenderer } from 'electron'
import { createSelector } from 'reselect'
import { showNotification } from 'lib/utils/notifications'
import db from 'store/db'
import { fetchBalance } from './balance'
import { fetchInfo, setHasSynced, infoSelectors } from './info'
-import { lndWalletStarted, lndWalletUnlockerStarted } from './onboarding'
+import { putWallet, setActiveWallet } from './wallet'
+import { onboardingFinished, setSeed } from './onboarding'
// ------------------------------------
// Constants
@@ -20,6 +22,24 @@ export const RECEIVE_LND_CFILTER_HEIGHT = 'RECEIVE_LND_CFILTER_HEIGHT'
export const SET_WALLET_UNLOCKER_ACTIVE = 'SET_WALLET_UNLOCKER_ACTIVE'
export const SET_LIGHTNING_WALLET_ACTIVE = 'SET_LIGHTNING_WALLET_ACTIVE'
+export const STARTING_LND = 'STARTING_LND'
+export const LND_STARTED = 'LND_STARTED'
+export const SET_START_LND_ERROR = 'SET_START_LND_ERROR'
+
+export const STOPPING_LND = 'STOPPING_LND'
+export const LND_STOPPED = 'LND_STOPPED'
+
+export const CREATING_NEW_WALLET = 'CREATING_NEW_WALLET'
+export const RECOVERING_OLD_WALLET = 'RECOVERING_OLD_WALLET'
+
+export const UNLOCKING_WALLET = 'UNLOCKING_WALLET'
+export const WALLET_UNLOCKED = 'WALLET_UNLOCKED'
+export const SET_UNLOCK_WALLET_ERROR = 'SET_UNLOCK_WALLET_ERROR'
+
+export const FETCH_SEED = 'FETCH_SEED'
+export const FETCH_SEED_ERROR = 'FETCH_SEED_ERROR'
+export const FETCH_SEED_SUCCESS = 'FETCH_SEED_SUCCESS'
+
// ------------------------------------
// Actions
// ------------------------------------
@@ -66,17 +86,29 @@ export const lndSyncStatus = (event, status) => async (dispatch, getState) => {
}
// Connected to Lightning gRPC interface (lnd wallet is connected and unlocked)
-export const lightningGrpcActive = (event, lndConfig) => dispatch => {
+export const lightningGrpcActive = (event, lndConfig) => async dispatch => {
dispatch({ type: SET_LIGHTNING_WALLET_ACTIVE })
+ // Once we we have established a connection, save the wallet settings.
+ if (lndConfig.id !== 'tmp') {
+ const wallet = await dispatch(putWallet(lndConfig))
+ dispatch(setActiveWallet(wallet.id))
+ }
+
// Let the onboarding process know that wallet is active.
dispatch(lndWalletStarted(lndConfig))
}
// Connected to WalletUnlocker gRPC interface (lnd is ready to unlock or create wallet)
-export const walletUnlockerGrpcActive = () => dispatch => {
+export const walletUnlockerGrpcActive = (event, lndConfig) => async dispatch => {
dispatch({ type: SET_WALLET_UNLOCKER_ACTIVE })
+ // Once we we have established a connection, save the wallet settings.
+ if (lndConfig.id !== 'tmp') {
+ const wallet = await dispatch(putWallet(lndConfig))
+ dispatch(setActiveWallet(wallet.id))
+ }
+
// Let the onboarding process know that the wallet unlocker has started.
dispatch(lndWalletUnlockerStarted())
}
@@ -96,10 +128,228 @@ export const lndCfilterHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_LND_CFILTER_HEIGHT, lndCfilterHeight: height })
}
+export const startLnd = options => async dispatch => {
+ return new Promise((resolve, reject) => {
+ // Tell the main process to start lnd using the supplied connection details.
+ dispatch({ type: STARTING_LND })
+ ipcRenderer.send('startLnd', options)
+
+ ipcRenderer.once('startLndError', error => {
+ ipcRenderer.removeListener('startLndSuccess', resolve)
+ reject(error)
+ })
+
+ ipcRenderer.once('startLndSuccess', res => {
+ ipcRenderer.removeListener('startLndError', reject)
+ resolve(res)
+ })
+ })
+}
+
+// Listener for errors connecting to LND gRPC
+export const startLndError = (event, errors) => dispatch => {
+ dispatch(setStartLndError(errors))
+}
+
+export function setStartLndError(errors) {
+ return {
+ type: SET_START_LND_ERROR,
+ errors
+ }
+}
+
+export const stopLnd = () => async (dispatch, getState) => {
+ const state = getState().lnd
+ if (state.lndStarted && !state.stoppingLnd) {
+ dispatch({ type: STOPPING_LND })
+ ipcRenderer.send('stopLnd')
+ }
+}
+
+export const lndStopped = () => async dispatch => {
+ dispatch({ type: LND_STOPPED })
+}
+
+export const lndStarted = () => async dispatch => {
+ dispatch({ type: LND_STARTED })
+}
+
+export const unlockWallet = password => async dispatch => {
+ dispatch({ type: UNLOCKING_WALLET })
+ ipcRenderer.send('walletUnlocker', {
+ msg: 'unlockWallet',
+ data: { wallet_password: password }
+ })
+}
+
+export const restart = () => () => {
+ ipcRenderer.send('restart')
+}
+
+/**
+ * As soon as we have an active connection to a WalletUnlocker service, attempt to generate a new seed which kicks off
+ * the process of creating or unlocking a wallet.
+ */
+export const lndWalletUnlockerStarted = () => (dispatch, getState) => {
+ const state = getState().lnd
+ const onboardingState = getState().onboarding
+
+ // Handle generate seed.
+ if (state.fetchingSeed) {
+ ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
+ }
+
+ // Handle unlock wallet.
+ else if (state.unlockingWallet) {
+ ipcRenderer.send('walletUnlocker', {
+ msg: 'unlockWallet',
+ data: { wallet_password: onboardingState.password }
+ })
+ }
+
+ // Handle create wallet.
+ else if (state.creatingNewWallet) {
+ ipcRenderer.send('walletUnlocker', {
+ msg: 'initWallet',
+ data: {
+ wallet_password: onboardingState.password,
+ cipher_seed_mnemonic: onboardingState.seed
+ }
+ })
+ }
+
+ // Handle recover wallet.
+ else if (state.recoveringOldWallet) {
+ ipcRenderer.send('walletUnlocker', {
+ msg: 'initWallet',
+ data: {
+ wallet_password: onboardingState.password,
+ cipher_seed_mnemonic: onboardingState.seed,
+ recovery_window: 250
+ }
+ })
+ }
+}
+
+export const walletCreated = () => dispatch => {
+ dispatch({ type: WALLET_UNLOCKED })
+ dispatch(onboardingFinished())
+ ipcRenderer.send('startLightningWallet')
+}
+
+export const walletUnlocked = () => dispatch => {
+ dispatch({ type: WALLET_UNLOCKED })
+ dispatch(onboardingFinished())
+ ipcRenderer.send('startLightningWallet')
+}
+
+export const setUnlockWalletError = (event, unlockWalletError) => dispatch => {
+ dispatch({ type: SET_UNLOCK_WALLET_ERROR, unlockWalletError })
+}
+
+export const fetchSeed = () => async dispatch => {
+ dispatch({ type: FETCH_SEED })
+ await dispatch(
+ startLnd({
+ id: `tmp`,
+ type: 'local',
+ chain: 'bitcoin',
+ network: 'testnet'
+ })
+ )
+}
+
+// Listener for when LND creates and sends us a generated seed
+export const fetchSeedSuccess = (event, { cipher_seed_mnemonic }) => dispatch => {
+ dispatch({ type: FETCH_SEED_SUCCESS, seed: cipher_seed_mnemonic })
+ dispatch(setSeed(cipher_seed_mnemonic))
+ dispatch(stopLnd())
+}
+
+// Listener for when LND throws an error on seed creation
+export const fetchSeedError = (event, error) => dispatch => {
+ dispatch({ type: FETCH_SEED_ERROR, error })
+}
+
+export const createNewWallet = () => async (dispatch, getState) => {
+ const onboardingState = getState().onboarding
+
+ // Define the wallet config.
+ let wallet = {
+ type: 'local',
+ chain: 'bitcoin',
+ network: 'testnet',
+ settings: {
+ autopilot: onboardingState.autopilot,
+ alias: onboardingState.alias
+ }
+ }
+
+ // Save the wallet config.
+ wallet = await dispatch(putWallet(wallet))
+
+ // Start Lnd and trigger the wallet to be initialised as soon as the wallet unlocker is available.
+ dispatch({ type: CREATING_NEW_WALLET })
+ await dispatch(startLnd(wallet))
+}
+
+export const recoverOldWallet = () => async dispatch => {
+ // Define the wallet config.
+ let wallet = {
+ type: 'local',
+ chain: 'bitcoin',
+ network: 'testnet'
+ }
+
+ // Save the wallet config.
+ wallet = await dispatch(putWallet(wallet))
+
+ // Start Lnd and trigger the wallet to be recovered as soon as the wallet unlocker is available.
+ dispatch({ type: RECOVERING_OLD_WALLET })
+ await dispatch(startLnd(wallet))
+}
+
+export const startActiveWallet = () => async (dispatch, getState) => {
+ const state = getState().lnd
+ if (!state.lndStarted && !state.startingLnd) {
+ const activeWallet = await db.settings.get({ key: 'activeWallet' })
+ if (activeWallet) {
+ const wallet = await db.wallets.get({ id: activeWallet.value })
+ if (wallet) {
+ dispatch(startLnd(wallet))
+ }
+ }
+ }
+}
+
+/**
+ * As soon as we have an active connection to an unlocked wallet, fetch the wallet info so that we have the key data as
+ * early as possible.
+ */
+export const lndWalletStarted = () => async dispatch => {
+ // Fetch info from lnd.
+ dispatch(fetchInfo())
+
+ // Let the onboarding process know that the wallet has started.
+ dispatch(onboardingFinished())
+}
+
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
+ [FETCH_SEED]: state => ({ ...state, fetchingSeed: true }),
+ [FETCH_SEED_SUCCESS]: state => ({
+ ...state,
+ fetchingSeed: false,
+ fetchSeedError: ''
+ }),
+ [FETCH_SEED_ERROR]: (state, { error }) => ({
+ ...state,
+ fetchingSeed: false,
+ fetchSeedError: error
+ }),
+
[SET_SYNC_STATUS_PENDING]: state => ({ ...state, syncStatus: 'pending' }),
[SET_SYNC_STATUS_WAITING]: state => ({ ...state, syncStatus: 'waiting' }),
[SET_SYNC_STATUS_IN_PROGRESS]: state => ({ ...state, syncStatus: 'in-progress' }),
@@ -112,6 +362,23 @@ const ACTION_HANDLERS = {
[RECEIVE_LND_BLOCK_HEIGHT]: (state, { lndBlockHeight }) => ({ ...state, lndBlockHeight }),
[RECEIVE_LND_CFILTER_HEIGHT]: (state, { lndCfilterHeight }) => ({ ...state, lndCfilterHeight }),
+ [STARTING_LND]: state => ({
+ ...state,
+ startingLnd: true,
+ lndStarted: false
+ }),
+ [LND_STARTED]: state => ({
+ ...state,
+ startingLnd: false,
+ lndStarted: true
+ }),
+ [SET_START_LND_ERROR]: (state, { errors }) => ({
+ ...state,
+ startLndHostError: errors.host,
+ startLndCertError: errors.cert,
+ startLndMacaroonError: errors.macaroon
+ }),
+
[SET_WALLET_UNLOCKER_ACTIVE]: state => ({
...state,
walletUnlockerGrpcActive: true,
@@ -121,6 +388,29 @@ const ACTION_HANDLERS = {
...state,
lightningGrpcActive: true,
walletUnlockerGrpcActive: false
+ }),
+
+ [STOPPING_LND]: state => ({
+ ...state,
+ stoppingLnd: true
+ }),
+ [LND_STOPPED]: state => ({
+ ...state,
+ ...initialState
+ }),
+
+ [CREATING_NEW_WALLET]: state => ({ ...state, creatingNewWallet: true }),
+ [RECOVERING_OLD_WALLET]: state => ({ ...state, recoveringOldWallet: true }),
+ [UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }),
+ [WALLET_UNLOCKED]: state => ({
+ ...state,
+ unlockingWallet: false,
+ unlockWalletError: ''
+ }),
+ [SET_UNLOCK_WALLET_ERROR]: (state, { unlockWalletError }) => ({
+ ...state,
+ unlockingWallet: false,
+ unlockWalletError
})
}
@@ -128,16 +418,28 @@ const ACTION_HANDLERS = {
// Reducer
// ------------------------------------
const initialState = {
- syncStatus: 'pending',
+ fetchingSeed: false,
+ startingLnd: false,
+ stoppingLnd: false,
+ lndStarted: false,
+ creatingNewWallet: false,
+ recoveringOldWallet: false,
+ unlockingWallet: false,
walletUnlockerGrpcActive: false,
lightningGrpcActive: false,
+ unlockWalletError: '',
+ startLndHostError: '',
+ startLndCertError: '',
+ startLndMacaroonError: '',
+ fetchSeedError: '',
+ syncStatus: 'pending',
blockHeight: 0,
lndBlockHeight: 0,
lndCfilterHeight: 0
}
// ------------------------------------
-// Reducer
+// Selectors
// ------------------------------------
const lndSelectors = {}
const blockHeightSelector = state => state.lnd.blockHeight
@@ -163,6 +465,10 @@ lndSelectors.syncPercentage = createSelector(
export { lndSelectors }
+// ------------------------------------
+// Reducer
+// ------------------------------------
+//
export default function lndReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
diff --git a/app/reducers/onboarding.js b/app/reducers/onboarding.js
index ad331a63..3c6b7770 100644
--- a/app/reducers/onboarding.js
+++ b/app/reducers/onboarding.js
@@ -1,16 +1,15 @@
-import crypto from 'crypto'
import { createSelector } from 'reselect'
-import { ipcRenderer } from 'electron'
import get from 'lodash.get'
import db from 'store/db'
import { validateHost as doHostValidation } from 'lib/utils/validateHost'
import { fileExists } from 'lib/utils/fileExists'
-import { fetchInfo } from './info'
-import { setError } from './error'
+import { setStartLndError } from './lnd'
// ------------------------------------
// Constants
// ------------------------------------
+export const ONBOARDING_STARTED = 'ONBOARDING_STARTED'
+export const ONBOARDING_FINISHED = 'ONBOARDING_FINISHED'
export const SET_CONNECTION_TYPE = 'SET_CONNECTION_TYPE'
export const SET_CONNECTION_STRING = 'SET_CONNECTION_STRING'
export const SET_CONNECTION_HOST = 'SET_CONNECTION_HOST'
@@ -19,34 +18,10 @@ export const SET_CONNECTION_MACAROON = 'SET_CONNECTION_MACAROON'
export const SET_ALIAS = 'SET_ALIAS'
export const SET_AUTOPILOT = 'SET_AUTOPILOT'
export const SET_PASSWORD = 'SET_PASSWORD'
-export const SET_LND_WALLET_UNLOCKER_STARTED = 'SET_LND_WALLET_UNLOCKER_STARTED'
-export const SET_LND_WALLET_STARTED = 'SET_LND_WALLET_STARTED'
-
-export const FETCH_SEED = 'FETCH_SEED'
export const SET_SEED = 'SET_SEED'
-
-export const ONBOARDING_STARTED = 'ONBOARDING_STARTED'
-export const ONBOARDING_FINISHED = 'ONBOARDING_FINISHED'
-
-export const STARTING_LND = 'STARTING_LND'
-export const LND_STARTED = 'LND_STARTED'
-export const SET_START_LND_ERROR = 'SET_START_LND_ERROR'
-
-export const STOPPING_LND = 'STOPPING_LND'
-export const LND_STOPPED = 'LND_STOPPED'
-
-export const LOADING_EXISTING_WALLET = 'LOADING_EXISTING_WALLET'
-export const CREATING_NEW_WALLET = 'CREATING_NEW_WALLET'
-export const RECOVERING_OLD_WALLET = 'RECOVERING_OLD_WALLET'
-
-export const UNLOCKING_WALLET = 'UNLOCKING_WALLET'
-export const WALLET_UNLOCKED = 'WALLET_UNLOCKED'
-export const SET_UNLOCK_WALLET_ERROR = 'SET_UNLOCK_WALLET_ERROR'
-
export const VALIDATING_HOST = 'VALIDATING_HOST'
export const VALIDATING_CERT = 'VALIDATING_CERT'
export const VALIDATING_MACAROON = 'VALIDATING_MACAROON'
-
export const RESET_ONBOARDING = 'RESET_ONBOARDING'
// ------------------------------------
@@ -68,6 +43,18 @@ export const resetOnboarding = () => dispatch => {
dispatch({ type: SET_SEED, seed: [] })
}
+export function onboardingStarted() {
+ return {
+ type: ONBOARDING_STARTED
+ }
+}
+
+export function onboardingFinished() {
+ return {
+ type: ONBOARDING_FINISHED
+ }
+}
+
export const setConnectionType = connectionType => async (dispatch, getState) => {
const previousType = connectionTypeSelector(getState())
@@ -142,15 +129,10 @@ export function setPassword(password) {
}
}
-export function setLndWalletUnlockerStarted() {
- return {
- type: SET_LND_WALLET_UNLOCKER_STARTED
- }
-}
-
-export function setLndWalletStarted() {
+export function setSeed(seed) {
return {
- type: SET_LND_WALLET_STARTED
+ type: SET_SEED,
+ seed
}
}
@@ -196,234 +178,8 @@ export const validateMacaroon = macaroonPath => async dispatch => {
}
}
-export const startLnd = options => async dispatch => {
- return new Promise((resolve, reject) => {
- // Tell the main process to start lnd using the supplied connection details.
- dispatch({ type: STARTING_LND })
- ipcRenderer.send('startLnd', options)
-
- ipcRenderer.once('startLndError', error => {
- ipcRenderer.removeListener('startLndSuccess', resolve)
- reject(error)
- })
-
- ipcRenderer.once('startLndSuccess', res => {
- ipcRenderer.removeListener('startLndError', reject)
- resolve(res)
- })
- })
-}
-
-// Listener for errors connecting to LND gRPC
-export const startLndError = (event, errors) => (dispatch, getState) => {
- const connectionType = connectionTypeSelector(getState())
- switch (connectionType) {
- case 'custom':
- dispatch(setStartLndError(errors))
- break
- case 'btcpayserver':
- dispatch(setStartLndError(errors))
- break
- default:
- dispatch(setError(errors))
- }
-}
-
-export const lndStarted = () => async dispatch => {
- dispatch({ type: LND_STARTED })
-}
-
-export function setStartLndError(errors) {
- return {
- type: SET_START_LND_ERROR,
- errors
- }
-}
-
-export const stopLnd = () => async dispatch => {
- dispatch({ type: STOPPING_LND })
- ipcRenderer.send('stopLnd')
-}
-
-export const lndStopped = () => async dispatch => {
- dispatch({ type: LND_STOPPED })
-}
-
-export const generateSeed = () => async dispatch => {
- dispatch({ type: FETCH_SEED })
- ipcRenderer.send('startLnd', {
- id: `tmp`,
- type: 'local',
- chain: 'bitcoin',
- network: 'testnet'
- })
-}
-
-export const createNewWallet = () => (dispatch, getState) => {
- crypto.randomBytes(16, async (err, buffer) => {
- const state = getState().onboarding
-
- // Define the wallet config.
- const wallet = {
- id: buffer.toString('hex'),
- type: 'local',
- chain: 'bitcoin',
- network: 'testnet',
- settings: {
- autopilot: state.autopilot,
- alias: state.alias
- }
- }
-
- // Save the wallet config.
- await db.wallets.put(wallet)
-
- // Start Lnd and trigger the wallet to be initialised as soon as the wallet unlocker is available.
- dispatch({ type: CREATING_NEW_WALLET })
- ipcRenderer.send('startLnd', wallet)
- })
-}
-
-export const recoverOldWallet = () => dispatch => {
- crypto.randomBytes(16, function(err, buffer) {
- const id = buffer.toString('hex')
-
- dispatch({ type: RECOVERING_OLD_WALLET })
- ipcRenderer.send('startLnd', {
- id,
- type: 'local',
- chain: 'bitcoin',
- network: 'testnet'
- })
- })
-}
-
-export const startActiveWallet = () => async dispatch => {
- const activeWallet = await db.settings.get({ key: 'activeWallet' })
- if (activeWallet) {
- const wallet = await db.wallets.get({ id: activeWallet.value })
- if (wallet) {
- dispatch(startLnd(wallet))
- }
- }
-}
-
-export const unlockWallet = password => async dispatch => {
- dispatch({ type: UNLOCKING_WALLET })
- ipcRenderer.send('walletUnlocker', {
- msg: 'unlockWallet',
- data: { wallet_password: password }
- })
-}
-
-/**
- * As soon as we have an active connection to a WalletUnlocker service, attempt to generate a new seed which kicks off
- * the process of creating or unlocking a wallet.
- */
-export const lndWalletUnlockerStarted = () => (dispatch, getState) => {
- dispatch(setLndWalletUnlockerStarted('active'))
- const state = getState().onboarding
-
- // Handle generate seed.
- if (state.fetchingSeed) {
- ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
- }
-
- // Handle unlock wallet.
- else if (state.unlockingWallet) {
- ipcRenderer.send('walletUnlocker', {
- msg: 'unlockWallet',
- data: { wallet_password: state.password }
- })
- }
-
- // Handle create wallet.
- else if (state.creatingNewWallet) {
- ipcRenderer.send('walletUnlocker', {
- msg: 'initWallet',
- data: { wallet_password: state.password, cipher_seed_mnemonic: state.seed }
- })
- }
-
- // Handle recover wallet.
- else if (state.recoveringOldWallet) {
- ipcRenderer.send('walletUnlocker', {
- msg: 'initWallet',
- data: {
- wallet_password: state.password,
- cipher_seed_mnemonic: state.seed,
- recovery_window: 250
- }
- })
- }
-
- // // Handle remote connect.
- // else if (state.startingLnd) {
- // ipcRenderer.send('walletUnlocker', {
- // msg: 'unlockWallet',
- // data: { wallet_password: state.password }
- // })
- // }
-}
-
-/**
- * As soon as we have an active connection to an unlocked wallet, fetch the wallet info so that we have the key data as
- * early as possible.
- */
-export const lndWalletStarted = lndConfig => async dispatch => {
- dispatch(setLndWalletStarted())
-
- // Save the wallet settings.
- const walletId = await db.wallets.put(lndConfig)
-
- // Save the active wallet config.
- await db.settings.put({
- key: 'activeWallet',
- value: walletId
- })
-
- dispatch(fetchInfo())
- dispatch(lndStarted(lndConfig))
- dispatch({ type: ONBOARDING_FINISHED })
-}
-
-// Listener for errors connecting to LND gRPC
-export const startOnboarding = () => async (dispatch, getState) => {
- const state = getState().onboarding
- if (state.stoppingLnd) {
- dispatch(lndStopped())
- }
- dispatch({ type: ONBOARDING_STARTED })
-}
-
-// Listener for when LND creates and sends us a generated seed
-export const receiveSeed = (event, { cipher_seed_mnemonic }) => dispatch => {
- dispatch({ type: SET_SEED, seed: cipher_seed_mnemonic })
- dispatch(stopLnd())
-}
-
-// Listener for when LND throws an error on seed creation
-export const receiveSeedError = (event, error) => dispatch => {
- dispatch({
- type: LOADING_EXISTING_WALLET,
- existingWalletDir: get(error, 'context.lndDataDir')
- })
-}
-
-export const walletCreated = () => dispatch => {
- dispatch({ type: WALLET_UNLOCKED })
- dispatch({ type: ONBOARDING_FINISHED })
- ipcRenderer.send('startLightningWallet')
-}
-
-export const walletUnlocked = () => dispatch => {
- dispatch({ type: WALLET_UNLOCKED })
- dispatch({ type: ONBOARDING_FINISHED })
- ipcRenderer.send('startLightningWallet')
-}
-
-export const setUnlockWalletError = (event, unlockWalletError) => dispatch => {
- dispatch({ type: SET_UNLOCK_WALLET_ERROR, unlockWalletError })
+export const startOnboarding = () => dispatch => {
+ dispatch(onboardingStarted())
}
// ------------------------------------
@@ -437,63 +193,10 @@ const ACTION_HANDLERS = {
[SET_CONNECTION_MACAROON]: (state, { connectionMacaroon }) => ({ ...state, connectionMacaroon }),
[SET_ALIAS]: (state, { alias }) => ({ ...state, alias }),
[SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }),
- [FETCH_SEED]: state => ({ ...state, fetchingSeed: true }),
[SET_SEED]: (state, { seed }) => ({ ...state, seed, fetchingSeed: false }),
[SET_PASSWORD]: (state, { password }) => ({ ...state, password }),
- [SET_LND_WALLET_UNLOCKER_STARTED]: state => ({
- ...state,
- lndWalletUnlockerStarted: true,
- lndWalletStarted: false
- }),
- [SET_LND_WALLET_STARTED]: state => ({
- ...state,
- lndWalletStarted: true,
- lndWalletUnlockerStarted: false
- }),
[ONBOARDING_STARTED]: state => ({ ...state, onboarding: true, onboarded: false }),
[ONBOARDING_FINISHED]: state => ({ ...state, onboarding: false, onboarded: true }),
- [STARTING_LND]: state => ({
- ...state,
- startingLnd: true,
- startLndHostError: '',
- startLndCertError: '',
- startLndMacaroonError: ''
- }),
- [LND_STARTED]: state => ({
- ...state,
- startingLnd: false,
- startLndHostError: '',
- startLndCertError: '',
- startLndMacaroonError: ''
- }),
- [SET_START_LND_ERROR]: (state, { errors }) => ({
- ...state,
- startingLnd: false,
- startLndHostError: errors.host,
- startLndCertError: errors.cert,
- startLndMacaroonError: errors.macaroon
- }),
- [STOPPING_LND]: state => ({
- ...state,
- stoppingLnd: true,
- lndWalletStarted: false,
- lndWalletUnlockerStarted: false
- }),
- [LND_STOPPED]: state => ({ ...state, stoppingLnd: false }),
- [LOADING_EXISTING_WALLET]: (state, { existingWalletDir }) => ({ ...state, existingWalletDir }),
- [CREATING_NEW_WALLET]: state => ({ ...state, creatingNewWallet: true }),
- [RECOVERING_OLD_WALLET]: state => ({ ...state, recoveringOldWallet: true }),
- [UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }),
- [WALLET_UNLOCKED]: state => ({
- ...state,
- unlockingWallet: false,
- unlockWalletError: ''
- }),
- [SET_UNLOCK_WALLET_ERROR]: (state, { unlockWalletError }) => ({
- ...state,
- unlockingWallet: false,
- unlockWalletError
- }),
[VALIDATING_HOST]: (state, { validatingHost }) => ({ ...state, validatingHost }),
[VALIDATING_CERT]: (state, { validatingCert }) => ({ ...state, validatingCert }),
[VALIDATING_MACAROON]: (state, { validatingMacaroon }) => ({ ...state, validatingMacaroon }),
@@ -509,8 +212,6 @@ const connectionStringSelector = state => state.onboarding.connectionString
const connectionTypeSelector = state => state.onboarding.connectionType
-onboardingSelectors.startingLnd = state => state.onboarding.startingLnd
-
onboardingSelectors.connectionStringParamsSelector = createSelector(
connectionStringSelector,
connectionString => {
@@ -535,30 +236,18 @@ export { onboardingSelectors }
const initialState = {
onboarding: false,
onboarded: false,
+ autopilot: true,
+ validatingHost: false,
+ validatingCert: false,
+ validatingMacaroon: false,
connectionType: 'create',
connectionString: '',
connectionHost: '',
connectionCert: '',
connectionMacaroon: '',
alias: '',
- autopilot: true,
password: '',
- startingLnd: false,
- startLndHostError: '',
- startLndCertError: '',
- startLndMacaroonError: '',
- fetchingSeed: false,
- seed: [],
- creatingNewWallet: false,
- recoveringOldWallet: false,
- existingWalletDir: null,
- unlockingWallet: false,
- unlockWalletError: '',
- validatingHost: false,
- validatingCert: false,
- validatingMacaroon: false,
- lndWalletUnlockerStarted: false,
- lndWalletStarted: false
+ seed: []
}
// ------------------------------------
diff --git a/app/reducers/wallet.js b/app/reducers/wallet.js
new file mode 100644
index 00000000..5ecbdce1
--- /dev/null
+++ b/app/reducers/wallet.js
@@ -0,0 +1,132 @@
+import { createSelector } from 'reselect'
+import db from 'store/db'
+
+// ------------------------------------
+// Constants
+// ------------------------------------
+export const SET_WALLETS = 'SET_WALLETS'
+export const SET_ACTIVE_WALLET = 'SET_ACTIVE_WALLET'
+export const SET_IS_WALLET_OPEN = 'SET_IS_WALLET_OPEN'
+export const DELETE_WALLET = 'DELETE_WALLET'
+export const PUT_WALLET = 'PUT_WALLET'
+
+// ------------------------------------
+// Actions
+// ------------------------------------
+
+export function setWallets(wallets) {
+ return {
+ type: SET_WALLETS,
+ wallets
+ }
+}
+
+export function setIsWalletOpen(isWalletOpen) {
+ db.settings.put({
+ key: 'isWalletOpen',
+ value: isWalletOpen
+ })
+ return {
+ type: SET_IS_WALLET_OPEN,
+ isWalletOpen
+ }
+}
+
+export function setActiveWallet(activeWallet) {
+ db.settings.put({
+ key: 'activeWallet',
+ value: activeWallet
+ })
+ return {
+ type: SET_ACTIVE_WALLET,
+ activeWallet
+ }
+}
+
+export const getWallets = () => async dispatch => {
+ let wallets
+ try {
+ wallets = await db.wallets.toArray()
+ } catch (e) {
+ wallets = []
+ }
+ dispatch(setWallets(wallets))
+ return wallets
+}
+
+export const putWallet = wallet => async dispatch => {
+ dispatch({ type: PUT_WALLET, wallet })
+ wallet.id = await db.wallets.put(wallet)
+ await dispatch(getWallets())
+ return wallet
+}
+
+export const deleteWallet = walletId => async dispatch => {
+ dispatch({ type: DELETE_WALLET, walletId })
+ await db.wallets.delete(walletId)
+ const wallets = await dispatch(getWallets())
+ dispatch(setActiveWallet(wallets[0].id))
+ setIsWalletOpen(false)
+}
+
+export const initWallets = () => async dispatch => {
+ let activeWallet
+ let isWalletOpen
+ try {
+ await dispatch(getWallets())
+ activeWallet = await db.settings.get({ key: 'activeWallet' })
+ activeWallet = activeWallet.value || null
+
+ isWalletOpen = await db.settings.get({ key: 'isWalletOpen' })
+ isWalletOpen = isWalletOpen.value
+ } catch (e) {
+ activeWallet = null
+ isWalletOpen = false
+ }
+ dispatch(setIsWalletOpen(isWalletOpen))
+ dispatch(setActiveWallet(activeWallet))
+}
+
+// ------------------------------------
+// Action Handlers
+// ------------------------------------
+const ACTION_HANDLERS = {
+ [SET_WALLETS]: (state, { wallets }) => ({ ...state, wallets }),
+ [SET_ACTIVE_WALLET]: (state, { activeWallet }) => ({ ...state, activeWallet }),
+ [SET_IS_WALLET_OPEN]: (state, { isWalletOpen }) => ({ ...state, isWalletOpen })
+}
+
+// ------------------------------------
+// Selectors
+// ------------------------------------
+
+const walletSelectors = {}
+const activeWalletSelector = state => state.wallet.activeWallet
+const walletsSelector = state => state.wallet.wallets
+
+walletSelectors.wallets = createSelector(walletsSelector, wallets => wallets)
+walletSelectors.activeWallet = createSelector(activeWalletSelector, activeWallet => activeWallet)
+walletSelectors.activeWalletSettings = createSelector(
+ walletsSelector,
+ activeWalletSelector,
+ (wallets, activeWallet) => wallets.find(wallet => wallet.id === activeWallet)
+)
+walletSelectors.hasWallets = createSelector(walletSelectors.wallets, wallets => wallets.length > 0)
+
+export { walletSelectors }
+
+// ------------------------------------
+// Reducer
+// ------------------------------------
+
+const initialState = {
+ isWalletOpen: false,
+ activeWallet: undefined,
+ wallets: []
+}
+
+export default function walletReducer(state = initialState, action) {
+ const handler = ACTION_HANDLERS[action.type]
+
+ return handler ? handler(state, action) : state
+}
diff --git a/app/store/configureStore.prod.js b/app/store/configureStore.prod.js
index 90d85745..df1ed4c5 100644
--- a/app/store/configureStore.prod.js
+++ b/app/store/configureStore.prod.js
@@ -1,11 +1,11 @@
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
-import { createHashHistory } from 'history'
+import { createMemoryHistory } from 'history'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from '../reducers'
import ipc from '../reducers/ipc'
-export const history = createHashHistory({ basename: window.location.pathname })
+export const history = createMemoryHistory({ basename: window.location.pathname })
export function configureStore(initialState) {
const middleware = []
diff --git a/stories/containers/home.stories.js b/stories/containers/home.stories.js
new file mode 100644
index 00000000..8ba4aa4a
--- /dev/null
+++ b/stories/containers/home.stories.js
@@ -0,0 +1,112 @@
+import React from 'react'
+import { storiesOf } from '@storybook/react'
+import { linkTo } from '@storybook/addon-links'
+import { State, Store } from '@sambego/storybook-state'
+import StoryRouter from 'storybook-react-router'
+import { Page } from 'components/UI'
+import { Home } from 'components/Home'
+
+const delay = time => new Promise(resolve => setTimeout(() => resolve(), time))
+
+const store = new Store({
+ activeWallet: 1,
+ lightningGrpcActive: false,
+ walletUnlockerGrpcActive: false,
+ unlockingWallet: false,
+ unlockWalletError: '',
+ wallets: [
+ {
+ id: 1,
+ autopilot: true,
+ autopilotAllocation: 0.6,
+ autopilotMaxchannels: 5,
+ autopilotMaxchansize: 16777215,
+ autopilotMinchansize: 20000,
+ autopilotMinconfs: 0,
+ autopilotPrivate: true,
+ chain: 'bitcoin',
+ network: 'testnet',
+ type: 'local'
+ },
+ {
+ id: 2,
+ autopilot: true,
+ autopilotAllocation: 0.9,
+ autopilotMaxchannels: 10,
+ autopilotMaxchansize: 16777215,
+ autopilotMinchansize: 20000,
+ autopilotMinconfs: 1,
+ autopilotPrivate: false,
+ chain: 'bitcoin',
+ network: 'mainnet',
+ type: 'local'
+ },
+ {
+ id: 3,
+ type: 'custom',
+ chain: 'bitcoin',
+ network: 'testnet',
+ host: 'mynode.local'
+ },
+ {
+ id: 4,
+ alias: 'The Lightning Store',
+ type: 'btcpayserver',
+ chain: 'bitcoin',
+ network: 'testnet',
+ host: 'example.btcpay.store'
+ }
+ ]
+})
+
+const startLnd = async wallet => {
+ console.log('startLnd', wallet)
+ await delay(500)
+ store.set({ walletUnlockerGrpcActive: true, lightningGrpcActive: false })
+}
+const stopLnd = async () => {
+ console.log('stopLnd')
+ await delay(500)
+ store.set({ walletUnlockerGrpcActive: false, lightningGrpcActive: false })
+}
+const unlockWallet = async (wallet, password) => {
+ console.log('unlockWallet', wallet, password)
+ await delay(300)
+ store.set({ walletUnlockerGrpcActive: false, lightningGrpcActive: true })
+}
+const deleteWallet = async walletId => {
+ console.log('deleteWallet', walletId)
+ await delay(200)
+}
+const setUnlockWalletError = async unlockWalletError => store.set({ unlockWalletError })
+const setActiveWallet = async activeWallet => store.set({ activeWallet })
+
+storiesOf('Containers.Home', module)
+ .addParameters({
+ info: {
+ disable: true
+ }
+ })
+ .addDecorator(
+ StoryRouter({
+ '/onboarding': linkTo('Containers.Onboarding', 'Onboarding'),
+ '/syncing': linkTo('Containers.Syncing', 'Syncing'),
+ '/app': linkTo('Containers.App', 'App')
+ })
+ )
+ .add('Home', () => {
+ return (
+
+
+
+
+
+ )
+ })
diff --git a/stories/containers/onboarding.stories.js b/stories/containers/onboarding.stories.js
index 828b8b5f..4d01a077 100644
--- a/stories/containers/onboarding.stories.js
+++ b/stories/containers/onboarding.stories.js
@@ -50,7 +50,7 @@ const resetOnboarding = () => {
store.set(initialValues)
}
-const generateSeed = async () => {
+const fetchSeed = async () => {
await delay(1000)
store.set({
seed: [
@@ -148,7 +148,7 @@ storiesOf('Containers.Onboarding', module)
validateHost={validateHost}
validateCert={validateCert}
validateMacaroon={validateMacaroon}
- generateSeed={generateSeed}
+ fetchSeed={fetchSeed}
/>
)