2015-12-30 05:31:03 +03:00
package main
import (
2015-12-30 05:58:58 +03:00
"bytes"
2016-07-13 03:47:24 +03:00
"encoding/hex"
2015-12-30 05:31:03 +03:00
"encoding/json"
2017-01-30 01:56:31 +03:00
"errors"
2016-04-25 06:27:19 +03:00
"fmt"
2016-07-08 01:35:06 +03:00
"io"
2017-01-24 07:32:17 +03:00
"io/ioutil"
2017-01-25 05:07:15 +03:00
"math"
2015-12-30 05:58:58 +03:00
"os"
2017-01-24 07:32:17 +03:00
"os/exec"
"strconv"
2016-06-21 22:35:07 +03:00
"strings"
2016-12-27 08:52:15 +03:00
2017-01-24 07:32:17 +03:00
"github.com/awalterschulze/gographviz"
2017-01-30 01:56:31 +03:00
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
2016-01-16 21:45:54 +03:00
"github.com/lightningnetwork/lnd/lnrpc"
2017-01-06 00:56:27 +03:00
"github.com/roasbeef/btcd/chaincfg/chainhash"
2017-01-24 07:32:17 +03:00
"github.com/roasbeef/btcutil"
2016-07-26 20:42:35 +03:00
"github.com/urfave/cli"
2015-12-30 05:31:03 +03:00
"golang.org/x/net/context"
)
2016-06-21 22:35:07 +03:00
// TODO(roasbeef): cli logic for supporting both positional and unix style
// arguments.
2017-01-30 01:56:31 +03:00
func printJson ( resp interface { } ) {
2015-12-30 05:58:58 +03:00
b , err := json . Marshal ( resp )
if err != nil {
fatal ( err )
}
var out bytes . Buffer
json . Indent ( & out , b , "" , "\t" )
out . WriteTo ( os . Stdout )
}
2017-01-30 01:56:31 +03:00
func printRespJson ( resp proto . Message ) {
jsonMarshaler := & jsonpb . Marshaler {
EmitDefaults : true ,
Indent : " " ,
}
jsonStr , err := jsonMarshaler . MarshalToString ( resp )
if err != nil {
fmt . Println ( "unable to decode response: " , err )
return
}
fmt . Println ( jsonStr )
}
2015-12-30 05:31:03 +03:00
var NewAddressCommand = cli . Command {
Name : "newaddress" ,
2016-06-21 22:35:07 +03:00
Usage : "generates a new address. Three address types are supported: p2wkh, np2wkh, p2pkh" ,
2015-12-30 05:31:03 +03:00
Action : newAddress ,
}
2016-06-29 23:01:08 +03:00
func newAddress ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2015-12-30 05:31:03 +03:00
2016-04-25 06:27:19 +03:00
stringAddrType := ctx . Args ( ) . Get ( 0 )
// Map the string encoded address type, to the concrete typed address
// type enum. An unrecognized address type will result in an error.
var addrType lnrpc . NewAddressRequest_AddressType
switch stringAddrType { // TODO(roasbeef): make them ints on the cli?
case "p2wkh" :
addrType = lnrpc . NewAddressRequest_WITNESS_PUBKEY_HASH
case "np2wkh" :
addrType = lnrpc . NewAddressRequest_NESTED_PUBKEY_HASH
case "p2pkh" :
addrType = lnrpc . NewAddressRequest_PUBKEY_HASH
default :
2016-06-29 23:01:08 +03:00
return fmt . Errorf ( "invalid address type %v, support address type " +
"are: p2wkh, np2wkh, p2pkh" , stringAddrType )
2016-04-25 06:27:19 +03:00
}
2015-12-30 05:31:03 +03:00
ctxb := context . Background ( )
2016-04-25 06:27:19 +03:00
addr , err := client . NewAddress ( ctxb , & lnrpc . NewAddressRequest {
Type : addrType ,
} )
2015-12-30 05:31:03 +03:00
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2015-12-30 05:31:03 +03:00
}
2015-12-30 05:58:58 +03:00
printRespJson ( addr )
2016-06-29 23:01:08 +03:00
return nil
2015-12-30 05:31:03 +03:00
}
2016-06-29 21:29:21 +03:00
var SendCoinsCommand = cli . Command {
Name : "sendcoins" ,
Description : "send a specified amount of bitcoin to the passed address" ,
Usage : "sendcoins --addr=<bitcoin addresss> --amt=<num coins in satoshis>" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "addr" ,
Usage : "the bitcoin address to send coins to on-chain" ,
} ,
// TODO(roasbeef): switch to BTC on command line? int may not be sufficient
cli . IntFlag {
Name : "amt" ,
Usage : "the number of bitcoin denominated in satoshis to send" ,
} ,
} ,
Action : sendCoins ,
}
2016-06-29 23:01:08 +03:00
func sendCoins ( ctx * cli . Context ) error {
2016-06-29 21:29:21 +03:00
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-06-29 21:29:21 +03:00
req := & lnrpc . SendCoinsRequest {
Addr : ctx . String ( "addr" ) ,
Amount : int64 ( ctx . Int ( "amt" ) ) ,
}
txid , err := client . SendCoins ( ctxb , req )
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-29 21:29:21 +03:00
}
printRespJson ( txid )
2016-06-29 23:01:08 +03:00
return nil
2016-06-29 21:29:21 +03:00
}
2015-12-30 05:31:03 +03:00
var SendManyCommand = cli . Command {
Name : "sendmany" ,
2016-06-29 21:29:21 +03:00
Description : "create and broadcast a transaction paying the specified " +
2015-12-30 05:31:03 +03:00
"amount(s) to the passed address(es)" ,
2016-06-29 21:29:21 +03:00
Usage : ` sendmany ' { "ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}' ` ,
2015-12-30 05:31:03 +03:00
Action : sendMany ,
}
2016-06-29 23:01:08 +03:00
func sendMany ( ctx * cli . Context ) error {
2015-12-30 05:31:03 +03:00
var amountToAddr map [ string ] int64
jsonMap := ctx . Args ( ) . Get ( 0 )
if err := json . Unmarshal ( [ ] byte ( jsonMap ) , & amountToAddr ) ; err != nil {
2016-06-29 23:01:08 +03:00
return err
2015-12-30 05:31:03 +03:00
}
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2015-12-30 05:31:03 +03:00
txid , err := client . SendMany ( ctxb , & lnrpc . SendManyRequest { amountToAddr } )
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2015-12-30 05:31:03 +03:00
}
2015-12-30 05:58:58 +03:00
printRespJson ( txid )
2016-06-29 23:01:08 +03:00
return nil
2015-12-30 05:31:03 +03:00
}
2016-01-17 06:10:29 +03:00
var ConnectCommand = cli . Command {
2017-01-10 06:09:45 +03:00
Name : "connect" ,
Usage : "connect to a remote lnd peer: <pubkey>@host (--perm=true|false])" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "perm" ,
Usage : "If true, then the daemon will attempt to persistently " +
"connect to the target peer. If false then the call " +
"will be synchronous." ,
} ,
} ,
2016-01-17 06:10:29 +03:00
Action : connectPeer ,
}
2016-06-29 23:01:08 +03:00
func connectPeer ( ctx * cli . Context ) error {
2016-01-17 06:10:29 +03:00
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-01-17 06:10:29 +03:00
targetAddress := ctx . Args ( ) . Get ( 0 )
2016-06-21 22:35:07 +03:00
splitAddr := strings . Split ( targetAddress , "@" )
2016-07-17 03:43:27 +03:00
if len ( splitAddr ) != 2 {
2016-10-28 05:42:47 +03:00
return fmt . Errorf ( "target address expected in format: " +
"pubkey@host:port" )
2016-07-17 03:43:27 +03:00
}
2016-06-21 22:35:07 +03:00
addr := & lnrpc . LightningAddress {
2016-10-28 05:42:47 +03:00
Pubkey : splitAddr [ 0 ] ,
Host : splitAddr [ 1 ] ,
2016-06-21 22:35:07 +03:00
}
2017-01-10 06:09:45 +03:00
req := & lnrpc . ConnectPeerRequest {
Addr : addr ,
Perm : ctx . Bool ( "perm" ) ,
}
2016-01-17 06:10:29 +03:00
lnid , err := client . ConnectPeer ( ctxb , req )
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-01-17 06:10:29 +03:00
}
printRespJson ( lnid )
2016-06-29 23:01:08 +03:00
return nil
2016-01-17 06:10:29 +03:00
}
2016-06-21 22:35:07 +03:00
// TODO(roasbeef): default number of confirmations
var OpenChannelCommand = cli . Command {
Name : "openchannel" ,
Description : "Attempt to open a new channel to an existing peer, " +
2016-09-14 01:36:27 +03:00
"optionally blocking until the channel is 'open'. Once the " +
"channel is open, a channelPoint (txid:vout) of the funding " +
2016-10-28 05:42:47 +03:00
"output is returned. NOTE: peer_id and node_key are " +
2016-09-14 01:36:27 +03:00
"mutually exclusive, only one should be used, not both." ,
2017-01-25 04:50:35 +03:00
Usage : "openchannel --node_key=X --local_amt=N --push_amt=N --num_confs=N" ,
2016-06-21 22:35:07 +03:00
Flags : [ ] cli . Flag {
cli . IntFlag {
Name : "peer_id" ,
2016-09-14 01:36:27 +03:00
Usage : "the relative id of the peer to open a channel with" ,
} ,
cli . StringFlag {
2016-10-28 05:42:47 +03:00
Name : "node_key" ,
Usage : "the identity public key of the target peer " +
"serialized in compressed format" ,
2016-06-21 22:35:07 +03:00
} ,
cli . IntFlag {
Name : "local_amt" ,
Usage : "the number of satoshis the wallet should commit to the channel" ,
} ,
cli . IntFlag {
2017-01-10 06:06:07 +03:00
Name : "push_amt" ,
Usage : "the number of satoshis to push to the remote " +
"side as part of the initial commitment state" ,
2016-06-21 22:35:07 +03:00
} ,
cli . IntFlag {
Name : "num_confs" ,
Usage : "the number of confirmations required before the " +
"channel is considered 'open'" ,
} ,
2016-07-08 01:35:06 +03:00
cli . BoolFlag {
Name : "block" ,
Usage : "block and wait until the channel is fully open" ,
} ,
2016-06-21 22:35:07 +03:00
} ,
Action : openChannel ,
}
2016-06-29 23:01:08 +03:00
func openChannel ( ctx * cli . Context ) error {
2016-06-21 22:35:07 +03:00
// TODO(roasbeef): add deadline to context
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-06-21 22:35:07 +03:00
2016-10-28 05:42:47 +03:00
if ctx . Int ( "peer_id" ) != 0 && ctx . String ( "node_key" ) != "" {
2016-09-14 01:36:27 +03:00
return fmt . Errorf ( "both peer_id and lightning_id cannot be set " +
"at the same time, only one can be specified" )
}
2016-06-21 22:35:07 +03:00
req := & lnrpc . OpenChannelRequest {
2017-01-10 06:06:07 +03:00
LocalFundingAmount : int64 ( ctx . Int ( "local_amt" ) ) ,
PushSat : int64 ( ctx . Int ( "push_amt" ) ) ,
NumConfs : uint32 ( ctx . Int ( "num_confs" ) ) ,
2016-06-21 22:35:07 +03:00
}
2016-09-14 01:36:27 +03:00
if ctx . Int ( "peer_id" ) != 0 {
req . TargetPeerId = int32 ( ctx . Int ( "peer_id" ) )
} else {
2016-10-28 05:42:47 +03:00
nodePubHex , err := hex . DecodeString ( ctx . String ( "node_key" ) )
2016-09-14 01:36:27 +03:00
if err != nil {
return fmt . Errorf ( "unable to decode lightning id: %v" , err )
}
2016-10-28 05:42:47 +03:00
req . NodePubkey = nodePubHex
2016-09-14 01:36:27 +03:00
}
2016-07-08 01:35:06 +03:00
stream , err := client . OpenChannel ( ctxb , req )
2016-06-21 22:35:07 +03:00
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-21 22:35:07 +03:00
}
2016-07-08 01:35:06 +03:00
for {
resp , err := stream . Recv ( )
if err == io . EOF {
return nil
} else if err != nil {
return err
}
2016-08-31 02:54:49 +03:00
switch update := resp . Update . ( type ) {
2017-02-08 06:36:15 +03:00
case * lnrpc . OpenStatusUpdate_ChanPending :
txid , err := chainhash . NewHash ( update . ChanPending . Txid )
if err != nil {
return err
}
printJson ( struct {
FundingTxid string ` json:"funding_txid" `
} {
FundingTxid : txid . String ( ) ,
} ,
)
if ! ctx . Bool ( "block" ) {
return nil
}
2016-08-31 02:54:49 +03:00
case * lnrpc . OpenStatusUpdate_ChanOpen :
channelPoint := update . ChanOpen . ChannelPoint
2017-01-06 00:56:27 +03:00
txid , err := chainhash . NewHash ( channelPoint . FundingTxid )
2016-08-31 02:54:49 +03:00
if err != nil {
return err
}
index := channelPoint . OutputIndex
2017-01-30 01:56:31 +03:00
printJson ( struct {
2016-08-31 02:54:49 +03:00
ChannelPoint string ` json:"channel_point" `
} {
ChannelPoint : fmt . Sprintf ( "%v:%v" , txid , index ) ,
} ,
)
2016-07-08 01:35:06 +03:00
}
2016-06-21 22:35:07 +03:00
}
2016-06-29 23:01:08 +03:00
return nil
2016-06-21 22:35:07 +03:00
}
// TODO(roasbeef): also allow short relative channel ID.
var CloseChannelCommand = cli . Command {
Name : "closechannel" ,
Description : "Close an existing channel. The channel can be closed either " +
"cooperatively, or uncooperatively (forced)." ,
Usage : "closechannel funding_txid output_index time_limit allow_force" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "funding_txid" ,
Usage : "the txid of the channel's funding transaction" ,
} ,
cli . IntFlag {
Name : "output_index" ,
Usage : "the output index for the funding output of the funding " +
"transaction" ,
} ,
cli . StringFlag {
Name : "time_limit" ,
Usage : "a relative deadline afterwhich the attempt should be " +
"abandonded" ,
} ,
cli . BoolFlag {
Name : "force" ,
2016-09-12 22:28:28 +03:00
Usage : "after the time limit has passed, attempt an " +
2016-06-21 22:35:07 +03:00
"uncooperative closure" ,
} ,
2016-07-08 01:35:06 +03:00
cli . BoolFlag {
Name : "block" ,
Usage : "block until the channel is closed" ,
} ,
2016-06-21 22:35:07 +03:00
} ,
Action : closeChannel ,
}
2016-06-29 23:01:08 +03:00
func closeChannel ( ctx * cli . Context ) error {
2016-06-21 22:35:07 +03:00
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-06-21 22:35:07 +03:00
2017-01-06 00:56:27 +03:00
txid , err := chainhash . NewHashFromStr ( ctx . String ( "funding_txid" ) )
2016-06-21 22:35:07 +03:00
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-21 22:35:07 +03:00
}
2016-09-12 22:28:28 +03:00
// TODO(roasbeef): implement time deadline within server
2016-06-21 22:35:07 +03:00
req := & lnrpc . CloseChannelRequest {
ChannelPoint : & lnrpc . ChannelPoint {
2016-06-22 21:10:20 +03:00
FundingTxid : txid [ : ] ,
2016-06-21 22:35:07 +03:00
OutputIndex : uint32 ( ctx . Int ( "output_index" ) ) ,
} ,
2016-09-12 22:28:28 +03:00
Force : ctx . Bool ( "force" ) ,
2016-06-21 22:35:07 +03:00
}
2016-07-08 01:35:06 +03:00
stream , err := client . CloseChannel ( ctxb , req )
2016-06-21 22:35:07 +03:00
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-21 22:35:07 +03:00
}
2016-07-08 01:35:06 +03:00
for {
resp , err := stream . Recv ( )
if err == io . EOF {
return nil
} else if err != nil {
return err
}
2016-08-31 02:54:49 +03:00
switch update := resp . Update . ( type ) {
2017-02-08 06:36:15 +03:00
case * lnrpc . CloseStatusUpdate_ClosePending :
closingHash := update . ClosePending . Txid
txid , err := chainhash . NewHash ( closingHash )
if err != nil {
return err
}
printJson ( struct {
ClosingTXID string ` json:"closing_txid" `
} {
ClosingTXID : txid . String ( ) ,
} )
if ! ctx . Bool ( "block" ) {
return nil
}
2016-08-31 02:54:49 +03:00
case * lnrpc . CloseStatusUpdate_ChanClose :
closingHash := update . ChanClose . ClosingTxid
2017-01-06 00:56:27 +03:00
txid , err := chainhash . NewHash ( closingHash )
2016-08-31 02:54:49 +03:00
if err != nil {
return err
}
2017-01-30 01:56:31 +03:00
printJson ( struct {
2016-08-31 02:54:49 +03:00
ClosingTXID string ` json:"closing_txid" `
} {
ClosingTXID : txid . String ( ) ,
} )
}
2016-07-08 01:35:06 +03:00
}
2016-06-29 23:01:08 +03:00
return nil
2016-06-21 22:35:07 +03:00
}
var ListPeersCommand = cli . Command {
Name : "listpeers" ,
Description : "List all active, currently connected peers." ,
Action : listPeers ,
}
2016-06-29 23:01:08 +03:00
func listPeers ( ctx * cli . Context ) error {
2016-06-21 22:35:07 +03:00
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-06-21 22:35:07 +03:00
req := & lnrpc . ListPeersRequest { }
resp , err := client . ListPeers ( ctxb , req )
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-21 22:35:07 +03:00
}
printRespJson ( resp )
2016-06-29 23:01:08 +03:00
return nil
2016-06-21 22:35:07 +03:00
}
var WalletBalanceCommand = cli . Command {
Name : "walletbalance" ,
Description : "compute and display the wallet's current balance" ,
Usage : "walletbalance --witness_only=[true|false]" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "witness_only" ,
Usage : "if only witness outputs should be considered when " +
"calculating the wallet's balance" ,
} ,
} ,
Action : walletBalance ,
}
2016-06-29 23:01:08 +03:00
func walletBalance ( ctx * cli . Context ) error {
2016-06-21 22:35:07 +03:00
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-06-21 22:35:07 +03:00
req := & lnrpc . WalletBalanceRequest {
WitnessOnly : ctx . Bool ( "witness_only" ) ,
}
resp , err := client . WalletBalance ( ctxb , req )
if err != nil {
2016-06-29 23:01:08 +03:00
return err
2016-06-21 22:35:07 +03:00
}
printRespJson ( resp )
2016-06-29 23:01:08 +03:00
return nil
2016-06-21 22:35:07 +03:00
}
2016-07-06 04:58:41 +03:00
2016-09-15 21:59:51 +03:00
var ChannelBalanceCommand = cli . Command {
Name : "channelbalance" ,
Description : "returns the sum of the total available channel balance across all open channels" ,
2016-09-19 22:05:54 +03:00
Action : channelBalance ,
2016-09-15 21:59:51 +03:00
}
func channelBalance ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-15 21:59:51 +03:00
req := & lnrpc . ChannelBalanceRequest { }
resp , err := client . ChannelBalance ( ctxb , req )
if err != nil {
return err
}
printRespJson ( resp )
return nil
}
2016-07-06 04:58:41 +03:00
var GetInfoCommand = cli . Command {
Name : "getinfo" ,
Description : "returns basic information related to the active daemon" ,
Action : getInfo ,
}
func getInfo ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-07-06 04:58:41 +03:00
req := & lnrpc . GetInfoRequest { }
resp , err := client . GetInfo ( ctxb , req )
if err != nil {
return err
}
printRespJson ( resp )
return nil
}
2016-07-08 01:35:58 +03:00
var PendingChannelsCommand = cli . Command {
Name : "pendingchannels" ,
Description : "display information pertaining to pending channels" ,
Usage : "pendingchannels --status=[all|opening|closing]" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "open, o" ,
Usage : "display the status of new pending channels" ,
} ,
cli . BoolFlag {
Name : "close, c" ,
Usage : "display the status of channels being closed" ,
} ,
cli . BoolFlag {
Name : "all, a" ,
Usage : "display the status of channels in the " +
"process of being opened or closed" ,
} ,
} ,
Action : pendingChannels ,
}
func pendingChannels ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-07-08 01:35:58 +03:00
var channelStatus lnrpc . ChannelStatus
switch {
case ctx . Bool ( "all" ) :
channelStatus = lnrpc . ChannelStatus_ALL
case ctx . Bool ( "open" ) :
channelStatus = lnrpc . ChannelStatus_OPENING
case ctx . Bool ( "close" ) :
channelStatus = lnrpc . ChannelStatus_CLOSING
default :
channelStatus = lnrpc . ChannelStatus_ALL
}
req := & lnrpc . PendingChannelRequest { channelStatus }
resp , err := client . PendingChannels ( ctxb , req )
if err != nil {
return err
}
printRespJson ( resp )
return nil
}
2016-07-13 03:47:24 +03:00
2016-09-26 06:04:58 +03:00
var ListChannelsCommand = cli . Command {
Name : "listchannels" ,
Description : "list all open channels" ,
Usage : "listchannels --active_only" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "active_only, a" ,
Usage : "only list channels which are currently active" ,
} ,
} ,
Action : listChannels ,
}
func listChannels ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-26 06:04:58 +03:00
req := & lnrpc . ListChannelsRequest { }
resp , err := client . ListChannels ( ctxb , req )
if err != nil {
return err
}
2017-01-18 00:39:30 +03:00
// TODO(roasbeef): defer close the client for the all
2016-09-26 06:04:58 +03:00
printRespJson ( resp )
return nil
}
2016-07-13 03:47:24 +03:00
var SendPaymentCommand = cli . Command {
Name : "sendpayment" ,
Description : "send a payment over lightning" ,
2016-10-28 05:42:47 +03:00
Usage : "sendpayment --dest=[node_key] --amt=[in_satoshis] --payment_hash=[hash] --debug_send=[true|false]" ,
2016-07-13 03:47:24 +03:00
Flags : [ ] cli . Flag {
cli . StringFlag {
2016-09-21 02:19:15 +03:00
Name : "dest, d" ,
Usage : "the compressed identity pubkey of the " +
"payment recipient" ,
2016-07-13 03:47:24 +03:00
} ,
cli . IntFlag { // TODO(roasbeef): float64?
Name : "amt, a" ,
Usage : "number of satoshis to send" ,
} ,
cli . StringFlag {
Name : "payment_hash, r" ,
Usage : "the hash to use within the payment's HTLC" ,
} ,
2016-09-21 02:14:45 +03:00
cli . BoolFlag {
Name : "debug_send" ,
Usage : "use the debug rHash when sending the HTLC" ,
} ,
2017-01-03 02:38:00 +03:00
cli . StringFlag {
Name : "pay_req" ,
Usage : "a zbase32-check encoded payment request to fulfill" ,
2016-07-13 03:47:24 +03:00
} ,
} ,
Action : sendPaymentCommand ,
}
func sendPaymentCommand ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-07-13 03:47:24 +03:00
2017-01-03 02:38:00 +03:00
var req * lnrpc . SendRequest
if ctx . String ( "pay_req" ) != "" {
req = & lnrpc . SendRequest {
PaymentRequest : ctx . String ( "pay_req" ) ,
}
} else {
destNode , err := hex . DecodeString ( ctx . String ( "dest" ) )
2016-09-21 02:14:45 +03:00
if err != nil {
return err
}
2017-01-03 02:38:00 +03:00
if len ( destNode ) != 33 {
return fmt . Errorf ( "dest node pubkey must be exactly 33 bytes, is " +
"instead: %v" , len ( destNode ) )
}
req = & lnrpc . SendRequest {
Dest : destNode ,
Amt : int64 ( ctx . Int ( "amt" ) ) ,
}
if ! ctx . Bool ( "debug_send" ) {
rHash , err := hex . DecodeString ( ctx . String ( "payment_hash" ) )
if err != nil {
return err
}
if len ( rHash ) != 32 {
return fmt . Errorf ( "payment hash must be exactly 32 " +
"bytes, is instead %v" , len ( rHash ) )
}
req . PaymentHash = rHash
2016-09-21 02:14:45 +03:00
}
2016-07-13 03:47:24 +03:00
}
paymentStream , err := client . SendPayment ( context . Background ( ) )
if err != nil {
return err
}
if err := paymentStream . Send ( req ) ; err != nil {
return err
}
resp , err := paymentStream . Recv ( )
if err != nil {
return err
}
2016-07-22 02:18:42 +03:00
paymentStream . CloseSend ( )
2016-07-13 03:47:24 +03:00
printRespJson ( resp )
return nil
}
2016-07-15 14:02:59 +03:00
2016-09-19 22:05:54 +03:00
var AddInvoiceCommand = cli . Command {
Name : "addinvoice" ,
Description : "add a new invoice, expressing intent for a future payment" ,
Usage : "addinvoice --memo=[note] --receipt=[sig+contract hash] --value=[in_satoshis] --preimage=[32_byte_hash]" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "memo" ,
Usage : "an optional memo to attach along with the invoice" ,
} ,
cli . StringFlag {
Name : "receipt" ,
Usage : "an optional cryptographic receipt of payment" ,
} ,
cli . StringFlag {
Name : "preimage" ,
Usage : "the hex-encoded preimage which will allow settling an incoming HTLC payable to this preimage" ,
} ,
cli . IntFlag {
Name : "value" ,
Usage : "the value of this invoice in satoshis" ,
} ,
} ,
Action : addInvoice ,
}
func addInvoice ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-19 22:05:54 +03:00
preimage , err := hex . DecodeString ( ctx . String ( "preimage" ) )
if err != nil {
return fmt . Errorf ( "unable to parse preimage: %v" , err )
}
receipt , err := hex . DecodeString ( ctx . String ( "receipt" ) )
if err != nil {
return fmt . Errorf ( "unable to parse receipt: %v" , err )
}
invoice := & lnrpc . Invoice {
Memo : ctx . String ( "memo" ) ,
Receipt : receipt ,
RPreimage : preimage ,
Value : int64 ( ctx . Int ( "value" ) ) ,
}
resp , err := client . AddInvoice ( context . Background ( ) , invoice )
if err != nil {
return err
}
2017-01-30 01:56:31 +03:00
printJson ( struct {
2017-01-03 02:37:10 +03:00
RHash string ` json:"r_hash" `
PayReq string ` json:"pay_req" `
2016-09-19 22:05:54 +03:00
} {
2017-01-03 02:37:10 +03:00
RHash : hex . EncodeToString ( resp . RHash ) ,
PayReq : resp . PaymentRequest ,
2016-09-19 22:05:54 +03:00
} )
return nil
}
var LookupInvoiceCommand = cli . Command {
Name : "lookupinvoice" ,
Description : "lookup an existing invoice by its payment hash" ,
Usage : "lookupinvoice --rhash=[32_byte_hash]" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "rhash" ,
Usage : "the payment hash of the invoice to query for, the hash " +
"should be a hex-encoded string" ,
} ,
} ,
Action : lookupInvoice ,
}
func lookupInvoice ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-19 22:05:54 +03:00
rHash , err := hex . DecodeString ( ctx . String ( "rhash" ) )
if err != nil {
return err
}
req := & lnrpc . PaymentHash {
RHash : rHash ,
}
invoice , err := client . LookupInvoice ( context . Background ( ) , req )
if err != nil {
return err
}
printRespJson ( invoice )
return nil
}
var ListInvoicesCommand = cli . Command {
Name : "listinvoices" ,
Usage : "listinvoice --pending_only=[true|false]" ,
Description : "list all invoices currently stored" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "pending_only" ,
Usage : "toggles if all invoices should be returned, or only " +
"those that are currently unsettled" ,
} ,
} ,
Action : listInvoices ,
}
func listInvoices ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-19 22:05:54 +03:00
pendingOnly := true
if ! ctx . Bool ( "pending_only" ) {
pendingOnly = false
}
req := & lnrpc . ListInvoiceRequest {
PendingOnly : pendingOnly ,
}
invoices , err := client . ListInvoices ( context . Background ( ) , req )
if err != nil {
return err
}
printRespJson ( invoices )
return nil
}
2016-12-27 08:52:15 +03:00
var DescribeGraphCommand = cli . Command {
Name : "describegraph" ,
Description : "prints a human readable version of the known channel " +
"graph from the PoV of the node" ,
2017-01-24 07:32:17 +03:00
Usage : "describegraph" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
2017-01-30 02:38:04 +03:00
Name : "render" ,
2017-01-25 05:07:15 +03:00
Usage : "If true, then an image of graph will be generated and displayed. The generated image is stored within the current directory with a file name of 'graph.svg'" ,
2017-01-24 07:32:17 +03:00
} ,
} ,
2016-12-27 08:52:15 +03:00
Action : describeGraph ,
2016-08-31 02:54:49 +03:00
}
2016-08-20 23:49:35 +03:00
2016-12-27 08:52:15 +03:00
func describeGraph ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-08-21 17:46:54 +03:00
2016-12-27 08:52:15 +03:00
req := & lnrpc . ChannelGraphRequest { }
2016-12-28 02:25:43 +03:00
2016-12-27 08:52:15 +03:00
graph , err := client . DescribeGraph ( context . Background ( ) , req )
2016-08-21 17:46:54 +03:00
if err != nil {
return err
}
2017-01-24 07:32:17 +03:00
// If the draw flag is on, then we'll use the 'dot' command to create a
// visualization of the graph itself.
2017-01-30 02:38:04 +03:00
if ctx . Bool ( "render" ) {
2017-01-24 07:32:17 +03:00
return drawChannelGraph ( graph )
}
2016-12-27 08:52:15 +03:00
printRespJson ( graph )
2016-08-21 17:46:54 +03:00
return nil
}
2017-01-25 05:07:15 +03:00
// normalizeFunc is a factory function which returns a function that normalizes
// the capacity of of edges within the graph. The value of the returned
// function can be used to either plot the capacities, or to use a weight in a
// rendering of the graph.
func normalizeFunc ( edges [ ] * lnrpc . ChannelEdge , scaleFactor float64 ) func ( int64 ) float64 {
var (
min float64 = math . MaxInt64
max float64
)
for _ , edge := range edges {
// In order to obtain saner values, we reduce the capacity of a
// channel to it's base 2 logarithm.
z := math . Log2 ( float64 ( edge . Capacity ) )
if z < min {
min = z
}
if z > max {
max = z
}
}
return func ( x int64 ) float64 {
y := math . Log2 ( float64 ( x ) )
2017-01-26 05:27:27 +03:00
// TODO(roasbeef): results in min being zero
2017-01-25 05:07:15 +03:00
return float64 ( y - min ) / float64 ( max - min ) * scaleFactor
}
}
2017-01-24 07:32:17 +03:00
func drawChannelGraph ( graph * lnrpc . ChannelGraph ) error {
// First we'll create a temporary file that we'll write the compiled
// string that describes our graph in the dot format to.
tempDotFile , err := ioutil . TempFile ( "" , "" )
if err != nil {
return err
}
defer os . Remove ( tempDotFile . Name ( ) )
// Next, we'll create (or re-create) the file that the final graph
// image will be written to.
2017-01-25 05:07:15 +03:00
imageFile , err := os . Create ( "graph.svg" )
2017-01-24 07:32:17 +03:00
if err != nil {
return err
}
// With our temporary files set up, we'll initialize the graphviz
// object that we'll use to draw our graph.
graphName := "LightningNetwork"
graphCanvas := gographviz . NewGraph ( )
graphCanvas . SetName ( graphName )
2017-01-25 05:07:15 +03:00
graphCanvas . SetDir ( false )
2017-01-24 07:32:17 +03:00
2017-01-26 05:27:27 +03:00
const numKeyChars = 10
truncateStr := func ( k string , n uint ) string {
return k [ : n ]
}
2017-01-24 07:32:17 +03:00
// For each node within the graph, we'll add a new vertex to the graph.
for _ , node := range graph . Nodes {
// Rather than using the entire hex-encoded string, we'll only
// use the first 10 characters. We also add a prefix of "Z" as
// graphviz is unable to parse the compressed pubkey as a
// non-integer.
//
// TODO(roasbeef): should be able to get around this?
2017-01-26 05:27:27 +03:00
nodeID := fmt . Sprintf ( ` "%v" ` , truncateStr ( node . PubKey , numKeyChars ) )
2017-01-24 07:32:17 +03:00
graphCanvas . AddNode ( graphName , nodeID , gographviz . Attrs { } )
}
2017-01-25 05:07:15 +03:00
normalize := normalizeFunc ( graph . Edges , 3 )
2017-01-24 07:32:17 +03:00
// Similarly, for each edge we'll add an edge between the corresponding
// nodes added to the graph above.
for _ , edge := range graph . Edges {
// Once again, we add a 'Z' prefix so we're compliant with the
// dot grammar.
2017-01-26 05:27:27 +03:00
src := fmt . Sprintf ( ` "%v" ` , truncateStr ( edge . Node1Pub , numKeyChars ) )
dest := fmt . Sprintf ( ` "%v" ` , truncateStr ( edge . Node2Pub , numKeyChars ) )
2017-01-24 07:32:17 +03:00
// The weight for our edge will be the total capacity of the
// channel, in BTC.
2017-01-25 05:07:15 +03:00
// TODO(roasbeef): can also factor in the edges time-lock delta
// and fee information
2017-01-24 07:32:17 +03:00
amt := btcutil . Amount ( edge . Capacity ) . ToBTC ( )
edgeWeight := strconv . FormatFloat ( amt , 'f' , - 1 , 64 )
// The label for each edge will simply be a truncated version
// of it's channel ID.
2017-01-26 05:27:27 +03:00
chanIDStr := strconv . FormatUint ( edge . ChannelId , 10 )
edgeLabel := fmt . Sprintf ( ` "cid:%v" ` , truncateStr ( chanIDStr , 7 ) )
2017-01-24 07:32:17 +03:00
2017-01-25 05:07:15 +03:00
// We'll also use a normalized version of the channels'
// capacity in satoshis in order to modulate the "thickness" of
// the line that creates the edge within the graph.
normalizedCapacity := normalize ( edge . Capacity )
edgeThickness := strconv . FormatFloat ( normalizedCapacity , 'f' , - 1 , 64 )
2017-01-26 05:27:27 +03:00
// TODO(roasbeef): color code based on percentile capacity
2017-01-25 05:07:15 +03:00
graphCanvas . AddEdge ( src , dest , false , gographviz . Attrs {
"penwidth" : edgeThickness ,
"weight" : edgeWeight ,
"label" : edgeLabel ,
} )
2017-01-24 07:32:17 +03:00
}
// With the declarative generation of the graph complete, we now write
// the dot-string description of the graph
graphDotString := graphCanvas . String ( )
if _ , err := tempDotFile . WriteString ( graphDotString ) ; err != nil {
return err
}
if err := tempDotFile . Sync ( ) ; err != nil {
return err
}
2017-01-26 05:29:24 +03:00
var errBuffer bytes . Buffer
2017-01-24 07:32:17 +03:00
// Once our dot file has been written to disk, we can use the dot
// command itself to generate the drawn rendering of the graph
// described.
2017-01-25 05:07:15 +03:00
drawCmd := exec . Command ( "dot" , "-T" + "svg" , "-o" + imageFile . Name ( ) ,
2017-01-24 07:32:17 +03:00
tempDotFile . Name ( ) )
2017-01-26 05:29:24 +03:00
drawCmd . Stderr = & errBuffer
2017-01-24 07:32:17 +03:00
if err := drawCmd . Run ( ) ; err != nil {
2017-01-26 05:29:24 +03:00
fmt . Println ( "error rendering graph: " , errBuffer . String ( ) )
fmt . Println ( "dot: " , graphDotString )
2017-01-24 07:32:17 +03:00
return err
}
2017-01-26 05:29:24 +03:00
errBuffer . Reset ( )
2017-01-24 07:32:17 +03:00
// Finally, we'll open the drawn graph to display to the user.
openCmd := exec . Command ( "open" , imageFile . Name ( ) )
2017-01-26 05:29:24 +03:00
openCmd . Stderr = & errBuffer
2017-01-24 07:32:17 +03:00
if err := openCmd . Run ( ) ; err != nil {
2017-01-26 05:29:24 +03:00
fmt . Println ( "error opening rendered graph image: " ,
errBuffer . String ( ) )
2017-01-24 07:32:17 +03:00
return err
}
return nil
}
2016-12-05 14:59:36 +03:00
var ListPaymentsCommand = cli . Command {
Name : "listpayments" ,
Usage : "listpayments" ,
Description : "list all outgoing payments" ,
2016-12-27 08:52:15 +03:00
Action : listPayments ,
2016-12-05 14:59:36 +03:00
}
func listPayments ( ctx * cli . Context ) error {
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-12-05 14:59:36 +03:00
req := & lnrpc . ListPaymentsRequest { }
payments , err := client . ListPayments ( context . Background ( ) , req )
if err != nil {
return err
}
printRespJson ( payments )
2016-12-28 02:45:10 +03:00
return nil
}
var GetChanInfoCommand = cli . Command {
Name : "getchaninfo" ,
2017-01-22 04:03:25 +03:00
Usage : "getchaninfo --chan_id=[8_byte_channel_id]" ,
2016-12-28 02:45:10 +03:00
Description : "prints out the latest authenticated state for a " +
"particular channel" ,
Flags : [ ] cli . Flag {
cli . IntFlag {
Name : "chan_id" ,
Usage : "the 8-byte compact channel ID to query for" ,
} ,
} ,
Action : getChanInfo ,
}
func getChanInfo ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-12-28 02:45:10 +03:00
req := & lnrpc . ChanInfoRequest {
ChanId : uint64 ( ctx . Int ( "chan_id" ) ) ,
}
chanInfo , err := client . GetChanInfo ( ctxb , req )
if err != nil {
return err
}
printRespJson ( chanInfo )
return nil
}
var GetNodeInfoCommand = cli . Command {
Name : "getnodeinfo" ,
Usage : "getnodeinfo --pub_key=[33_byte_serialized_pub_lky]" ,
Description : "prints out the latest authenticated node state for an " +
"advertised node" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "pub_key" ,
Usage : "the 33-byte hex-encoded compressed public of the target " +
"node" ,
} ,
} ,
Action : getNodeInfo ,
}
func getNodeInfo ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-12-28 02:45:10 +03:00
req := & lnrpc . NodeInfoRequest {
PubKey : ctx . String ( "pub_key" ) ,
}
nodeInfo , err := client . GetNodeInfo ( ctxb , req )
if err != nil {
return err
}
printRespJson ( nodeInfo )
return nil
}
var QueryRouteCommand = cli . Command {
Name : "queryroute" ,
Usage : "queryroute --dest=[dest_pub_key] --amt=[amt_to_send_in_satoshis]" ,
Description : "queries the channel router for a potential path to the destination that has sufficient flow for the amount including fees" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "dest" ,
Usage : "the 33-byte hex-encoded public key for the payment " +
"destination" ,
} ,
cli . IntFlag {
Name : "amt" ,
Usage : "the amount to send expressed in satoshis" ,
} ,
} ,
Action : queryRoute ,
}
func queryRoute ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-12-28 02:45:10 +03:00
req := & lnrpc . RouteRequest {
PubKey : ctx . String ( "dest" ) ,
Amt : int64 ( ctx . Int ( "amt" ) ) ,
}
route , err := client . QueryRoute ( ctxb , req )
if err != nil {
return err
}
printRespJson ( route )
return nil
}
var GetNetworkInfoCommand = cli . Command {
Name : "getnetworkinfo" ,
Usage : "getnetworkinfo" ,
Description : "returns a set of statistics pertaining to the known channel " +
"graph" ,
Action : getNetworkInfo ,
}
func getNetworkInfo ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-12-28 02:45:10 +03:00
req := & lnrpc . NetworkInfoRequest { }
netInfo , err := client . GetNetworkInfo ( ctxb , req )
if err != nil {
return err
}
2016-12-05 14:59:36 +03:00
2016-12-28 02:45:10 +03:00
printRespJson ( netInfo )
2016-12-05 14:59:36 +03:00
return nil
2016-12-27 08:52:15 +03:00
}
2017-01-15 05:19:02 +03:00
var DebugLevel = cli . Command {
Name : "debuglevel" ,
Usage : "debuglevel [--show|--level=<level_spec>]" ,
Description : "Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems" ,
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "show" ,
Usage : "if true, then the list of available sub-systems will be printed out" ,
} ,
cli . StringFlag {
Name : "level" ,
Usage : "the level specification to target either a coarse logging level, or granular set of specific sub-systems with loggin levels for each" ,
} ,
} ,
Action : debugLevel ,
}
func debugLevel ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2017-01-15 05:19:02 +03:00
req := & lnrpc . DebugLevelRequest {
Show : ctx . Bool ( "show" ) ,
LevelSpec : ctx . String ( "level" ) ,
}
resp , err := client . DebugLevel ( ctxb , req )
if err != nil {
return err
}
printRespJson ( resp )
return nil
}
2017-01-18 00:39:30 +03:00
var DecodePayReq = cli . Command {
Name : "decodepayreq" ,
Usage : "decodepayreq --pay_req=[encoded_pay_req]" ,
Description : "Decode the passed payment request revealing the destination, payment hash and value of the payment request" ,
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "pay_req" ,
Usage : "the zpay32 encoded payment request" ,
} ,
} ,
Action : decodePayReq ,
}
func decodePayReq ( ctx * cli . Context ) error {
ctxb := context . Background ( )
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2017-01-18 00:39:30 +03:00
2017-01-30 01:56:31 +03:00
if ctx . String ( "pay_req" ) == "" {
return errors . New ( "the --pay_req argument cannot be empty" )
2017-01-18 00:39:30 +03:00
}
2017-01-30 01:56:31 +03:00
resp , err := client . DecodePayReq ( ctxb , & lnrpc . PayReqString {
PayReq : ctx . String ( "pay_req" ) ,
} )
2017-01-18 00:39:30 +03:00
if err != nil {
return err
}
printRespJson ( resp )
return nil
}