Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add on-chain withdraw UI #69

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

- ui: Add a new interface for withdrawing on-chain funds

## 0.2.5 - 2019-02-24

- Use the compact alphanumeric QR encoding mode for bech32 addresses
Expand Down
20 changes: 15 additions & 5 deletions client/src/intent.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = ({ DOM, route, conf$, scan$, urihandler$ }) => {
, goNewChan$ = route('/channels/new')
, goDeposit$ = route('/deposit').mapTo('bech32')
.merge(click('[data-newaddr-type]').map(e => e.ownerTarget.dataset.newaddrType))
, goWithdraw$ = route('/withdraw')

// Display and confirm payment requests (from QR, lightning: URIs and manual entry)
, viewPay$ = O.merge(scan$, urihandler$).map(parseUri).filter(x => !!x)
Expand Down Expand Up @@ -69,10 +70,18 @@ module.exports = ({ DOM, route, conf$, scan$, urihandler$ }) => {
.share()
, openChan$ = submit('[do=open-channel]')
.map(d => ({ ...d, channel_capacity_sat: toSatCapacity(d.channel_capacity_msat) }))
, fundMaxChan$ = on('[name=channel-fund-max]', 'input')
.map(e => e.target.checked)
.merge(goNewChan$.mapTo(false))
.startWith(false)

// Withdraw
, execWithdraw$ = submit('[do=exec-withdraw]')
.map(d => ({ ...d, amount_sat: toSatCapacity(d.amount_sat) }))

, fundMax$ = O.merge(
on('[name=channel-fund-max]', 'input')
, on('[name=withdraw-fund-max]', 'input'))
.map(e => e.target.checked)
.merge(goNewChan$.mapTo(false))
.merge(goWithdraw$.mapTo(false))
.startWith(false)

return { conf$, page$
, goHome$, goScan$, goSend$, goRecv$, goNode$, goLogs$, goRpc$, goDeposit$
Expand All @@ -82,7 +91,8 @@ module.exports = ({ DOM, route, conf$, scan$, urihandler$ }) => {
, newInv$, amtVal$
, togExp$, togTheme$, togUnit$
, feedStart$, togFeed$
, togChan$, updChan$, openChan$, closeChan$, fundMaxChan$
, togChan$, updChan$, openChan$, closeChan$
, goWithdraw$, execWithdraw$, fundMax$
, dismiss$
}
}
Expand Down
6 changes: 4 additions & 2 deletions client/src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ const

module.exports = ({ dismiss$, togExp$, togTheme$, togUnit$, page$, goHome$, goRecv$, goChan$
, amtVal$, execRpc$, execRes$, clrHist$, feedStart$: feedStart_$, togFeed$, togChan$
, fundMaxChan$
, fundMax$
, conf$: savedConf$
, req$$, error$, invoice$, incoming$, outgoing$, payments$, invoices$, funds$
, funded$, closed$
, withdrawn$
, btcusd$, info$, peers$ }) => {
const

Expand Down Expand Up @@ -68,6 +69,7 @@ module.exports = ({ dismiss$, togExp$, togTheme$, togUnit$, page$, goHome$, goRe
, outgoing$.map(p => [ 'success', `Sent payment of @{{${p.msatoshi}}}` ])
, funded$.map(c => [ 'success', `Opening channel for @{{${c.chan.msatoshi_total}}}, awaiting on-chain confirmation` ])
, closed$.map(c => [ 'success', `Channel ${c.chan.short_channel_id || c.chan.channel_id} is closing` ])
, withdrawn$.map(w => [ 'success', `Withdraw sent. txid: ${w.txid}` ])
, dismiss$.mapTo(null)
)
// hide "connection lost" errors when we get back online
Expand Down Expand Up @@ -171,7 +173,7 @@ module.exports = ({ dismiss$, togExp$, togTheme$, togUnit$, page$, goHome$, goRe
, info$: info$.startWith(null), peers$: peers$.startWith(null), channels$: channels$.startWith(null)
, feed$: feed$.startWith(null), feedStart$, feedActive$
, amtData$, chanActive$, rpcHist$
, fundMaxChan$
, fundMax$
, msatusd$, btcusd$: btcusd$.startWith(null)
}).shareReplay(1)
}
5 changes: 3 additions & 2 deletions client/src/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ exports.parseRes = ({ HTTP, SSE }) => {
, invoice$: reply('invoice').map(r => ({ ...r.body, ...r.request.ctx }))
, outgoing$: reply('pay').map(r => ({ ...r.body, ...r.request.ctx }))
, newaddr$: reply('newaddr').map(r => ({ address: r.body.address, type: r.request.send.params[0] }))
, withdrawn$: reply('withdraw').map(r => ({ txid: r.body.txid }))
, funded$: reply('connectfund').map(r => r.body)
, closed$: reply('closeget').map(r => r.body)
, execRes$: reply('console').map(r => ({ ...r.request.send, res: r.body }))
Expand All @@ -43,7 +44,7 @@ exports.parseRes = ({ HTTP, SSE }) => {

// RPC commands to send
// NOTE: "connectfund" and "closeget" are custom rpc commands provided by the Spark server.
exports.makeReq = ({ viewPay$, confPay$, newInv$, goLogs$, goChan$, goNewChan$, goDeposit$, updChan$, openChan$, closeChan$, execRpc$ }) => O.merge(
exports.makeReq = ({ viewPay$, confPay$, newInv$, goLogs$, goChan$, goNewChan$, execWithdraw$, goDeposit$, updChan$, openChan$, closeChan$, execRpc$ }) => O.merge(
viewPay$.map(bolt11 => [ 'decodepay', [ bolt11 ], { bolt11 } ])
, confPay$.map(pay => [ 'pay', [ pay.bolt11, ...(pay.custom_msat ? [ pay.custom_msat ] : []) ], pay ])
, newInv$.map(inv => [ 'invoice', [ inv.msatoshi, inv.label, inv.description, INVOICE_TTL ], inv ])
Expand All @@ -52,7 +53,7 @@ exports.makeReq = ({ viewPay$, confPay$, newInv$, goLogs$, goChan$, goNewChan$,
, updChan$.mapTo( [ 'listpeers' ] )
, openChan$.map(d => [ 'connectfund', [ d.nodeuri, d.channel_capacity_sat, d.feerate ] ])
, closeChan$.map(d => [ 'closeget', [ d.peerid, d.chanid ] ])

, execWithdraw$.map(d => [ 'withdraw', [ d.address, d.amount_sat, d.feerate ] ])
, goDeposit$.map(type => [ 'newaddr', [ type ] ])

, timer(60000).mapTo( [ 'listinvoices', [], { bg: true } ])
Expand Down
3 changes: 2 additions & 1 deletion client/src/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import themeColors from '../theme-colors.json'
const isFunc = x => typeof x == 'function'

// DOM view
exports.vdom = ({ state$, goHome$, goScan$, goSend$, goRecv$, goChan$, goNewChan$, goNode$, goRpc$, payreq$, invoice$, newaddr$, logs$ }) => {
exports.vdom = ({ state$, goHome$, goScan$, goSend$, goRecv$, goChan$, goNewChan$, goWithdraw$, goNode$, goRpc$, payreq$, invoice$, newaddr$, logs$ }) => {
const body$ = O.merge(
// user actions
goHome$.startWith(1).mapTo(views.home)
Expand All @@ -15,6 +15,7 @@ exports.vdom = ({ state$, goHome$, goScan$, goSend$, goRecv$, goChan$, goNewChan
, goRecv$.mapTo(views.recv)
, goChan$.mapTo(views.channels)
, goNewChan$.mapTo(views.newChannel)
, goWithdraw$.mapTo(views.withdraw)
, goNode$.mapTo(views.nodeInfo)
, goRpc$.mapTo(views.rpc)

Expand Down
6 changes: 3 additions & 3 deletions client/src/views/channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const channels = ({ channels, chanActive, unitf, info, conf: { expert } }
])
}

export const newChannel = ({ amtData, fundMaxChan, obalance, unitf, conf: { unit, expert } }) => {
export const newChannel = ({ amtData, fundMax, obalance, unitf, conf: { unit, expert } }) => {
const availText = obalance != null ? `Available: ${unitf(obalance)}` : ''

return form({ attrs: { do: 'open-channel' } }, [
Expand All @@ -55,14 +55,14 @@ export const newChannel = ({ amtData, fundMaxChan, obalance, unitf, conf: { unit
name: 'nodeuri', placeholder: 'nodeid@host[:port]', required: true } }))

, formGroup('Channel funding', div([
!fundMaxChan
!fundMax
? amountField(amtData, 'channel_capacity_msat', true, availText)
: div('.input-group', [
input({ attrs: { type: 'hidden', name: 'channel_capacity_msat', value: 'all' } })
, input('.form-control.form-control-lg.disabled', { attrs: { disabled: true, placeholder: availText } })
, div('.input-group-append.toggle-unit', span('.input-group-text', unit))
])
, fancyCheckbox('channel-fund-max', 'Fund maximum', fundMaxChan, '.btn-sm')
, fancyCheckbox('channel-fund-max', 'Fund maximum', fundMax, '.btn-sm')
]))

, expert ? formGroup('Fee rate', input('.form-control.form-control-lg'
Expand Down
5 changes: 4 additions & 1 deletion client/src/views/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ exports.nodeInfo = async ({ info, peers, conf: { expert } }) => {
process.env.BUILD_TARGET != 'web' ? a('.btn.btn-secondary.btn-sm', { attrs: { href: 'settings.html', rel: 'external' }}, 'Server settings') : ''
, ' '
, a('.btn.btn-secondary.btn-sm', { attrs: { href: '#/channels' }}, 'Channels')
, ' '
])
, p('.text-center.mt-4', [
, a('.btn.btn-secondary.btn-sm', { attrs: { href: '#/deposit' }}, 'Deposit')
, ' '
, a('.btn.btn-secondary.btn-sm', { attrs: { href: '#/withdraw' }}, 'Withdraw')
])
, expert ? yaml(info) : ''
])
Expand Down
34 changes: 32 additions & 2 deletions client/src/views/onchain.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { div, img, h2, h4, small, a, button, p } from '@cycle/dom'
import { yaml, qruri } from './util'
import { div, img, h2, h4, span, a, p, button, form, input } from '@cycle/dom'
import { yaml, qruri, formGroup, amountField, fancyCheckbox } from './util'

const labelType = { bech32: 'Bech32', 'p2sh-segwit': 'P2SH' }
, otherType = { bech32: 'p2sh-segwit', 'p2sh-segwit': 'bech32' }
Expand Down Expand Up @@ -28,3 +28,33 @@ export const deposit = ({ address, type }) => addrQr(address, type).then(qr => (
, p('.text-muted.small', 'Note: c-lightning does not process unconfirmed payments. You will not receive a notification for the payment, please check back once its confirmed.')
, expert ? yaml({ outputs: funds && funds.outputs }) : ''
]))

export const withdraw = ({ amtData, fundMax, obalance, unitf, conf: { unit, expert } }) => {
const availText = obalance != null ? `Available: ${unitf(obalance)}` : ''

return form({ attrs: { do: 'exec-withdraw' } }, [
h2('On-chain withdraw')

, formGroup('Address', input('.form-control.form-control-lg' , { attrs: {
name: 'address', required: true } }))

, formGroup('Withdraw Amount', div([
!fundMax
? amountField(amtData, 'amount_sat', true, availText)
: div('.input-group', [
input({ attrs: { type: 'hidden', name: 'amount_sat', value: 'all' } })
, input('.form-control.form-control-lg.disabled', { attrs: { disabled: true, placeholder: availText } })
, div('.input-group-append.toggle-unit', span('.input-group-text', unit))
])
, fancyCheckbox('withdraw-fund-max', 'Withdraw All', fundMax, '.btn-sm')
]))

, expert ? formGroup('Fee rate', input('.form-control.form-control-lg'
, { attrs: { type: 'text', name: 'feerate', placeholder: '(optional)'
, pattern: '[0-9]+(perk[bw])?|slow|normal|urgent' } })) : ''

, div('.form-buttons', [
button('.btn.btn-lg.btn-primary', { attrs: { type: 'submit' } }, 'Withdraw')
])
])
}
2 changes: 1 addition & 1 deletion client/styl/style.styl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ html
.alert
font-size 0.9rem
font-weight 400
word-break break-word
word-wrap break-word

.btn-link
cursor pointer
Expand Down