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"
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-02-23 22:56:47 +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" )
2017-03-16 22:06:12 +03:00
out . WriteString ( "\n" )
2015-12-30 05:58:58 +03:00
out . WriteTo ( os . Stdout )
}
2017-02-23 22:56:47 +03:00
func printRespJSON ( resp proto . Message ) {
2017-01-30 01:56:31 +03:00
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 )
}
2017-02-24 16:32:33 +03:00
var newAddressCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "newaddress" ,
Usage : "generates a new address." ,
ArgsUsage : "address-type" ,
Description : "Generate a wallet new address. Address-types has to be one of:\n" +
" - p2wkh: Push to witness key hash\n" +
" - np2wkh: Push to nested witness key hash\n" +
" - p2pkh: Push to public key hash" ,
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
2017-03-03 01:23:16 +03:00
stringAddrType := ctx . Args ( ) . First ( )
2016-04-25 06:27:19 +03:00
// 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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( addr )
2016-06-29 23:01:08 +03:00
return nil
2015-12-30 05:31:03 +03:00
}
2017-02-24 16:32:33 +03:00
var sendCoinsCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "sendcoins" ,
Usage : "send bitcoin on-chain to an address" ,
ArgsUsage : "addr amt" ,
Description : "Send amt coins in satoshis to the BASE58 encoded bitcoin address addr.\n\n" +
" Positional arguments and flags can be used interchangeably but not at the same time!" ,
2016-06-29 21:29:21 +03:00
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "addr" ,
2017-03-03 01:23:16 +03:00
Usage : "the BASE58 encoded bitcoin address to send coins to on-chain" ,
2016-06-29 21:29:21 +03:00
} ,
// TODO(roasbeef): switch to BTC on command line? int may not be sufficient
2017-03-03 01:23:16 +03:00
cli . Int64Flag {
2016-06-29 21:29:21 +03:00
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 {
2017-03-03 01:23:16 +03:00
var (
addr string
amt int64
err error
)
args := ctx . Args ( )
if ctx . NArg ( ) == 0 && ctx . NumFlags ( ) == 0 {
cli . ShowCommandHelp ( ctx , "sendcoins" )
return nil
}
switch {
case ctx . IsSet ( "addr" ) :
addr = ctx . String ( "addr" )
case args . Present ( ) :
addr = args . First ( )
args = args . Tail ( )
default :
return fmt . Errorf ( "Address argument missing" )
}
switch {
case ctx . IsSet ( "amt" ) :
amt = ctx . Int64 ( "amt" )
case args . Present ( ) :
amt , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
default :
return fmt . Errorf ( "Amount argument missing" )
}
if err != nil {
return fmt . Errorf ( "unable to decode amount: %v" , err )
}
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 {
2017-03-03 01:23:16 +03:00
Addr : addr ,
Amount : amt ,
2016-06-29 21:29:21 +03:00
}
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( txid )
2016-06-29 23:01:08 +03:00
return nil
2016-06-29 21:29:21 +03:00
}
2017-02-24 16:32:33 +03:00
var sendManyCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "sendmany" ,
Usage : "send bitcoin on-chain to multiple addresses." ,
ArgsUsage : "send-json-string" ,
2016-06-29 21:29:21 +03:00
Description : "create and broadcast a transaction paying the specified " +
2017-03-03 01:23:16 +03:00
"amount(s) to the passed address(es)\n" +
" 'send-json-string' decodes addresses and the amount to send " +
"respectively in the following format.\n" +
` ' { "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
2017-03-03 01:23:16 +03:00
jsonMap := ctx . Args ( ) . First ( )
2015-12-30 05:31:03 +03:00
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
2017-02-23 22:09:34 +03:00
txid , err := client . SendMany ( ctxb , & lnrpc . SendManyRequest {
AddrToAmount : amountToAddr ,
} )
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
}
2017-02-23 22:56:47 +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
2017-02-24 16:32:33 +03:00
var connectCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "connect" ,
Usage : "connect to a remote lnd peer" ,
ArgsUsage : "<pubkey>@host" ,
2017-01-10 06:09:45 +03:00
Flags : [ ] cli . Flag {
cli . BoolFlag {
Name : "perm" ,
2017-03-03 01:23:16 +03:00
Usage : "If set, the daemon will attempt to persistently " +
"connect to the target peer.\n" +
" If not, the call will be synchronous." ,
2017-01-10 06:09:45 +03:00
} ,
} ,
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
2017-03-03 01:23:16 +03:00
targetAddress := ctx . Args ( ) . First ( )
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
}
2017-02-23 22:56:47 +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
2017-03-03 01:23:16 +03:00
// TODO(roasbeef): change default number of confirmations
2017-02-24 16:32:33 +03:00
var openChannelCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "openchannel" ,
Usage : "Open a channel to an existing peer." ,
Description : "Attempt to open a new channel to an existing peer with the key node-key, " +
"optionally blocking until the channel is 'open'. " +
"The channel will be initialized with local-amt satoshis local and push-amt " +
"satoshis for the remote node. Once the " +
2016-09-14 01:36:27 +03:00
"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-03-03 01:23:16 +03:00
ArgsUsage : "node-key local-amt push-amt [num-confs]" ,
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'" ,
2017-03-03 01:23:16 +03:00
Value : 1 ,
2016-06-21 22:35:07 +03:00
} ,
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 ( )
2017-03-03 22:33:16 +03:00
2017-03-03 01:23:16 +03:00
args := ctx . Args ( )
var err error
// Show command help if no arguments provided
if ctx . NArg ( ) == 0 && ctx . NumFlags ( ) == 0 {
cli . ShowCommandHelp ( ctx , "openchannel" )
return nil
}
2016-06-21 22:35:07 +03:00
2017-03-03 01:23:16 +03:00
if ctx . IsSet ( "peer_id" ) && ctx . IsSet ( "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-03-03 01:23:16 +03:00
NumConfs : uint32 ( ctx . Int ( "num_confs" ) ) ,
2016-06-21 22:35:07 +03:00
}
2017-03-03 01:23:16 +03:00
switch {
case ctx . IsSet ( "peer_id" ) :
2016-09-14 01:36:27 +03:00
req . TargetPeerId = int32 ( ctx . Int ( "peer_id" ) )
2017-03-03 01:23:16 +03:00
case ctx . IsSet ( "node_key" ) :
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 {
2017-03-03 01:23:16 +03:00
return fmt . Errorf ( "unable to decode node public key: %v" , err )
2016-09-14 01:36:27 +03:00
}
2016-10-28 05:42:47 +03:00
req . NodePubkey = nodePubHex
2017-03-03 01:23:16 +03:00
case args . Present ( ) :
nodePubHex , err := hex . DecodeString ( args . First ( ) )
if err != nil {
return fmt . Errorf ( "unable to decode node public key: %v" , err )
}
args = args . Tail ( )
req . NodePubkey = nodePubHex
default :
2017-03-03 22:33:16 +03:00
return fmt . Errorf ( "node id argument missing" )
2017-03-03 01:23:16 +03:00
}
switch {
case ctx . IsSet ( "local_amt" ) :
req . LocalFundingAmount = int64 ( ctx . Int ( "local_amt" ) )
case args . Present ( ) :
req . LocalFundingAmount , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
if err != nil {
return fmt . Errorf ( "unable to decode local amt: %v" , err )
}
args = args . Tail ( )
default :
return fmt . Errorf ( "local amt argument missing" )
}
2017-03-03 22:33:16 +03:00
if ctx . IsSet ( "push_amt" ) {
2017-03-03 01:23:16 +03:00
req . PushSat = int64 ( ctx . Int ( "push_amt" ) )
2017-03-03 22:33:16 +03:00
} else if args . Present ( ) {
2017-03-03 01:23:16 +03:00
req . PushSat , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
if err != nil {
return fmt . Errorf ( "unable to decode push amt: %v" , err )
}
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
}
2017-02-23 22:56:47 +03:00
printJSON ( struct {
2017-02-08 06:36:15 +03:00
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-02-23 22:56:47 +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
}
}
// TODO(roasbeef): also allow short relative channel ID.
2017-02-23 22:56:47 +03:00
2017-02-24 16:32:33 +03:00
var closeChannelCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "closechannel" ,
Usage : "Close an existing channel." ,
2016-06-21 22:35:07 +03:00
Description : "Close an existing channel. The channel can be closed either " +
"cooperatively, or uncooperatively (forced)." ,
2017-03-03 01:23:16 +03:00
ArgsUsage : "funding_txid [output_index [time_limit]]" ,
2016-06-21 22:35:07 +03:00
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-03-03 01:23:16 +03:00
args := ctx . Args ( )
var (
txid string
err error
)
// Show command help if no arguments provieded
if ctx . NArg ( ) == 0 && ctx . NumFlags ( ) == 0 {
cli . ShowCommandHelp ( ctx , "closeChannel" )
return nil
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 {
2017-03-03 01:23:16 +03:00
ChannelPoint : & lnrpc . ChannelPoint { } ,
Force : ctx . Bool ( "force" ) ,
}
switch {
case ctx . IsSet ( "funding_txid" ) :
txid = ctx . String ( "funding_txid" )
case args . Present ( ) :
txid = args . First ( )
args = args . Tail ( )
default :
return fmt . Errorf ( "funding txid argument missing" )
}
txidhash , err := chainhash . NewHashFromStr ( txid )
if err != nil {
return err
}
req . ChannelPoint . FundingTxid = txidhash [ : ]
switch {
case ctx . IsSet ( "output_index" ) :
req . ChannelPoint . OutputIndex = uint32 ( ctx . Int ( "output_index" ) )
case args . Present ( ) :
index , err := strconv . ParseInt ( args . First ( ) , 10 , 32 )
if err != nil {
return fmt . Errorf ( "unable to decode output index: %v" , err )
}
req . ChannelPoint . OutputIndex = uint32 ( index )
default :
req . ChannelPoint . OutputIndex = 0
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
}
2017-02-23 22:56:47 +03:00
printJSON ( struct {
2017-02-08 06:36:15 +03:00
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-02-23 22:56:47 +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-21 22:35:07 +03:00
}
2017-02-24 16:32:33 +03:00
var listPeersCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "listpeers" ,
Usage : "List all active, currently connected peers." ,
Action : listPeers ,
2016-06-21 22:35:07 +03:00
}
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2016-06-29 23:01:08 +03:00
return nil
2016-06-21 22:35:07 +03:00
}
2017-02-24 16:32:33 +03:00
var walletBalanceCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "walletbalance" ,
Usage : "compute and display the wallet's current balance" ,
2016-06-21 22:35:07 +03:00
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
}
2017-02-23 22:56:47 +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
2017-02-24 16:32:33 +03:00
var channelBalanceCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "channelbalance" ,
Usage : "returns the sum of the total available channel balance across all open channels" ,
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2016-09-15 21:59:51 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var getInfoCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "getinfo" ,
Usage : "returns basic information related to the active daemon" ,
Action : getInfo ,
2016-07-06 04:58:41 +03:00
}
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2016-07-06 04:58:41 +03:00
return nil
}
2016-07-08 01:35:58 +03:00
2017-02-24 16:32:33 +03:00
var pendingChannelsCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "pendingchannels" ,
Usage : "display information pertaining to pending channels" ,
2016-07-08 01:35:58 +03:00
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
}
2017-02-23 22:09:34 +03:00
req := & lnrpc . PendingChannelRequest { Status : channelStatus }
2016-07-08 01:35:58 +03:00
resp , err := client . PendingChannels ( ctxb , req )
if err != nil {
return err
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2016-07-08 01:35:58 +03:00
return nil
}
2016-07-13 03:47:24 +03:00
2017-02-24 16:32:33 +03:00
var listChannelsCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "listchannels" ,
Usage : "list all open channels" ,
2016-09-26 06:04:58 +03:00
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
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2016-09-26 06:04:58 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var sendPaymentCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "sendpayment" ,
Usage : "send a payment over lightning" ,
ArgsUsage : "(destination amount payment_hash " +
"| --pay_req=[payment request])" ,
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
} ,
2017-03-03 01:23:16 +03:00
cli . Int64Flag {
2016-07-13 03:47:24 +03:00
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
} ,
} ,
2017-02-24 16:32:33 +03:00
Action : sendPayment ,
2016-07-13 03:47:24 +03:00
}
2017-02-24 16:32:33 +03:00
func sendPayment ( 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-03-03 01:23:16 +03:00
// Show command help if no arguments provieded
if ctx . NArg ( ) == 0 && ctx . NumFlags ( ) == 0 {
cli . ShowCommandHelp ( ctx , "sendpayment" )
return nil
}
2017-01-03 02:38:00 +03:00
var req * lnrpc . SendRequest
2017-03-03 01:23:16 +03:00
if ctx . IsSet ( "pay_req" ) {
2017-01-03 02:38:00 +03:00
req = & lnrpc . SendRequest {
PaymentRequest : ctx . String ( "pay_req" ) ,
}
} else {
2017-03-03 01:23:16 +03:00
args := ctx . Args ( )
var (
destNode [ ] byte
err error
amount int64
)
switch {
case ctx . IsSet ( "dest" ) :
destNode , err = hex . DecodeString ( ctx . String ( "dest" ) )
case args . Present ( ) :
destNode , err = hex . DecodeString ( args . First ( ) )
args = args . Tail ( )
default :
return fmt . Errorf ( "destination txid argument missing" )
}
2016-09-21 02:14:45 +03:00
if err != nil {
return err
}
2017-03-03 01:23:16 +03:00
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 ) )
}
2017-03-03 01:23:16 +03:00
if ctx . IsSet ( "amt" ) {
amount = ctx . Int64 ( "amt" )
} else if args . Present ( ) {
amount , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
args = args . Tail ( )
if err != nil {
return fmt . Errorf ( "unable to decode payment amount: %v" , err )
}
}
2017-01-03 02:38:00 +03:00
req = & lnrpc . SendRequest {
Dest : destNode ,
2017-03-03 01:23:16 +03:00
Amt : amount ,
2017-01-03 02:38:00 +03:00
}
2017-03-03 01:23:16 +03:00
if ctx . Bool ( "debug_send" ) && ( ctx . IsSet ( "payment_hash" ) || args . Present ( ) ) {
return fmt . Errorf ( "do not provide a payment hash with debug send" )
} else if ! ctx . Bool ( "debug_send" ) {
var rHash [ ] byte
switch {
case ctx . IsSet ( "payment_hash" ) :
rHash , err = hex . DecodeString ( ctx . String ( "payment_hash" ) )
case args . Present ( ) :
rHash , err = hex . DecodeString ( args . First ( ) )
default :
return fmt . Errorf ( "payment hash argument missing" )
}
2017-01-03 02:38:00 +03:00
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 ( )
2017-02-23 22:56:47 +03:00
printJSON ( struct {
2017-02-21 10:58:17 +03:00
P string ` json:"payment_preimage" `
R * lnrpc . Route ` json:"payment_route" `
} {
P : hex . EncodeToString ( resp . PaymentPreimage ) ,
R : resp . PaymentRoute ,
} )
2016-07-13 03:47:24 +03:00
return nil
}
2016-07-15 14:02:59 +03:00
2017-02-24 16:32:33 +03:00
var addInvoiceCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "addinvoice" ,
Usage : "add a new invoice." ,
Description : "Add a new invoice, expressing intent for a future payment. " +
"The value of the invoice in satoshis and a 32 byte hash preimage are neccesary for the creation" ,
ArgsUsage : "value preimage" ,
2016-09-19 22:05:54 +03:00
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" ,
2017-03-03 01:23:16 +03:00
Usage : "the hex-encoded preimage (32 byte) which will allow settling an incoming HTLC payable to this preimage" ,
2016-09-19 22:05:54 +03:00
} ,
2017-03-03 01:23:16 +03:00
cli . Int64Flag {
2016-09-19 22:05:54 +03:00
Name : "value" ,
Usage : "the value of this invoice in satoshis" ,
} ,
} ,
Action : addInvoice ,
}
func addInvoice ( ctx * cli . Context ) error {
2017-03-03 01:23:16 +03:00
var (
preimage [ ] byte
receipt [ ] byte
value int64
err error
)
2017-01-30 01:51:30 +03:00
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
2016-09-19 22:05:54 +03:00
2017-03-03 01:23:16 +03:00
args := ctx . Args ( )
switch {
case ctx . IsSet ( "value" ) :
value = ctx . Int64 ( "value" )
case args . Present ( ) :
value , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
args = args . Tail ( )
if err != nil {
return fmt . Errorf ( "unable to decode value argument: %v" , err )
}
default :
return fmt . Errorf ( "value argument missing" )
}
switch {
case ctx . IsSet ( "preimage" ) :
preimage , err = hex . DecodeString ( ctx . String ( "preimage" ) )
case args . Present ( ) :
preimage , err = hex . DecodeString ( args . First ( ) )
}
2016-09-19 22:05:54 +03:00
if err != nil {
return fmt . Errorf ( "unable to parse preimage: %v" , err )
}
2017-03-03 01:23:16 +03:00
receipt , err = hex . DecodeString ( ctx . String ( "receipt" ) )
2016-09-19 22:05:54 +03:00
if err != nil {
return fmt . Errorf ( "unable to parse receipt: %v" , err )
}
invoice := & lnrpc . Invoice {
Memo : ctx . String ( "memo" ) ,
Receipt : receipt ,
RPreimage : preimage ,
2017-03-03 01:23:16 +03:00
Value : value ,
2016-09-19 22:05:54 +03:00
}
resp , err := client . AddInvoice ( context . Background ( ) , invoice )
if err != nil {
return err
}
2017-02-23 22:56:47 +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
}
2017-02-24 16:32:33 +03:00
var lookupInvoiceCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "lookupinvoice" ,
Usage : "Lookup an existing invoice by its payment hash." ,
ArgsUsage : "rhash" ,
2016-09-19 22:05:54 +03:00
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "rhash" ,
2017-03-03 01:23:16 +03:00
Usage : "the 32 byte payment hash of the invoice to query for, the hash " +
2016-09-19 22:05:54 +03:00
"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
2017-03-03 01:23:16 +03:00
var (
rHash [ ] byte
err error
)
switch {
case ctx . IsSet ( "rhash" ) :
rHash , err = hex . DecodeString ( ctx . String ( "rhash" ) )
case ctx . Args ( ) . Present ( ) :
rHash , err = hex . DecodeString ( ctx . Args ( ) . First ( ) )
default :
return fmt . Errorf ( "rhash argument missing" )
}
2016-09-19 22:05:54 +03:00
if err != nil {
2017-03-03 01:23:16 +03:00
return fmt . Errorf ( "unable to decode rhash argument: %v" , err )
2016-09-19 22:05:54 +03:00
}
req := & lnrpc . PaymentHash {
RHash : rHash ,
}
invoice , err := client . LookupInvoice ( context . Background ( ) , req )
if err != nil {
return err
}
2017-02-23 22:56:47 +03:00
printRespJSON ( invoice )
2016-09-19 22:05:54 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var listInvoicesCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "listinvoices" ,
Usage : "List all invoices currently stored." ,
2016-09-19 22:05:54 +03:00
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( invoices )
2016-09-19 22:05:54 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var describeGraphCommand = cli . Command {
2016-12-27 08:52:15 +03:00
Name : "describegraph" ,
Description : "prints a human readable version of the known channel " +
"graph from the PoV of the node" ,
2017-03-03 01:23:16 +03:00
Usage : "describe the network graph" ,
2017-01-24 07:32:17 +03:00
Flags : [ ] cli . Flag {
cli . BoolFlag {
2017-01-30 02:38:04 +03:00
Name : "render" ,
2017-03-03 01:23:16 +03:00
Usage : "If set, 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 )
}
2017-02-23 22:56:47 +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-03-09 07:44:32 +03:00
return ( y - min ) / ( max - min ) * scaleFactor
2017-01-25 05:07:15 +03:00
}
}
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
}
2017-02-24 16:32:33 +03:00
var listPaymentsCommand = cli . Command {
2017-03-03 01:23:16 +03:00
Name : "listpayments" ,
Usage : "list all outgoing payments" ,
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( payments )
2016-12-28 02:45:10 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var getChanInfoCommand = cli . Command {
2016-12-28 02:45:10 +03:00
Name : "getchaninfo" ,
2017-03-03 01:23:16 +03:00
Usage : "get the state of a channel" ,
2016-12-28 02:45:10 +03:00
Description : "prints out the latest authenticated state for a " +
"particular channel" ,
2017-03-03 01:23:16 +03:00
ArgsUsage : "chan_id" ,
2016-12-28 02:45:10 +03:00
Flags : [ ] cli . Flag {
2017-03-03 01:23:16 +03:00
cli . Int64Flag {
2016-12-28 02:45:10 +03:00
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
2017-03-03 01:23:16 +03:00
var (
2017-03-09 07:44:32 +03:00
chanID int64
err error
2017-03-03 01:23:16 +03:00
)
switch {
case ctx . IsSet ( "chan_id" ) :
2017-03-09 07:44:32 +03:00
chanID = ctx . Int64 ( "chan_id" )
2017-03-03 01:23:16 +03:00
case ctx . Args ( ) . Present ( ) :
2017-03-09 07:44:32 +03:00
chanID , err = strconv . ParseInt ( ctx . Args ( ) . First ( ) , 10 , 64 )
2017-03-03 01:23:16 +03:00
default :
return fmt . Errorf ( "chan_id argument missing" )
}
2016-12-28 02:45:10 +03:00
req := & lnrpc . ChanInfoRequest {
2017-03-09 07:44:32 +03:00
ChanId : uint64 ( chanID ) ,
2016-12-28 02:45:10 +03:00
}
chanInfo , err := client . GetChanInfo ( ctxb , req )
if err != nil {
return err
}
2017-02-23 22:56:47 +03:00
printRespJSON ( chanInfo )
2016-12-28 02:45:10 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var getNodeInfoCommand = cli . Command {
2016-12-28 02:45:10 +03:00
Name : "getnodeinfo" ,
2017-03-03 01:23:16 +03:00
Usage : "Get information on a specific node." ,
2016-12-28 02:45:10 +03:00
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
2017-03-03 01:23:16 +03:00
if ! ctx . IsSet ( "pub_key" ) {
return fmt . Errorf ( "pub_key argument missing" )
}
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( nodeInfo )
2016-12-28 02:45:10 +03:00
return nil
}
2017-03-21 05:01:57 +03:00
var queryRoutesCommand = cli . Command {
Name : "queryroutes" ,
2017-03-03 01:23:16 +03:00
Usage : "Query a route to a destination." ,
Description : "Queries the channel router for a potential path to the destination that has sufficient flow for the amount including fees" ,
ArgsUsage : "dest amt" ,
2016-12-28 02:45:10 +03:00
Flags : [ ] cli . Flag {
cli . StringFlag {
Name : "dest" ,
Usage : "the 33-byte hex-encoded public key for the payment " +
"destination" ,
} ,
2017-03-03 01:23:16 +03:00
cli . Int64Flag {
2016-12-28 02:45:10 +03:00
Name : "amt" ,
Usage : "the amount to send expressed in satoshis" ,
} ,
} ,
2017-03-21 05:01:57 +03:00
Action : queryRoutes ,
2016-12-28 02:45:10 +03:00
}
2017-03-21 05:01:57 +03:00
func queryRoutes ( ctx * cli . Context ) error {
2016-12-28 02:45:10 +03:00
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
2017-03-03 01:23:16 +03:00
var (
dest string
amt int64
err error
)
args := ctx . Args ( )
switch {
case ctx . IsSet ( "dest" ) :
dest = ctx . String ( "dest" )
case args . Present ( ) :
dest = args . First ( )
args = args . Tail ( )
default :
return fmt . Errorf ( "dest argument missing" )
}
switch {
case ctx . IsSet ( "amt" ) :
amt = ctx . Int64 ( "amt" )
case args . Present ( ) :
amt , err = strconv . ParseInt ( args . First ( ) , 10 , 64 )
if err != nil {
return fmt . Errorf ( "unable to decode amt argument: %v" , err )
}
default :
return fmt . Errorf ( "amt argument missing" )
}
2017-03-21 05:01:57 +03:00
req := & lnrpc . QueryRoutesRequest {
2017-03-03 01:23:16 +03:00
PubKey : dest ,
Amt : amt ,
2016-12-28 02:45:10 +03:00
}
2017-03-21 05:01:57 +03:00
route , err := client . QueryRoutes ( ctxb , req )
2016-12-28 02:45:10 +03:00
if err != nil {
return err
}
2017-02-23 22:56:47 +03:00
printRespJSON ( route )
2016-12-28 02:45:10 +03:00
return nil
}
2017-02-24 16:32:33 +03:00
var getNetworkInfoCommand = cli . Command {
2016-12-28 02:45:10 +03:00
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
2017-02-23 22:56:47 +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
2017-02-24 16:32:33 +03:00
var debugLevelCommand = cli . Command {
2017-01-15 05:19:02 +03:00
Name : "debuglevel" ,
2017-03-03 01:23:16 +03:00
Usage : "Set the debug level." ,
2017-01-15 05:19:02 +03:00
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
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2017-01-15 05:19:02 +03:00
return nil
}
2017-01-18 00:39:30 +03:00
2017-02-24 16:32:33 +03:00
var decodePayReqComamnd = cli . Command {
2017-01-18 00:39:30 +03:00
Name : "decodepayreq" ,
2017-03-03 01:23:16 +03:00
Usage : "Decode a payment request." ,
2017-01-18 00:39:30 +03:00
Description : "Decode the passed payment request revealing the destination, payment hash and value of the payment request" ,
2017-03-03 01:23:16 +03:00
ArgsUsage : "pay_req" ,
2017-01-18 00:39:30 +03:00
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-03-03 01:23:16 +03:00
var payreq string
switch {
case ctx . IsSet ( "pay_req" ) :
payreq = ctx . String ( "pay_req" )
case ctx . Args ( ) . Present ( ) :
payreq = ctx . Args ( ) . First ( )
default :
return fmt . Errorf ( "pay_req argument missing" )
2017-01-18 00:39:30 +03:00
}
2017-01-30 01:56:31 +03:00
resp , err := client . DecodePayReq ( ctxb , & lnrpc . PayReqString {
2017-03-03 01:23:16 +03:00
PayReq : payreq ,
2017-01-30 01:56:31 +03:00
} )
2017-01-18 00:39:30 +03:00
if err != nil {
return err
}
2017-02-23 22:56:47 +03:00
printRespJSON ( resp )
2017-01-18 00:39:30 +03:00
return nil
}
2017-03-04 11:23:04 +03:00
2017-03-09 07:44:32 +03:00
var listChainTxnsCommand = cli . Command {
2017-03-04 11:23:04 +03:00
Name : "listchaintxns" ,
Usage : "List transactions from the wallet." ,
Description : "List all transactions an address of the wallet was involved in." ,
2017-03-09 07:44:32 +03:00
Action : listChainTxns ,
2017-03-04 11:23:04 +03:00
}
func listChainTxns ( ctx * cli . Context ) error {
ctxb := context . Background ( )
client , cleanUp := getClient ( ctx )
defer cleanUp ( )
resp , err := client . GetTransactions ( ctxb , & lnrpc . GetTransactionsRequest { } )
if err != nil {
return err
}
2017-03-09 07:44:32 +03:00
printRespJSON ( resp )
2017-03-04 11:23:04 +03:00
return nil
}