Browse Source

Merge pull request #350 from LN-Zap/feature/seed

Feature/seed
renovate/lint-staged-8.x
JimmyMow 7 years ago
committed by GitHub
parent
commit
7e4d53149c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      app/components/Onboarding/Autopilot.scss
  2. 24
      app/components/Onboarding/InitWallet.js
  3. 60
      app/components/Onboarding/InitWallet.scss
  4. 49
      app/components/Onboarding/Login.js
  5. 124
      app/components/Onboarding/Login.scss
  6. 45
      app/components/Onboarding/NewAezeedPassword.js
  7. 35
      app/components/Onboarding/NewAezeedPassword.scss
  8. 45
      app/components/Onboarding/NewWalletPassword.js
  9. 35
      app/components/Onboarding/NewWalletPassword.scss
  10. 28
      app/components/Onboarding/NewWalletSeed.js
  11. 49
      app/components/Onboarding/NewWalletSeed.scss
  12. 121
      app/components/Onboarding/Onboarding.js
  13. 38
      app/components/Onboarding/ReEnterSeed.js
  14. 61
      app/components/Onboarding/ReEnterSeed.scss
  15. 43
      app/components/Onboarding/Signup.js
  16. 52
      app/components/Onboarding/Signup.scss
  17. 2
      app/components/Wallet/ReceiveModal.js
  18. 98
      app/containers/Root.js
  19. 1
      app/icons/eye.svg
  20. 153
      app/lnd/config/rpc.proto
  21. 18
      app/lnd/index.js
  22. 148
      app/lnd/lib/rpc.proto
  23. 29
      app/lnd/lib/walletUnlocker.js
  24. 9
      app/lnd/methods/channelController.js
  25. 46
      app/lnd/methods/walletController.js
  26. 24
      app/lnd/walletUnlockerMethods/index.js
  27. 35
      app/main.dev.js
  28. 18
      app/reducers/ipc.js
  29. 231
      app/reducers/onboarding.js
  30. 148
      app/rpc.proto

16
app/components/Onboarding/Autopilot.scss

@ -9,28 +9,28 @@
&.enable {
&.active {
div {
color: $green;
border-color: $green;
color: $gold;
border-color: $gold;
}
}
div:hover {
color: $green;
border-color: $green;
color: $gold;
border-color: $gold;
}
}
&.disable {
&.active {
div {
color: $red;
border-color: $red;
color: $gold;
border-color: $gold;
}
}
div:hover {
color: $red;
border-color: $red;
color: $gold;
border-color: $gold;
}
}

24
app/components/Onboarding/InitWallet.js

@ -0,0 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import Login from './Login'
import Signup from './Signup'
import styles from './InitWallet.scss'
const InitWallet = ({ hasSeed, loginProps, signupProps }) => (
<div className={styles.container}>
{
hasSeed ?
<Login {...loginProps} />
:
<Signup {...signupProps} />
}
</div>
)
InitWallet.propTypes = {
hasSeed: PropTypes.bool.isRequired,
loginProps: PropTypes.object.isRequired,
signupProps: PropTypes.object.isRequired
}
export default InitWallet

60
app/components/Onboarding/InitWallet.scss

@ -0,0 +1,60 @@
@import '../../variables.scss';
.container {
position: relative;
}
.password {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 22px;
}
.password::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.buttons {
margin-top: 15%;
text-align: center;
div {
color: $white;
&:nth-child(1) {
text-align: center;
margin-bottom: 40px;
span {
padding: 15px 35px;
background: $darkspaceblue;
font-size: 14px;
opacity: 0.5;
transition: all 0.25s;
&.active {
opacity: 1.0;
cursor: pointer;
&:hover {
background: lighten($darkspaceblue, 10%);
}
}
}
}
&:nth-child(2), &:nth-child(3) {
font-size: 12px;
cursor: pointer;
margin: 10px 0;
&:hover {
text-decoration: underline;
}
}
}
}

49
app/components/Onboarding/Login.js

@ -0,0 +1,49 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Login.scss'
const Login = ({
password,
updatePassword,
unlockingWallet,
unlockWallet,
unlockWalletError
}) => (
<div className={styles.container}>
<input
type='password'
placeholder='Password'
className={`${styles.password} ${unlockWalletError.isError && styles.inputError}`}
ref={input => input && input.focus()}
value={password}
onChange={event => updatePassword(event.target.value)}
/>
<p className={`${unlockWalletError.isError && styles.active} ${styles.error}`}>
{unlockWalletError.message}
</p>
<section className={styles.buttons}>
<div>
<span className={`${!unlockingWallet && styles.active} ${styles.button}`} onClick={() => unlockWallet(password)}>
{
unlockingWallet ?
<i className={styles.spinner} />
:
'Log In'
}
</span>
</div>
<div>Recover existing wallet</div>
</section>
</div>
)
Login.propTypes = {
password: PropTypes.string.isRequired,
updatePassword: PropTypes.func.isRequired,
unlockingWallet: PropTypes.bool.isRequired,
unlockWallet: PropTypes.func.isRequired,
unlockWalletError: PropTypes.object.isRequired
}
export default Login

124
app/components/Onboarding/Login.scss

@ -0,0 +1,124 @@
@import '../../variables.scss';
.container {
position: relative;
}
.password {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 22px;
border-bottom: 1px solid transparent;
transition: all 0.25s;
&.inputError {
border-bottom: 1px solid $red;
}
}
.password::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.error {
margin-top: 20px;
color: $red;
visibility: hidden;
font-size: 12px;
transition: all 0.25s;
&.active {
visibility: visible;
}
}
.buttons {
margin-top: 15%;
text-align: center;
div {
color: $white;
&:nth-child(1) {
text-align: center;
margin-bottom: 40px;
span {
padding: 15px 35px;
background: $darkspaceblue;
font-size: 14px;
opacity: 0.5;
transition: all 0.25s;
&.button {
position: relative;
}
&.active {
opacity: 1.0;
cursor: pointer;
&:hover {
background: lighten($darkspaceblue, 10%);
}
}
}
}
&:nth-child(2), &:nth-child(3) {
font-size: 12px;
cursor: pointer;
margin: 10px 0;
&:hover {
text-decoration: underline;
}
}
}
}
.spinner {
height: 20px;
width: 20px;
border: 1px solid rgba(235, 184, 100, 0.1);
border-left-color: rgba(235, 184, 100, 0.4);
-webkit-border-radius: 999px;
-moz-border-radius: 999px;
border-radius: 999px;
-webkit-animation: animation-rotate 1000ms linear infinite;
-moz-animation: animation-rotate 1000ms linear infinite;
-o-animation: animation-rotate 1000ms linear infinite;
animation: animation-rotate 1000ms linear infinite;
display: inline-block;
position: absolute;
top: calc(50% - 10px);
left: calc(50% - 10px);
}
@-webkit-keyframes animation-rotate {
100% {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes animation-rotate {
100% {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes animation-rotate {
100% {
-o-transform: rotate(360deg);
}
}
@keyframes animation-rotate {
100% {
transform: rotate(360deg);
}
}

45
app/components/Onboarding/NewAezeedPassword.js

@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './NewAezeedPassword.scss'
const NewAezeedPassword = ({
aezeedPassword,
aezeedPasswordConfirmation,
showAezeedPasswordConfirmationError,
updateAezeedPassword,
updateAezeedPasswordConfirmation
}) => (
<div className={styles.container}>
<section className={styles.input}>
<input
type='password'
placeholder='Password'
className={styles.password}
value={aezeedPassword}
onChange={event => updateAezeedPassword(event.target.value)}
/>
</section>
<section className={styles.input}>
<input
type='password'
placeholder='Confirm Password'
className={`${styles.password} ${showAezeedPasswordConfirmationError && styles.error}`}
value={aezeedPasswordConfirmation}
onChange={event => updateAezeedPasswordConfirmation(event.target.value)}
/>
<p className={`${styles.errorMessage} ${showAezeedPasswordConfirmationError && styles.visible}`}>Passwords do not match</p>
</section>
</div>
)
NewAezeedPassword.propTypes = {
aezeedPassword: PropTypes.string.isRequired,
aezeedPasswordConfirmation: PropTypes.string.isRequired,
showAezeedPasswordConfirmationError: PropTypes.bool.isRequired,
updateAezeedPassword: PropTypes.func.isRequired,
updateAezeedPasswordConfirmation: PropTypes.func.isRequired
}
export default NewAezeedPassword

35
app/components/Onboarding/NewAezeedPassword.scss

@ -0,0 +1,35 @@
@import '../../variables.scss';
.input:nth-child(2) {
margin-top: 40px;
}
.password {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 22px;
transition: all 0.25s;
&.error {
border-bottom: 1px solid $red;
}
}
.password::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.errorMessage {
color: $red;
margin-top: 10px;
font-size: 10px;
visibility: hidden;
&.visible {
visibility: visible;
}
}

45
app/components/Onboarding/NewWalletPassword.js

@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './NewWalletPassword.scss'
const NewWalletPassword = ({
createWalletPassword,
createWalletPasswordConfirmation,
showCreateWalletPasswordConfirmationError,
updateCreateWalletPassword,
updateCreateWalletPasswordConfirmation
}) => (
<div className={styles.container}>
<section className={styles.input}>
<input
type='password'
placeholder='Password'
className={styles.password}
value={createWalletPassword}
onChange={event => updateCreateWalletPassword(event.target.value)}
/>
</section>
<section className={styles.input}>
<input
type='password'
placeholder='Confirm Password'
className={`${styles.password} ${showCreateWalletPasswordConfirmationError && styles.error}`}
value={createWalletPasswordConfirmation}
onChange={event => updateCreateWalletPasswordConfirmation(event.target.value)}
/>
<p className={`${styles.errorMessage} ${showCreateWalletPasswordConfirmationError && styles.visible}`}>Passwords do not match</p>
</section>
</div>
)
NewWalletPassword.propTypes = {
createWalletPassword: PropTypes.string.isRequired,
createWalletPasswordConfirmation: PropTypes.string.isRequired,
showCreateWalletPasswordConfirmationError: PropTypes.bool.isRequired,
updateCreateWalletPassword: PropTypes.func.isRequired,
updateCreateWalletPasswordConfirmation: PropTypes.func.isRequired
}
export default NewWalletPassword

35
app/components/Onboarding/NewWalletPassword.scss

@ -0,0 +1,35 @@
@import '../../variables.scss';
.input:nth-child(2) {
margin-top: 40px;
}
.password {
background: transparent;
outline: none;
border: 0;
color: $gold;
-webkit-text-fill-color: $white;
font-size: 22px;
transition: all 0.25s;
&.error {
border-bottom: 1px solid $red;
}
}
.password::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.errorMessage {
color: $red;
margin-top: 10px;
font-size: 10px;
visibility: hidden;
&.visible {
visibility: visible;
}
}

28
app/components/Onboarding/NewWalletSeed.js

@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './NewWalletSeed.scss'
const NewWalletSeed = ({ seed }) => (
<div className={styles.container}>
<ul className={styles.seedContainer}>
{
seed.map((word, index) => (
<li key={index}>
<section>
<label htmlFor={word}>{index + 1}</label>
</section>
<section>
<span>{word}</span>
</section>
</li>
))
}
</ul>
</div>
)
NewWalletSeed.propTypes = {
seed: PropTypes.array.isRequired
}
export default NewWalletSeed

49
app/components/Onboarding/NewWalletSeed.scss

@ -0,0 +1,49 @@
@import '../../variables.scss';
.container {
font-size: 14px;
color: $white;
font-family: 'Roboto';
letter-spacing: 1.5px;
li {
display: inline-block;
margin: 5px 0;
width: 25%;
font-family: 'Courier';
section {
display: inline-block;
vertical-align: middle;
color: $white;
&:nth-child(1) {
width: 15%;
text-align: center;
opacity: 0.5;
}
&:nth-child(2) {
width: calc(85% - 10px);
margin: 0 5px;
}
}
}
.word {
margin: 0 3px;
background-color: #1c1e26;
outline: 0;
border: none;
padding: 10px;
color: $white;
&.valid {
color: $green;
}
&.invalid {
color: $red;
}
}
}

121
app/components/Onboarding/Onboarding.js

@ -6,25 +6,42 @@ import LoadingBolt from 'components/LoadingBolt'
import FormContainer from './FormContainer'
import Alias from './Alias'
import Autopilot from './Autopilot'
import Login from './Login'
import Signup from './Signup'
import NewWalletSeed from './NewWalletSeed'
import ReEnterSeed from './ReEnterSeed'
import NewWalletPassword from './NewWalletPassword'
import NewAezeedPassword from './NewAezeedPassword'
import styles from './Onboarding.scss'
const Onboarding = ({
onboarding: {
step,
alias,
autopilot
autopilot,
startingLnd,
createWalletPassword,
seed,
aezeedPassword,
fetchingSeed
},
changeStep,
submit,
startLnd,
submitNewWallet,
aliasProps,
autopilotProps
initWalletProps,
autopilotProps,
newWalletSeedProps,
newWalletPasswordProps,
newAezeedPasswordProps,
reEnterSeedProps
}) => {
const renderStep = () => {
switch (step) {
case 1:
return (
<FormContainer
title='1. 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'
back={null}
next={() => changeStep(2)}
@ -35,19 +52,103 @@ const Onboarding = ({
case 2:
return (
<FormContainer
title='2. 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
back={() => changeStep(1)}
next={() => submit(alias, autopilot)}
next={() => startLnd(alias, autopilot)}
>
<Autopilot {...autopilotProps} />
</FormContainer>
)
case 3:
return (
<FormContainer
title='Welcome back!'
description='Enter your wallet password or create a new wallet' // eslint-disable-line
back={null}
next={null}
>
<Login {...initWalletProps.loginProps} />
</FormContainer>
)
case 4:
return (
<FormContainer
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
back={null}
next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password
if (newWalletPasswordProps.showCreateWalletPasswordConfirmationError) { return }
changeStep(5)
}}
>
<NewWalletPassword {...newWalletPasswordProps} />
</FormContainer>
)
case 5:
return (
<FormContainer
title={'Alright, let\'s get set up'}
description='Would you like to create a new wallet or import an existing one?' // eslint-disable-line
back={() => changeStep(4)}
next={() => (initWalletProps.signupProps.signupForm.create ? changeStep(6) : console.log('import'))}
>
<Signup {...initWalletProps.signupProps} />
</FormContainer>
)
case 6:
return (
<FormContainer
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
back={() => changeStep(5)}
next={() => changeStep(7)}
>
<NewWalletSeed {...newWalletSeedProps} />
</FormContainer>
)
case 7:
return (
<FormContainer
title='Re-enter your seed'
description='Yeah I know, might be annoying, but just to be safe!' // eslint-disable-line
back={() => changeStep(6)}
next={() => {
// don't allow them to move on if they havent re-entered the seed correctly
if (!reEnterSeedProps.reEnterSeedChecker) { return }
changeStep(8)
}}
>
<ReEnterSeed {...reEnterSeedProps} />
</FormContainer>
)
case 8:
return (
<FormContainer
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
back={() => changeStep(6)}
next={() => {
// dont allow the user to move on if the confirmation password doesnt match the original password
if (newAezeedPasswordProps.showAezeedPasswordConfirmationError) { return }
submitNewWallet(createWalletPassword, seed, aezeedPassword)
}}
>
<NewAezeedPassword {...newAezeedPasswordProps} />
</FormContainer>
)
default:
return <LoadingBolt />
}
}
if (startingLnd) { return <LoadingBolt /> }
if (fetchingSeed) { return <LoadingBolt /> }
return (
<div className={styles.container}>
{renderStep()}
@ -59,8 +160,14 @@ Onboarding.propTypes = {
onboarding: PropTypes.object.isRequired,
aliasProps: PropTypes.object.isRequired,
autopilotProps: PropTypes.object.isRequired,
initWalletProps: PropTypes.object.isRequired,
newWalletSeedProps: PropTypes.object.isRequired,
newWalletPasswordProps: PropTypes.object.isRequired,
newAezeedPasswordProps: PropTypes.object.isRequired,
reEnterSeedProps: PropTypes.object.isRequired,
changeStep: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired
startLnd: PropTypes.func.isRequired,
submitNewWallet: PropTypes.func.isRequired
}
export default Onboarding

38
app/components/Onboarding/ReEnterSeed.js

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

61
app/components/Onboarding/ReEnterSeed.scss

@ -0,0 +1,61 @@
@import '../../variables.scss';
.seedContainer {
position: relative;
display: inline-block;
font-size: 12px;
li {
display: inline-block;
margin: 5px 0;
width: 25%;
section {
display: inline-block;
vertical-align: middle;
color: $white;
&:nth-child(1) {
width: 15%;
text-align: center;
opacity: 0.5;
}
&:nth-child(2) {
width: calc(85% - 10px);
margin: 0 5px;
}
}
}
}
.word {
margin: 0 3px;
background-color: #1c1e26;
outline: 0;
border: none;
padding: 5px 10px;
color: $white;
font-family: courier;
font-family: 'Courier';
&.valid {
color: $green;
}
&.invalid {
color: $red;
}
}
.word::-webkit-input-placeholder {
text-shadow: none;
-webkit-text-fill-color: initial;
}
.contentEditable {
width: 100px;
background: red;
}

43
app/components/Onboarding/Signup.js

@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FaCircle, FaCircleThin } from 'react-icons/lib/fa'
import styles from './Signup.scss'
const Signup = ({ signupForm, setSignupCreate, setSignupImport }) => (
<div className={styles.container}>
<section className={`${styles.enable} ${signupForm.create && styles.active}`}>
<div onClick={setSignupCreate}>
{
signupForm.create ?
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Create new wallet
</span>
</div>
</section>
<section className={`${styles.disable} ${signupForm.import && styles.active}`}>
<div onClick={setSignupImport}>
{
signupForm.import ?
<FaCircle />
:
<FaCircleThin />
}
<span className={styles.label}>
Import existing wallet
</span>
</div>
</section>
</div>
)
Signup.propTypes = {
signupForm: PropTypes.object.isRequired,
setSignupCreate: PropTypes.func.isRequired,
setSignupImport: PropTypes.func.isRequired
}
export default Signup

52
app/components/Onboarding/Signup.scss

@ -0,0 +1,52 @@
@import '../../variables.scss';
.container {
color: $white;
section {
margin: 20px 0;
&.enable {
&.active {
div {
color: $gold;
border-color: $gold;
}
}
div:hover {
color: $gold;
border-color: $gold;
}
}
&.disable {
&.active {
div {
color: $gold;
border-color: $gold;
}
}
div:hover {
color: $gold;
border-color: $gold;
}
}
div {
width: 185px;
display: inline-block;
padding: 20px;
border: 1px solid $white;
border-radius: 5px;
font-weight: 200;
cursor: pointer;
transition: all 0.25s;
}
.label {
margin-left: 15px;
}
}
}

2
app/components/Wallet/ReceiveModal.js

@ -103,7 +103,7 @@ class ReceiveModal extends React.Component {
ReceiveModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
pubkey: PropTypes.string.isRequired,
pubkey: PropTypes.string,
address: PropTypes.string.isRequired,
closeReceiveModal: PropTypes.func.isRequired
}

98
app/containers/Root.js

@ -7,15 +7,43 @@ import PropTypes from 'prop-types'
import LoadingBolt from '../components/LoadingBolt'
import Onboarding from '../components/Onboarding'
import Syncing from '../components/Onboarding/Syncing'
import { updateAlias, setAutopilot, changeStep, submit } from '../reducers/onboarding'
import {
updateAlias,
updatePassword,
setAutopilot,
changeStep,
startLnd,
createWallet,
updateCreateWalletPassword,
updateCreateWalletPasswordConfirmation,
updateAezeedPassword,
updateAezeedPasswordConfirmation,
submitNewWallet,
onboardingSelectors,
unlockWallet,
setSignupCreate,
setSignupImport,
updateSeedInput
} from '../reducers/onboarding'
import { fetchBlockHeight, lndSelectors } from '../reducers/lnd'
import Routes from '../routes'
const mapDispatchToProps = {
updateAlias,
updatePassword,
updateCreateWalletPassword,
updateCreateWalletPasswordConfirmation,
updateAezeedPassword,
updateAezeedPasswordConfirmation,
setAutopilot,
changeStep,
submit,
startLnd,
createWallet,
submitNewWallet,
unlockWallet,
setSignupCreate,
setSignupImport,
updateSeedInput,
fetchBlockHeight
}
@ -24,7 +52,11 @@ const mapStateToProps = state => ({
lnd: state.lnd,
onboarding: state.onboarding,
syncPercentage: lndSelectors.syncPercentage(state)
syncPercentage: lndSelectors.syncPercentage(state),
passwordIsValid: onboardingSelectors.passwordIsValid(state),
showCreateWalletPasswordConfirmationError: onboardingSelectors.showCreateWalletPasswordConfirmationError(state),
showAezeedPasswordConfirmationError: onboardingSelectors.showAezeedPasswordConfirmationError(state),
reEnterSeedChecker: onboardingSelectors.reEnterSeedChecker(state)
})
const mergeProps = (stateProps, dispatchProps, ownProps) => {
@ -43,12 +75,68 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
setAutopilot: dispatchProps.setAutopilot
}
const initWalletProps = {
hasSeed: stateProps.onboarding.hasSeed,
loginProps: {
password: stateProps.onboarding.password,
passwordIsValid: stateProps.passwordIsValid,
hasSeed: stateProps.onboarding.hasSeed,
unlockingWallet: stateProps.onboarding.unlockingWallet,
unlockWalletError: stateProps.onboarding.unlockWalletError,
updatePassword: dispatchProps.updatePassword,
createWallet: dispatchProps.createWallet,
unlockWallet: dispatchProps.unlockWallet
},
signupProps: {
signupForm: stateProps.onboarding.signupForm,
setSignupCreate: dispatchProps.setSignupCreate,
setSignupImport: dispatchProps.setSignupImport
}
}
const newWalletSeedProps = {
seed: stateProps.onboarding.seed
}
const newWalletPasswordProps = {
createWalletPassword: stateProps.onboarding.createWalletPassword,
createWalletPasswordConfirmation: stateProps.onboarding.createWalletPasswordConfirmation,
showCreateWalletPasswordConfirmationError: stateProps.showCreateWalletPasswordConfirmationError,
updateCreateWalletPassword: dispatchProps.updateCreateWalletPassword,
updateCreateWalletPasswordConfirmation: dispatchProps.updateCreateWalletPasswordConfirmation
}
const newAezeedPasswordProps = {
aezeedPassword: stateProps.onboarding.aezeedPassword,
aezeedPasswordConfirmation: stateProps.onboarding.updateAezeedPasswordConfirmation,
showAezeedPasswordConfirmationError: stateProps.showAezeedPasswordConfirmationError,
updateAezeedPassword: dispatchProps.updateAezeedPassword,
updateAezeedPasswordConfirmation: dispatchProps.updateAezeedPasswordConfirmation
}
const reEnterSeedProps = {
seed: stateProps.onboarding.seed,
seedInput: stateProps.onboarding.seedInput,
reEnterSeedChecker: stateProps.reEnterSeedChecker,
updateSeedInput: dispatchProps.updateSeedInput
}
const onboardingProps = {
onboarding: stateProps.onboarding,
changeStep: dispatchProps.changeStep,
submit: dispatchProps.submit,
startLnd: dispatchProps.startLnd,
submitNewWallet: dispatchProps.submitNewWallet,
aliasProps,
autopilotProps
autopilotProps,
initWalletProps,
newWalletSeedProps,
newWalletPasswordProps,
newAezeedPasswordProps,
reEnterSeedProps
}
return {

1
app/icons/eye.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>

After

Width:  |  Height:  |  Size: 316 B

153
app/lnd/config/rpc.proto

@ -1,6 +1,6 @@
syntax = "proto3";
import "google/api/annotations.proto";
// import "google/api/annotations.proto";
package lnrpc;
/**
@ -28,13 +28,39 @@ package lnrpc;
// The WalletUnlocker service is used to set up a wallet password for
// lnd at first startup, and unlock a previously set up wallet.
service WalletUnlocker {
/** lncli: `create`
CreateWallet is used at lnd startup to set the encryption password for
the wallet database.
/**
GenSeed is the first method that should be used to instantiate a new lnd
instance. This method allows a caller to generate a new aezeed cipher seed
given an optional passphrase. If provided, the passphrase will be necessary
to decrypt the cipherseed to expose the internal wallet seed.
Once the cipherseed is obtained and verified by the user, the InitWallet
method should be used to commit the newly generated seed, and create the
wallet.
*/
rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) {
rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) {
option (google.api.http) = {
post: "/v1/createwallet"
get: "/v1/genseed"
};
}
/** lncli: `init`
InitWallet is used when lnd is starting up for the first time to fully
initialize the daemon and its internal wallet. At the very least a wallet
password must be provided. This will be used to encrypt sensitive material
on disk.
In the case of a recovery scenario, the user can also specify their aezeed
mnemonic and passphrase. If set, then the daemon will use this prior state
to initialize its internal wallet.
Alternatively, this can be used along with the GenSeed RPC to obtain a
seed, then present it to the user. Once it has been verified by the user,
the seed can be fed into this RPC in order to commit the new wallet.
*/
rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) {
option (google.api.http) = {
post: "/v1/initwallet"
body: "*"
};
}
@ -51,20 +77,74 @@ service WalletUnlocker {
}
}
message CreateWalletRequest {
bytes password = 1;
message GenSeedRequest {
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 1;
/**
seed_entropy is an optional 16-bytes generated via CSPRNG. If not
specified, then a fresh set of randomness will be used to create the seed.
*/
bytes seed_entropy = 2;
}
message CreateWalletResponse {}
message GenSeedResponse {
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This field is optional, as if not
provided, then the daemon will generate a new cipher seed for the user.
Otherwise, then the daemon will attempt to recover the wallet state linked
to this cipher seed.
*/
repeated string cipher_seed_mnemonic = 1;
/**
enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
cipher text before run through our mnemonic encoding scheme.
*/
bytes enciphered_seed = 2;
}
message InitWalletRequest {
/**
wallet_password is the passphrase that should be used to encrypt the
wallet. This MUST be at least 8 chars in length. After creation, this
password is required to unlock the daemon.
*/
bytes wallet_password = 1;
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This may have been generated by the
GenSeed method, or be an existing seed.
*/
repeated string cipher_seed_mnemonic = 2;
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 3;
}
message InitWalletResponse {
}
message UnlockWalletRequest {
bytes password = 1;
/**
wallet_password should be the current valid passphrase for the daemon. This
will be required to decrypt on-disk material that the daemon requires to
function properly.
*/
bytes wallet_password = 1;
}
message UnlockWalletResponse {}
service Lightning {
/** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
by the wallet. This method can be modified by having the request specify
only witness outputs should be factored into the final output sum.
*/
@ -251,7 +331,7 @@ service Lightning {
*/
rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) {
option (google.api.http) = {
delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}"
delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}"
};
}
@ -294,18 +374,18 @@ service Lightning {
*/
rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) {
option (google.api.http) = {
get: "/v1/invoices/{pending_only}"
get: "/v1/invoices"
};
}
/** lncli: `lookupinvoice`
LookupInvoice attemps to look up an invoice according to its payment hash.
LookupInvoice attempts to look up an invoice according to its payment hash.
The passed payment hash *must* be exactly 32 bytes, if not, an error is
returned.
*/
rpc LookupInvoice (PaymentHash) returns (Invoice) {
option (google.api.http) = {
get: "/v1/invoices/{r_hash_str}"
get: "/v1/invoice/{r_hash_str}"
};
}
@ -389,7 +469,7 @@ service Lightning {
route to a target destination capable of carrying a specific amount of
satoshis. The retuned route contains the full details required to craft and
send an HTLC, also including the necessary information that should be
present within the Sphinx packet encapsualted within the HTLC.
present within the Sphinx packet encapsulated within the HTLC.
*/
rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) {
option (google.api.http) = {
@ -447,7 +527,7 @@ service Lightning {
*/
rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) {
option (google.api.http) = {
post: "/v1/fees"
post: "/v1/chanpolicy"
body: "*"
};
}
@ -518,16 +598,16 @@ message SendResponse {
}
message ChannelPoint {
// TODO(roasbeef): make str vs bytes into a oneof
oneof funding_txid {
/// Txid of the funding transaction
bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"];
/// Txid of the funding transaction
bytes funding_txid = 1 [ json_name = "funding_txid" ];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [ json_name = "funding_txid_str" ];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [json_name = "funding_txid_str"];
}
/// The index of the output of the funding transaction
uint32 output_index = 3 [ json_name = "output_index" ];
uint32 output_index = 3 [json_name = "output_index"];
}
message LightningAddress {
@ -610,7 +690,7 @@ message VerifyMessageRequest {
/// The message over which the signature is to be verified
bytes msg = 1 [ json_name = "msg" ];
/// The signature to be verifed over the given message
/// The signature to be verified over the given message
string signature = 2 [ json_name = "signature" ];
}
message VerifyMessageResponse {
@ -630,8 +710,6 @@ message ConnectPeerRequest {
bool perm = 2;
}
message ConnectPeerResponse {
/// The id of the newly connected peer
int32 peer_id = 1 [json_name = "peer_id"];
}
message DisconnectPeerRequest {
@ -738,9 +816,6 @@ message Peer {
/// The identity pubkey of the peer
string pub_key = 1 [json_name = "pub_key"];
/// The peer's id from the local point of view
int32 peer_id = 2 [json_name = "peer_id"];
/// Network address of the peer; eg `127.0.0.1:10011`
string address = 3 [json_name = "address"];
@ -806,6 +881,9 @@ message GetInfoResponse {
/// The URIs of the current node.
repeated string uris = 12 [json_name = "uris"];
/// Timestamp of the block best known to the wallet
int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ];
}
message ConfirmationUpdate {
@ -840,8 +918,9 @@ message CloseChannelRequest {
int32 target_conf = 3;
/// A manual fee rate set in sat/byte that should be used when crafting the closure transaction.
int64 sat_per_byte = 5;
int64 sat_per_byte = 4;
}
message CloseStatusUpdate {
oneof update {
PendingUpdate close_pending = 1 [json_name = "close_pending"];
@ -857,13 +936,10 @@ message PendingUpdate {
message OpenChannelRequest {
/// The peer_id of the node to open a channel with
int32 target_peer_id = 1 [json_name = "target_peer_id"];
/// The pubkey of the node to open a channel with
bytes node_pubkey = 2 [json_name = "node_pubkey"];
/// The hex encorded pubkey of the node to open a channel with
/// The hex encoded pubkey of the node to open a channel with
string node_pubkey_string = 3 [json_name = "node_pubkey_string"];
/// The number of satoshis the wallet should commit to the channel
@ -1031,6 +1107,9 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
int64 amt = 2;
/// The max number of routes to return.
int32 num_routes = 3;
}
message QueryRoutesResponse {
repeated Route routes = 1 [ json_name = "routes"];
@ -1337,6 +1416,7 @@ message InvoiceSubscription {
message Payment {
/// The payment hash
string payment_hash = 1 [json_name = "payment_hash"];
/// The value of the payment in satoshis
int64 value = 2 [json_name = "value"];
@ -1348,6 +1428,9 @@ message Payment {
/// The fee paid for this payment in satoshis
int64 fee = 5 [json_name = "fee"];
/// The payment preimage
string payment_preimage = 6 [json_name = "payment_preimage"];
}
message ListPaymentsRequest {

18
app/lnd/index.js

@ -2,10 +2,12 @@ import grpc from 'grpc'
import fs from 'fs'
import config from './config'
import lightning from './lib/lightning'
import walletUnlocker from './lib/walletUnlocker'
import subscribe from './subscribe'
import methods from './methods'
import walletUnlockerMethods from './walletUnlockerMethods'
export default (callback) => {
const initLnd = (callback) => {
const macaroonFile = fs.readFileSync(config.macaroon)
const meta = new grpc.Metadata()
meta.add('macaroon', macaroonFile.toString('hex'))
@ -14,5 +16,19 @@ export default (callback) => {
const lndSubscribe = mainWindow => subscribe(mainWindow, lnd, meta)
const lndMethods = (event, msg, data) => methods(lnd, meta, event, msg, data)
callback(lndSubscribe, lndMethods)
}
const initWalletUnlocker = (callback) => {
const walletUnlockerObj = walletUnlocker(config.lightningRpc, config.lightningHost)
const walletUnlockerMethodsCallback = (event, msg, data) => walletUnlockerMethods(walletUnlockerObj, event, msg, data)
callback(walletUnlockerMethodsCallback)
}
export default {
initLnd,
initWalletUnlocker
}

148
app/lnd/lib/rpc.proto

@ -28,13 +28,39 @@ package lnrpc;
// The WalletUnlocker service is used to set up a wallet password for
// lnd at first startup, and unlock a previously set up wallet.
service WalletUnlocker {
/** lncli: `create`
CreateWallet is used at lnd startup to set the encryption password for
the wallet database.
/**
GenSeed is the first method that should be used to instantiate a new lnd
instance. This method allows a caller to generate a new aezeed cipher seed
given an optional passphrase. If provided, the passphrase will be necessary
to decrypt the cipherseed to expose the internal wallet seed.
Once the cipherseed is obtained and verified by the user, the InitWallet
method should be used to commit the newly generated seed, and create the
wallet.
*/
rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) {
rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) {
option (google.api.http) = {
post: "/v1/createwallet"
get: "/v1/genseed"
};
}
/** lncli: `init`
InitWallet is used when lnd is starting up for the first time to fully
initialize the daemon and its internal wallet. At the very least a wallet
password must be provided. This will be used to encrypt sensitive material
on disk.
In the case of a recovery scenario, the user can also specify their aezeed
mnemonic and passphrase. If set, then the daemon will use this prior state
to initialize its internal wallet.
Alternatively, this can be used along with the GenSeed RPC to obtain a
seed, then present it to the user. Once it has been verified by the user,
the seed can be fed into this RPC in order to commit the new wallet.
*/
rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) {
option (google.api.http) = {
post: "/v1/initwallet"
body: "*"
};
}
@ -51,20 +77,74 @@ service WalletUnlocker {
}
}
message CreateWalletRequest {
bytes password = 1;
message GenSeedRequest {
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 1;
/**
seed_entropy is an optional 16-bytes generated via CSPRNG. If not
specified, then a fresh set of randomness will be used to create the seed.
*/
bytes seed_entropy = 2;
}
message GenSeedResponse {
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This field is optional, as if not
provided, then the daemon will generate a new cipher seed for the user.
Otherwise, then the daemon will attempt to recover the wallet state linked
to this cipher seed.
*/
repeated string cipher_seed_mnemonic = 1;
/**
enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
cipher text before run through our mnemonic encoding scheme.
*/
bytes enciphered_seed = 2;
}
message CreateWalletResponse {}
message InitWalletRequest {
/**
wallet_password is the passphrase that should be used to encrypt the
wallet. This MUST be at least 8 chars in length. After creation, this
password is required to unlock the daemon.
*/
bytes wallet_password = 1;
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This may have been generated by the
GenSeed method, or be an existing seed.
*/
repeated string cipher_seed_mnemonic = 2;
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 3;
}
message InitWalletResponse {
}
message UnlockWalletRequest {
bytes password = 1;
/**
wallet_password should be the current valid passphrase for the daemon. This
will be required to decrypt on-disk material that the daemon requires to
function properly.
*/
bytes wallet_password = 1;
}
message UnlockWalletResponse {}
service Lightning {
/** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
by the wallet. This method can be modified by having the request specify
only witness outputs should be factored into the final output sum.
*/
@ -251,7 +331,7 @@ service Lightning {
*/
rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) {
option (google.api.http) = {
delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}"
delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}"
};
}
@ -294,18 +374,18 @@ service Lightning {
*/
rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) {
option (google.api.http) = {
get: "/v1/invoices/{pending_only}"
get: "/v1/invoices"
};
}
/** lncli: `lookupinvoice`
LookupInvoice attemps to look up an invoice according to its payment hash.
LookupInvoice attempts to look up an invoice according to its payment hash.
The passed payment hash *must* be exactly 32 bytes, if not, an error is
returned.
*/
rpc LookupInvoice (PaymentHash) returns (Invoice) {
option (google.api.http) = {
get: "/v1/invoices/{r_hash_str}"
get: "/v1/invoice/{r_hash_str}"
};
}
@ -389,7 +469,7 @@ service Lightning {
route to a target destination capable of carrying a specific amount of
satoshis. The retuned route contains the full details required to craft and
send an HTLC, also including the necessary information that should be
present within the Sphinx packet encapsualted within the HTLC.
present within the Sphinx packet encapsulated within the HTLC.
*/
rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) {
option (google.api.http) = {
@ -447,7 +527,7 @@ service Lightning {
*/
rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) {
option (google.api.http) = {
post: "/v1/fees"
post: "/v1/chanpolicy"
body: "*"
};
}
@ -518,16 +598,16 @@ message SendResponse {
}
message ChannelPoint {
// TODO(roasbeef): make str vs bytes into a oneof
/// Txid of the funding transaction
bytes funding_txid = 1 [ json_name = "funding_txid" ];
oneof funding_txid {
/// Txid of the funding transaction
bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [ json_name = "funding_txid_str" ];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [json_name = "funding_txid_str"];
}
/// The index of the output of the funding transaction
uint32 output_index = 3 [ json_name = "output_index" ];
uint32 output_index = 3 [json_name = "output_index"];
}
message LightningAddress {
@ -610,7 +690,7 @@ message VerifyMessageRequest {
/// The message over which the signature is to be verified
bytes msg = 1 [ json_name = "msg" ];
/// The signature to be verifed over the given message
/// The signature to be verified over the given message
string signature = 2 [ json_name = "signature" ];
}
message VerifyMessageResponse {
@ -630,8 +710,6 @@ message ConnectPeerRequest {
bool perm = 2;
}
message ConnectPeerResponse {
/// The id of the newly connected peer
int32 peer_id = 1 [json_name = "peer_id"];
}
message DisconnectPeerRequest {
@ -738,9 +816,6 @@ message Peer {
/// The identity pubkey of the peer
string pub_key = 1 [json_name = "pub_key"];
/// The peer's id from the local point of view
int32 peer_id = 2 [json_name = "peer_id"];
/// Network address of the peer; eg `127.0.0.1:10011`
string address = 3 [json_name = "address"];
@ -806,6 +881,9 @@ message GetInfoResponse {
/// The URIs of the current node.
repeated string uris = 12 [json_name = "uris"];
/// Timestamp of the block best known to the wallet
int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ];
}
message ConfirmationUpdate {
@ -840,8 +918,9 @@ message CloseChannelRequest {
int32 target_conf = 3;
/// A manual fee rate set in sat/byte that should be used when crafting the closure transaction.
int64 sat_per_byte = 5;
int64 sat_per_byte = 4;
}
message CloseStatusUpdate {
oneof update {
PendingUpdate close_pending = 1 [json_name = "close_pending"];
@ -857,13 +936,10 @@ message PendingUpdate {
message OpenChannelRequest {
/// The peer_id of the node to open a channel with
int32 target_peer_id = 1 [json_name = "target_peer_id"];
/// The pubkey of the node to open a channel with
bytes node_pubkey = 2 [json_name = "node_pubkey"];
/// The hex encorded pubkey of the node to open a channel with
/// The hex encoded pubkey of the node to open a channel with
string node_pubkey_string = 3 [json_name = "node_pubkey_string"];
/// The number of satoshis the wallet should commit to the channel
@ -1031,6 +1107,9 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
int64 amt = 2;
/// The max number of routes to return.
int32 num_routes = 3;
}
message QueryRoutesResponse {
repeated Route routes = 1 [ json_name = "routes"];
@ -1337,6 +1416,7 @@ message InvoiceSubscription {
message Payment {
/// The payment hash
string payment_hash = 1 [json_name = "payment_hash"];
/// The value of the payment in satoshis
int64 value = 2 [json_name = "value"];

29
app/lnd/lib/walletUnlocker.js

@ -0,0 +1,29 @@
import fs from 'fs'
import path from 'path'
import grpc from 'grpc'
import config from '../config'
// Default is ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
// https://github.com/grpc/grpc/blob/master/doc/environment_variables.md
//
// Current LND cipher suites here:
// https://github.com/lightningnetwork/lnd/blob/master/lnd.go#L80
//
// 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
process.env.GRPC_SSL_CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES || [
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-CBC-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305'
].join(':')
const walletUnlocker = (rpcpath, host) => {
const lndCert = fs.readFileSync(config.cert)
const credentials = grpc.credentials.createSsl(lndCert)
const rpc = grpc.load(path.join(__dirname, 'rpc.proto'))
return new rpc.lnrpc.WalletUnlocker(host, credentials)
}
export default walletUnlocker

9
app/lnd/methods/channelController.js

@ -1,10 +1,7 @@
import bitcore from 'bitcore-lib'
import find from 'lodash/find'
import { listPeers, connectPeer } from './peersController'
import pushopenchannel from '../push/openchannel'
const BufferUtil = bitcore.util.buffer
function ensurePeerConnected(lnd, meta, pubkey, host) {
return listPeers(lnd, meta)
.then(({ peers }) => {
@ -29,7 +26,7 @@ export function connectAndOpen(lnd, meta, event, payload) {
return ensurePeerConnected(lnd, meta, pubkey, host)
.then(() => {
const call = lnd.openChannel({
node_pubkey: BufferUtil.hexToBuffer(pubkey),
node_pubkey: Buffer.from(pubkey, 'hex'),
local_funding_amount: Number(localamt)
}, meta)
@ -54,7 +51,7 @@ export function connectAndOpen(lnd, meta, event, payload) {
export function openChannel(lnd, meta, event, payload) {
const { pubkey, localamt, pushamt } = payload
const res = {
node_pubkey: BufferUtil.hexToBuffer(pubkey),
node_pubkey: Buffer.from(pubkey, 'hex'),
local_funding_amount: Number(localamt),
push_sat: Number(pushamt)
}
@ -110,7 +107,7 @@ export function closeChannel(lnd, meta, event, payload) {
const tx = payload.channel_point.funding_txid.match(/.{2}/g).reverse().join('')
const res = {
channel_point: {
funding_txid: BufferUtil.hexToBuffer(tx),
funding_txid: Buffer.from(tx, 'hex'),
output_index: Number(payload.channel_point.output_index)
},
force

46
app/lnd/methods/walletController.js

@ -92,3 +92,49 @@ export function setAlias(lnd, meta, { new_alias }) {
})
})
}
/**
* Generates a seed for the wallet
*/
export function genSeed(walletUnlocker) {
return new Promise((resolve, reject) => {
walletUnlocker.genSeed({}, (err, data) => {
if (err) { reject(err) }
resolve(data)
})
})
}
/**
* Unlocks a wallet with a password
* @param {[type]} password [description]
*/
export function unlockWallet(walletUnlocker, { wallet_password }) {
return new Promise((resolve, reject) => {
walletUnlocker.unlockWallet({ wallet_password }, (err, data) => {
if (err) { reject(err) }
resolve(data)
})
})
}
/**
* Unlocks a wallet with a password
* @param {[type]} password [description]
* @param {[type]} cipher_seed_mnemonic [description]
*/
export function initWallet(walletUnlocker, { wallet_password, cipher_seed_mnemonic, aezeed_passphrase }) {
return new Promise((resolve, reject) => {
walletUnlocker.initWallet({
wallet_password,
cipher_seed_mnemonic,
aezeed_passphrase: Buffer.from(aezeed_passphrase, 'hex')
}, (err, data) => {
if (err) { reject(err) }
resolve(data)
})
})
}

24
app/lnd/walletUnlockerMethods/index.js

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

35
app/main.dev.js

@ -111,7 +111,7 @@ const sendGrpcConnected = () => {
// Create and subscribe the grpc object
const startGrpc = () => {
lnd((lndSubscribe, lndMethods) => {
lnd.initLnd((lndSubscribe, lndMethods) => {
// Subscribe to bi-directional streams
lndSubscribe(mainWindow)
@ -124,6 +124,18 @@ const startGrpc = () => {
})
}
// Create and subscribe the grpc object
const startWalletUnlocker = () => {
lnd.initWalletUnlocker((walletUnlockerMethods) => {
// Listen for all gRPC restful methods
ipcMain.on('walletUnlocker', (event, { msg, data }) => {
walletUnlockerMethods(event, msg, data)
})
})
mainWindow.webContents.send('walletUnlockerStarted')
}
// Send the front end event letting them know LND is synced to the blockchain
const sendLndSynced = () => {
const sendLndSyncedInterval = setInterval(() => {
@ -152,12 +164,11 @@ const startLnd = (alias, autopilot) => {
'--bitcoin.active',
'--bitcoin.testnet',
'--bitcoin.node=neutrino',
'--neutrino.connect=188.166.148.62:18333',
'--neutrino.connect=btcd.jackmallers.com:18333',
'--neutrino.addpeer=btcd.jackmallers.com:18333',
'--neutrino.addpeer=159.65.48.139:18333',
'--neutrino.connect=127.0.0.1:18333',
'--debuglevel=debug',
'--noencryptwallet',
`${autopilot ? '--autopilot.active' : ''}`,
`${alias ? `--alias=${alias}` : ''}`
]
@ -179,12 +190,22 @@ const startLnd = (alias, autopilot) => {
if (fs.existsSync(certPath)) {
clearInterval(certInterval)
console.log('CERT EXISTS, STARTING GRPC')
startGrpc()
console.log('CERT EXISTS, STARTING WALLET UNLOCKER')
startWalletUnlocker()
if (mainWindow) {
mainWindow.webContents.send('walletUnlockerStarted')
}
}
}, 1000)
}
if (line.includes('The wallet has been unlocked')) {
console.log('WALLET OPENED, STARTING LIGHTNING GRPC CONNECTION')
sendLndSyncing()
startGrpc()
}
// Pass current clock height progress to front end for loading state UX
if (mainWindow && (line.includes('Caught up to height') || line.includes('Catching up block hashes to height'))) {
// const blockHeight = line.slice(line.indexOf('Caught up to height') + 'Caught up to height'.length).trim()
@ -282,10 +303,8 @@ app.on('ready', async () => {
}
// Start LND
// startLnd()
// once the onboarding has finished we wanna let the application we have started syncing and start LND
ipcMain.on('onboardingFinished', (event, { alias, autopilot }) => {
sendLndSyncing()
ipcMain.on('startLnd', (event, { alias, autopilot }) => {
startLnd(alias, autopilot)
})
} else {

18
app/reducers/ipc.js

@ -35,7 +35,15 @@ import {
import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoutes } from './network'
import { startOnboarding } from './onboarding'
import {
startOnboarding,
walletUnlockerStarted,
receiveSeed,
receiveSeedError,
successfullyCreatedWallet,
walletUnlocked,
unlockWalletError
} from './onboarding'
// Import all receiving IPC event handlers and pass them into createIpc
const ipc = createIpc({
@ -95,7 +103,13 @@ const ipc = createIpc({
receiveQueryRoutes,
receiveInvoiceAndQueryRoutes,
startOnboarding
startOnboarding,
walletUnlockerStarted,
receiveSeed,
receiveSeedError,
successfullyCreatedWallet,
walletUnlocked,
unlockWalletError
})
export default ipc

231
app/reducers/onboarding.js

@ -1,17 +1,39 @@
import { createSelector } from 'reselect'
import { ipcRenderer } from 'electron'
// ------------------------------------
// Constants
// ------------------------------------
export const UPDATE_ALIAS = 'UPDATE_ALIAS'
export const UPDATE_PASSWORD = 'UPDATE_PASSWORD'
export const UPDATE_CREATE_WALLET_PASSWORD = 'UPDATE_CREATE_WALLET_PASSWORD'
export const UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION = 'UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION'
export const UPDATE_AEZEED_PASSWORD = 'UPDATE_AEZEED_PASSWORD'
export const UPDATE_AEZEED_PASSWORD_CONFIRMATION = 'UPDATE_AEZEED_PASSWORD_CONFIRMATION'
export const UPDATE_SEED_INPUT = 'UPDATE_SEED_INPUT'
export const CHANGE_STEP = 'CHANGE_STEP'
export const SET_AUTOPILOT = 'SET_AUTOPILOT'
export const FETCH_SEED = 'FETCH_SEED'
export const SET_SEED = 'SET_SEED'
export const SET_HAS_SEED = 'SET_HAS_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 CREATING_NEW_WALLET = 'CREATING_NEW_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 SET_SIGNUP_CREATE = 'SET_SIGNUP_CREATE'
export const SET_SIGNUP_IMPORT = 'SET_SIGNUP_IMPORT'
// ------------------------------------
// Actions
// ------------------------------------
@ -22,6 +44,48 @@ export function updateAlias(alias) {
}
}
export function updatePassword(password) {
return {
type: UPDATE_PASSWORD,
password
}
}
export function updateCreateWalletPassword(createWalletPassword) {
return {
type: UPDATE_CREATE_WALLET_PASSWORD,
createWalletPassword
}
}
export function updateCreateWalletPasswordConfirmation(createWalletPasswordConfirmation) {
return {
type: UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION,
createWalletPasswordConfirmation
}
}
export function updateAezeedPassword(aezeedPassword) {
return {
type: UPDATE_AEZEED_PASSWORD,
aezeedPassword
}
}
export function updateAezeedPasswordConfirmation(aezeedPasswordConfirmation) {
return {
type: UPDATE_AEZEED_PASSWORD_CONFIRMATION,
aezeedPasswordConfirmation
}
}
export function updateSeedInput(inputSeedObj) {
return {
type: UPDATE_SEED_INPUT,
inputSeedObj
}
}
export function setAutopilot(autopilot) {
return {
type: SET_AUTOPILOT,
@ -29,6 +93,18 @@ export function setAutopilot(autopilot) {
}
}
export function setSignupCreate() {
return {
type: SET_SIGNUP_CREATE
}
}
export function setSignupImport() {
return {
type: SET_SIGNUP_IMPORT
}
}
export function changeStep(step) {
return {
type: CHANGE_STEP,
@ -36,31 +112,142 @@ export function changeStep(step) {
}
}
export function submit(alias, autopilot) {
// alert the app we're done onboarding and it's cool to start LND
// send the alias they set along with whether they want autopilot on or not
ipcRenderer.send('onboardingFinished', { alias, autopilot })
export function startLnd(alias, autopilot) {
// once the user submits the data needed to start LND we will alert the app that it should start LND
ipcRenderer.send('startLnd', { alias, autopilot })
return {
type: ONBOARDING_FINISHED
type: STARTING_LND
}
}
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
ipcRenderer.send('walletUnlocker', { msg: 'initWallet', data: { wallet_password, cipher_seed_mnemonic, aezeed_passphrase } })
dispatch({ type: CREATING_NEW_WALLET })
}
export const startOnboarding = () => (dispatch) => {
dispatch({ type: ONBOARDING_STARTED })
}
// Listener from after the LND walletUnlocker has started
export const walletUnlockerStarted = () => (dispatch) => {
dispatch({ type: LND_STARTED })
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
}
export const createWallet = () => (dispatch) => {
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: CHANGE_STEP, step: 4 })
}
export const successfullyCreatedWallet = () => dispatch => dispatch({ type: ONBOARDING_FINISHED })
// Listener for when LND creates and sends us a generated seed
export const receiveSeed = (event, { cipher_seed_mnemonic }) => (dispatch) => {
dispatch({ type: CHANGE_STEP, step: 4 })
// 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 })
}
// Listener for when LND throws an error on seed creation
export const receiveSeedError = () => (dispatch) => {
dispatch({ type: SET_HAS_SEED, hasSeed: true })
// there is already a seed, send user to the login component
dispatch({ type: CHANGE_STEP, step: 3 })
}
// Unlock an existing wallet with a wallet password
export const unlockWallet = wallet_password => (dispatch) => {
ipcRenderer.send('walletUnlocker', { msg: 'unlockWallet', data: { wallet_password } })
dispatch({ type: UNLOCKING_WALLET })
}
export const walletUnlocked = () => (dispatch) => {
dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED })
}
export const unlockWalletError = () => (dispatch) => {
dispatch({ type: SET_UNLOCK_WALLET_ERROR })
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[UPDATE_ALIAS]: (state, { alias }) => ({ ...state, alias }),
[UPDATE_PASSWORD]: (state, { password }) => ({ ...state, password }),
[UPDATE_CREATE_WALLET_PASSWORD]: (state, { createWalletPassword }) => ({ ...state, createWalletPassword }),
[UPDATE_CREATE_WALLET_PASSWORD_CONFIRMATION]: (state, { createWalletPasswordConfirmation }) => ({ ...state, createWalletPasswordConfirmation }),
[UPDATE_AEZEED_PASSWORD]: (state, { aezeedPassword }) => ({ ...state, aezeedPassword }),
[UPDATE_AEZEED_PASSWORD_CONFIRMATION]: (state, { aezeedPasswordConfirmation }) => ({ ...state, aezeedPasswordConfirmation }),
[UPDATE_SEED_INPUT]: (state, { inputSeedObj }) => ({
...state,
seedInput: Object.assign([], state.seedInput, { [inputSeedObj.index]: inputSeedObj })
}),
[SET_AUTOPILOT]: (state, { autopilot }) => ({ ...state, autopilot }),
[SET_HAS_SEED]: (state, { hasSeed }) => ({ ...state, hasSeed }),
[SET_SEED]: (state, { seed }) => ({ ...state, seed, fetchingSeed: false }),
[CHANGE_STEP]: (state, { step }) => ({ ...state, step }),
[ONBOARDING_STARTED]: state => ({ ...state, onboarded: false }),
[ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true })
[ONBOARDING_FINISHED]: state => ({ ...state, onboarded: true }),
[STARTING_LND]: state => ({ ...state, startingLnd: true }),
[LND_STARTED]: state => ({ ...state, startingLnd: false }),
[CREATING_NEW_WALLET]: state => ({ ...state, creatingNewWallet: true }),
[UNLOCKING_WALLET]: state => ({ ...state, unlockingWallet: true }),
[WALLET_UNLOCKED]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: false, message: '' } }),
[SET_UNLOCK_WALLET_ERROR]: state => ({ ...state, unlockingWallet: false, unlockWalletError: { isError: true, message: 'Incorrect password' } }),
[SET_SIGNUP_CREATE]: state => ({ ...state, signupForm: { create: true, import: false } }),
[SET_SIGNUP_IMPORT]: state => ({ ...state, signupForm: { create: false, import: true } })
}
const onboardingSelectors = {}
const passwordSelector = state => state.onboarding.password
const createWalletPasswordSelector = state => state.onboarding.createWalletPassword
const createWalletPasswordConfirmationSelector = state => state.onboarding.createWalletPasswordConfirmation
const aezeedPasswordSelector = state => state.onboarding.aezeedPassword
const aezeedPasswordConfirmationSelector = state => state.onboarding.aezeedPasswordConfirmation
const seedSelector = state => state.onboarding.seed
const seedInputSelector = state => state.onboarding.seedInput
onboardingSelectors.passwordIsValid = createSelector(
passwordSelector,
password => password.length >= 8
)
onboardingSelectors.showCreateWalletPasswordConfirmationError = createSelector(
createWalletPasswordSelector,
createWalletPasswordConfirmationSelector,
(pass1, pass2) => pass1 !== pass2 && pass2.length > 0
)
onboardingSelectors.showAezeedPasswordConfirmationError = createSelector(
aezeedPasswordSelector,
aezeedPasswordConfirmationSelector,
(pass1, pass2) => pass1 !== pass2 && pass2.length > 0
)
onboardingSelectors.reEnterSeedChecker = createSelector(
seedSelector,
seedInputSelector,
(seed, seedInput) => seed.length === seedInput.length && seed.every((word, i) => word === seedInput[i].word)
)
export { onboardingSelectors }
// ------------------------------------
// Reducer
// ------------------------------------
@ -68,6 +255,38 @@ const initialState = {
onboarded: true,
step: 1,
alias: '',
password: '',
startingLnd: false,
fetchingSeed: false,
hasSeed: false,
seed: [],
// wallet password. password used to encrypt the wallet and is required to unlock the daemon after set
createWalletPassword: '',
createWalletPasswordConfirmation: '',
creatingNewWallet: false,
// seed password. this is optional and used to encrypt the seed
aezeedPassword: '',
aezeedPasswordConfirmation: '',
unlockingWallet: false,
unlockWalletError: {
isError: false,
message: ''
},
// array of inputs for when the user re-enters their seed
// object has a word attr and a index attr:
// { word: 'foo', index: 0 }
seedInput: [],
// step where the user decides whether they want a newly created seed or to import an existing one
signupForm: {
create: false,
import: false
},
autopilot: null
}

148
app/rpc.proto

@ -28,13 +28,39 @@ package lnrpc;
// The WalletUnlocker service is used to set up a wallet password for
// lnd at first startup, and unlock a previously set up wallet.
service WalletUnlocker {
/** lncli: `create`
CreateWallet is used at lnd startup to set the encryption password for
the wallet database.
/**
GenSeed is the first method that should be used to instantiate a new lnd
instance. This method allows a caller to generate a new aezeed cipher seed
given an optional passphrase. If provided, the passphrase will be necessary
to decrypt the cipherseed to expose the internal wallet seed.
Once the cipherseed is obtained and verified by the user, the InitWallet
method should be used to commit the newly generated seed, and create the
wallet.
*/
rpc CreateWallet(CreateWalletRequest) returns (CreateWalletResponse) {
rpc GenSeed(GenSeedRequest) returns (GenSeedResponse) {
option (google.api.http) = {
post: "/v1/createwallet"
get: "/v1/genseed"
};
}
/** lncli: `init`
InitWallet is used when lnd is starting up for the first time to fully
initialize the daemon and its internal wallet. At the very least a wallet
password must be provided. This will be used to encrypt sensitive material
on disk.
In the case of a recovery scenario, the user can also specify their aezeed
mnemonic and passphrase. If set, then the daemon will use this prior state
to initialize its internal wallet.
Alternatively, this can be used along with the GenSeed RPC to obtain a
seed, then present it to the user. Once it has been verified by the user,
the seed can be fed into this RPC in order to commit the new wallet.
*/
rpc InitWallet(InitWalletRequest) returns (InitWalletResponse) {
option (google.api.http) = {
post: "/v1/initwallet"
body: "*"
};
}
@ -51,20 +77,74 @@ service WalletUnlocker {
}
}
message CreateWalletRequest {
bytes password = 1;
message GenSeedRequest {
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 1;
/**
seed_entropy is an optional 16-bytes generated via CSPRNG. If not
specified, then a fresh set of randomness will be used to create the seed.
*/
bytes seed_entropy = 2;
}
message GenSeedResponse {
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This field is optional, as if not
provided, then the daemon will generate a new cipher seed for the user.
Otherwise, then the daemon will attempt to recover the wallet state linked
to this cipher seed.
*/
repeated string cipher_seed_mnemonic = 1;
/**
enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
cipher text before run through our mnemonic encoding scheme.
*/
bytes enciphered_seed = 2;
}
message CreateWalletResponse {}
message InitWalletRequest {
/**
wallet_password is the passphrase that should be used to encrypt the
wallet. This MUST be at least 8 chars in length. After creation, this
password is required to unlock the daemon.
*/
bytes wallet_password = 1;
/**
cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed
cipher seed obtained by the user. This may have been generated by the
GenSeed method, or be an existing seed.
*/
repeated string cipher_seed_mnemonic = 2;
/**
aezeed_passphrase is an optional user provided passphrase that will be used
to encrypt the generated aezeed cipher seed.
*/
bytes aezeed_passphrase = 3;
}
message InitWalletResponse {
}
message UnlockWalletRequest {
bytes password = 1;
/**
wallet_password should be the current valid passphrase for the daemon. This
will be required to decrypt on-disk material that the daemon requires to
function properly.
*/
bytes wallet_password = 1;
}
message UnlockWalletResponse {}
service Lightning {
/** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all confirmed unspent outputs and all unconfirmed unspent outputs under control
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
by the wallet. This method can be modified by having the request specify
only witness outputs should be factored into the final output sum.
*/
@ -251,7 +331,7 @@ service Lightning {
*/
rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate) {
option (google.api.http) = {
delete: "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}"
delete: "/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}"
};
}
@ -294,18 +374,18 @@ service Lightning {
*/
rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse) {
option (google.api.http) = {
get: "/v1/invoices/{pending_only}"
get: "/v1/invoices"
};
}
/** lncli: `lookupinvoice`
LookupInvoice attemps to look up an invoice according to its payment hash.
LookupInvoice attempts to look up an invoice according to its payment hash.
The passed payment hash *must* be exactly 32 bytes, if not, an error is
returned.
*/
rpc LookupInvoice (PaymentHash) returns (Invoice) {
option (google.api.http) = {
get: "/v1/invoices/{r_hash_str}"
get: "/v1/invoice/{r_hash_str}"
};
}
@ -389,7 +469,7 @@ service Lightning {
route to a target destination capable of carrying a specific amount of
satoshis. The retuned route contains the full details required to craft and
send an HTLC, also including the necessary information that should be
present within the Sphinx packet encapsualted within the HTLC.
present within the Sphinx packet encapsulated within the HTLC.
*/
rpc QueryRoutes(QueryRoutesRequest) returns (QueryRoutesResponse) {
option (google.api.http) = {
@ -447,7 +527,7 @@ service Lightning {
*/
rpc UpdateChannelPolicy(PolicyUpdateRequest) returns (PolicyUpdateResponse) {
option (google.api.http) = {
post: "/v1/fees"
post: "/v1/chanpolicy"
body: "*"
};
}
@ -518,16 +598,16 @@ message SendResponse {
}
message ChannelPoint {
// TODO(roasbeef): make str vs bytes into a oneof
/// Txid of the funding transaction
bytes funding_txid = 1 [ json_name = "funding_txid" ];
oneof funding_txid {
/// Txid of the funding transaction
bytes funding_txid_bytes = 1 [json_name = "funding_txid_bytes"];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [ json_name = "funding_txid_str" ];
/// Hex-encoded string representing the funding transaction
string funding_txid_str = 2 [json_name = "funding_txid_str"];
}
/// The index of the output of the funding transaction
uint32 output_index = 3 [ json_name = "output_index" ];
uint32 output_index = 3 [json_name = "output_index"];
}
message LightningAddress {
@ -610,7 +690,7 @@ message VerifyMessageRequest {
/// The message over which the signature is to be verified
bytes msg = 1 [ json_name = "msg" ];
/// The signature to be verifed over the given message
/// The signature to be verified over the given message
string signature = 2 [ json_name = "signature" ];
}
message VerifyMessageResponse {
@ -630,8 +710,6 @@ message ConnectPeerRequest {
bool perm = 2;
}
message ConnectPeerResponse {
/// The id of the newly connected peer
int32 peer_id = 1 [json_name = "peer_id"];
}
message DisconnectPeerRequest {
@ -738,9 +816,6 @@ message Peer {
/// The identity pubkey of the peer
string pub_key = 1 [json_name = "pub_key"];
/// The peer's id from the local point of view
int32 peer_id = 2 [json_name = "peer_id"];
/// Network address of the peer; eg `127.0.0.1:10011`
string address = 3 [json_name = "address"];
@ -806,6 +881,9 @@ message GetInfoResponse {
/// The URIs of the current node.
repeated string uris = 12 [json_name = "uris"];
/// Timestamp of the block best known to the wallet
int64 best_header_timestamp = 13 [ json_name = "best_header_timestamp" ];
}
message ConfirmationUpdate {
@ -840,8 +918,9 @@ message CloseChannelRequest {
int32 target_conf = 3;
/// A manual fee rate set in sat/byte that should be used when crafting the closure transaction.
int64 sat_per_byte = 5;
int64 sat_per_byte = 4;
}
message CloseStatusUpdate {
oneof update {
PendingUpdate close_pending = 1 [json_name = "close_pending"];
@ -857,13 +936,10 @@ message PendingUpdate {
message OpenChannelRequest {
/// The peer_id of the node to open a channel with
int32 target_peer_id = 1 [json_name = "target_peer_id"];
/// The pubkey of the node to open a channel with
bytes node_pubkey = 2 [json_name = "node_pubkey"];
/// The hex encorded pubkey of the node to open a channel with
/// The hex encoded pubkey of the node to open a channel with
string node_pubkey_string = 3 [json_name = "node_pubkey_string"];
/// The number of satoshis the wallet should commit to the channel
@ -1031,6 +1107,9 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
int64 amt = 2;
/// The max number of routes to return.
int32 num_routes = 3;
}
message QueryRoutesResponse {
repeated Route routes = 1 [ json_name = "routes"];
@ -1337,6 +1416,7 @@ message InvoiceSubscription {
message Payment {
/// The payment hash
string payment_hash = 1 [json_name = "payment_hash"];
/// The value of the payment in satoshis
int64 value = 2 [json_name = "value"];

Loading…
Cancel
Save