github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/cmd/lit-af/shell.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  
     9  	"io/ioutil"
    10  	"net/http"
    11  
    12  	"github.com/fatih/color"
    13  	"github.com/mit-dci/lit/coinparam"
    14  	"github.com/mit-dci/lit/consts"
    15  	"github.com/mit-dci/lit/litrpc"
    16  	"github.com/mit-dci/lit/lnutil"
    17  )
    18  
    19  var lsCommand = &Command{
    20  	Format: fmt.Sprintf("%s%s\n", lnutil.White("ls"), lnutil.ReqColor(("topic"))),
    21  	Description: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
    22  		"Show various information about our current state, such as connections, addresses, UTXO's, balances, etc.",
    23  		fmt.Sprintf("%s %s",
    24  			lnutil.White("topic"),
    25  			"What information to show. Provide one of:"),
    26  		fmt.Sprintf("\t%-20s %s",
    27  			lnutil.White("-a"),
    28  			"Information on connections to other peers"),
    29  		fmt.Sprintf("\t%-20s %s",
    30  			lnutil.White("conns"),
    31  			"Information on connections to other peers"),
    32  		fmt.Sprintf("\t%-20s %s",
    33  			lnutil.White("chans"),
    34  			"Information on payment channels"),
    35  		fmt.Sprintf("\t%-20s %s",
    36  			lnutil.White("dualfunds"),
    37  			"Information on pending dual funding requests"),
    38  		fmt.Sprintf("\t%-20s %s",
    39  			lnutil.White("txos"),
    40  			"Information on unspent outputs"),
    41  		fmt.Sprintf("\t%-20s %s",
    42  			lnutil.White("ports"),
    43  			"Information on listening addresses/ports"),
    44  		fmt.Sprintf("\t%-20s %s",
    45  			lnutil.White("addrs"),
    46  			"Information on blockchain addresses"),
    47  		fmt.Sprintf("\t%-20s %s",
    48  			lnutil.White("bals"),
    49  			"Information on wallet balances")),
    50  	ShortDescription: "Show various information about our current state\n",
    51  }
    52  
    53  var exitCommand = &Command{
    54  	Format:           lnutil.White("exit\n"),
    55  	Description:      fmt.Sprintf("Alias: %s\nExit the interactive shell.\n", lnutil.White("quit")),
    56  	ShortDescription: fmt.Sprintf("Alias: %s\nExit the interactive shell.\n", lnutil.White("quit")),
    57  }
    58  
    59  var helpCommand = &Command{
    60  	Format:           fmt.Sprintf("%s%s\n", lnutil.White("help"), lnutil.OptColor("command")),
    61  	Description:      "Show information about a given command\n",
    62  	ShortDescription: "Show information about a given command\n",
    63  }
    64  
    65  var offCommand = &Command{
    66  	Format:           lnutil.White("off"),
    67  	Description:      "Shut down the lit node.\n",
    68  	ShortDescription: "Shut down the lit node.\n",
    69  }
    70  
    71  func parseErr(err error, str string) error {
    72  	if err != nil {
    73  		fmt.Fprintf(color.Output, "%s error: %s\n", str, err)
    74  	}
    75  	return nil
    76  }
    77  
    78  // Shellparse parses user input and hands it to command functions if matching
    79  func (lc *litAfClient) Shellparse(cmdslice []string) error {
    80  	var err error
    81  	var args []string
    82  	cmd := cmdslice[0]
    83  	if len(cmdslice) > 1 {
    84  		args = cmdslice[1:]
    85  	}
    86  
    87  	if cmd == "exit" || cmd == "quit" {
    88  		return lc.Exit(args)
    89  	}
    90  	// help gives you really terse help.  Just a list of commands.
    91  	if cmd == "help" {
    92  		err = lc.Help(args)
    93  		return parseErr(err, "help")
    94  	}
    95  
    96  	if cmd == "watch" {
    97  		err = lc.Watch(args)
    98  		return parseErr(err, "watch")
    99  	}
   100  
   101  	// address a new address and displays it
   102  	if cmd == "adr" {
   103  		err = lc.Address(args)
   104  		return parseErr(err, "adr")
   105  	}
   106  
   107  	// ls shows the current set of utxos, addresses and score
   108  	if cmd == "ls" {
   109  		err = lc.Ls(args)
   110  		return parseErr(err, "ls")
   111  	}
   112  
   113  	// send sends coins to the address specified
   114  	if cmd == "send" {
   115  		err = lc.Send(args)
   116  		return parseErr(err, "send")
   117  	}
   118  
   119  	if cmd == "lis" { // listen for lnd peers
   120  		err = lc.Lis(args)
   121  		return parseErr(err, "lis")
   122  	}
   123  
   124  	if cmd == "off" { // stop remote node
   125  		// actually returns an error
   126  		return lc.Stop(args)
   127  	}
   128  
   129  	if cmd == "sweep" { // make lots of 1-in 1-out txs
   130  		err = lc.Sweep(args)
   131  		return parseErr(err, "sweep")
   132  	}
   133  
   134  	// push money in a channel away from you
   135  	if cmd == "push" {
   136  		err = lc.Push(args)
   137  		return parseErr(err, "push")
   138  	}
   139  
   140  	if cmd == "add" {
   141  		err = lc.AddHTLC(args)
   142  		return parseErr(err, "add")
   143  	}
   144  
   145  	if cmd == "clear" {
   146  		err = lc.ClearHTLC(args)
   147  		return parseErr(err, "clear")
   148  	}
   149  
   150  	if cmd == "claim" {
   151  		err = lc.ClaimHTLC(args)
   152  		return parseErr(err, "claim")
   153  	}
   154  
   155  	if cmd == "con" { // connect to lnd host
   156  		err = lc.Connect(args)
   157  		return parseErr(err, "con")
   158  	}
   159  
   160  	if cmd == "dlc" { // the root command for Discreet log contracts
   161  		err = lc.Dlc(args)
   162  		return parseErr(err, "dlc")
   163  	}
   164  
   165  	// remote control
   166  	if cmd == "rcauth" {
   167  		err = lc.RemoteControlAuth(args)
   168  		return parseErr(err, "rcauth")
   169  	}
   170  
   171  	if cmd == "rcreq" {
   172  		err = lc.RemoteControlRequest(args)
   173  		return parseErr(err, "rcreq")
   174  	}
   175  
   176  	// fund and create a new channel
   177  	if cmd == "fund" {
   178  		err = lc.FundChannel(args)
   179  		return parseErr(err, "fund")
   180  	}
   181  
   182  	// mutually fund and create a new channel
   183  	if cmd == "dualfund" {
   184  		if (len(args) > 0 && args[0] == "-h") || len(args) == 0 {
   185  			err = lc.DualFund(args)
   186  		} else {
   187  			if args[0] == "start" {
   188  				err = lc.DualFundChannel(args[1:])
   189  			} else if args[0] == "decline" {
   190  				err = lc.DualFundDecline(args[1:])
   191  			} else if args[0] == "accept" {
   192  				err = lc.DualFundAccept(args[1:])
   193  			} else {
   194  				fmt.Fprintf(color.Output, "dualfund error - unrecognized subcommand %s\n", args[0])
   195  			}
   196  		}
   197  
   198  		if err != nil {
   199  			fmt.Fprintf(color.Output, "dualfund error: %s\n", err)
   200  		}
   201  		return nil
   202  	}
   203  	// cooperative close of a channel
   204  	if cmd == "close" {
   205  		err = lc.CloseChannel(args)
   206  		return parseErr(err, "close")
   207  	}
   208  	if cmd == "break" {
   209  		err = lc.BreakChannel(args)
   210  		return parseErr(err, "break")
   211  	}
   212  	if cmd == "say" {
   213  		err = lc.Say(args)
   214  		return parseErr(err, "say")
   215  	}
   216  
   217  	if cmd == "fan" { // fan-out tx
   218  		err = lc.Fan(args)
   219  		return parseErr(err, "fan")
   220  	}
   221  	if cmd == "fee" { // get fee rate for a wallet
   222  		err = lc.Fee(args)
   223  		return parseErr(err, "fee")
   224  	}
   225  	if cmd == "dump" { // dump all private keys
   226  		err = lc.Dump(args)
   227  		return parseErr(err, "dump")
   228  	}
   229  	if cmd == "history" { // dump justice tx history
   230  		err = lc.History(args)
   231  		return parseErr(err, "history")
   232  	}
   233  	if cmd == "graph" { // dump graphviz for channels
   234  		err = lc.Graph(args)
   235  		return parseErr(err, "grpah")
   236  	}
   237  	if cmd == "paymultihop" { // pay via multi-hop
   238  		err = lc.PayMultihop(args)
   239  		if err != nil {
   240  			fmt.Fprintf(color.Output, "paymultihop error: %s\n", err)
   241  		}
   242  		return nil
   243  	}
   244  
   245  	fmt.Fprintf(color.Output, "Command not recognized. type help for command list.\n")
   246  	return nil
   247  }
   248  
   249  func (lc *litAfClient) Exit(textArgs []string) error {
   250  	if len(textArgs) > 0 {
   251  		if len(textArgs) == 1 && textArgs[0] == "-h" {
   252  			fmt.Fprintf(color.Output, exitCommand.Format)
   253  			fmt.Fprintf(color.Output, exitCommand.Description)
   254  			return nil
   255  		}
   256  		fmt.Fprintf(color.Output, "Unexpected argument: "+textArgs[0])
   257  		return nil
   258  	}
   259  	return fmt.Errorf("User exit")
   260  }
   261  
   262  func (lc *litAfClient) Ls2(textArgs []string) error {
   263  	resp, err := http.Post("http://127.0.0.1:9750/lit",
   264  		"application/json",
   265  		bytes.NewBufferString(
   266  			`{"jsonrpc":"2.0","id":1,"method":"LitRPC.TxoList","params":[]}`))
   267  	if err != nil {
   268  		return err
   269  	}
   270  	defer resp.Body.Close()
   271  	b, err := ioutil.ReadAll(resp.Body)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	fmt.Printf("JSON over HTTP response: %s\n", string(b))
   276  	return nil
   277  }
   278  
   279  func isExists(array []string, elem string) bool {
   280  	for _, x := range array {
   281  		if x == elem {
   282  			return true
   283  		}
   284  	}
   285  	return false
   286  }
   287  
   288  func (lc *litAfClient) Ls(textArgs []string) error {
   289  	stopEx, err := CheckHelpCommand(lsCommand, textArgs, 1)
   290  	if err != nil || stopEx {
   291  		return err
   292  	}
   293  
   294  	listofCommands := []string{"conns", "chans", "dualfunds", "txos", "ports", "addrs", "bals", "pays", "-a"}
   295  	cmd := textArgs[0]
   296  
   297  	if !isExists(listofCommands, cmd) {
   298  		return fmt.Errorf("Invalid Argument passed. Use ls -h for help")
   299  	}
   300  
   301  	if len(textArgs) > 1 {
   302  		return fmt.Errorf("Only provide one argument to ls. Use ls -h for help")
   303  	}
   304  
   305  	// TODO Move these to their respective places?  Perhaps this gets optimized out anyways.
   306  	pReply := new(litrpc.ListConnectionsReply)
   307  	cReply := new(litrpc.ChannelListReply)
   308  	aReply := new(litrpc.AddressReply)
   309  	tReply := new(litrpc.TxoListReply)
   310  	bReply := new(litrpc.BalanceReply)
   311  	lReply := new(litrpc.ListeningPortsReply)
   312  	rcReply := new(litrpc.RCPendingAuthRequestsReply)
   313  	dfReply := new(litrpc.PendingDualFundReply)
   314  	mhReply := new(litrpc.MultihopPaymentsReply)
   315  
   316  	displayAllCommands := false
   317  	if cmd == "-a" {
   318  		displayAllCommands = true
   319  	}
   320  
   321  	// Balance reply needed for bals and chans
   322  	if cmd == "chans" || cmd == "bals" || displayAllCommands {
   323  		err := lc.Call("LitRPC.Balance", nil, bReply)
   324  		if err != nil {
   325  			return err
   326  		}
   327  	}
   328  
   329  	if cmd == "conns" || displayAllCommands {
   330  		err := lc.Call("LitRPC.ListConnections", nil, pReply)
   331  		if err != nil {
   332  			return err
   333  		}
   334  
   335  		if len(pReply.Connections) > 0 {
   336  			if displayAllCommands {
   337  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Peers:"))
   338  			}
   339  			for _, peer := range pReply.Connections {
   340  				fmt.Fprintf(color.Output, "%s %s (%s)\n",
   341  					lnutil.White(peer.PeerNumber), peer.RemoteHost, peer.LitAdr)
   342  			}
   343  		}
   344  	}
   345  
   346  	err = lc.Call("LitRPC.Balance", nil, bReply)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	walHeights := map[uint32]int32{}
   352  	for _, b := range bReply.Balances {
   353  		walHeights[b.CoinType] = b.SyncHeight
   354  	}
   355  
   356  	err = lc.Call("LitRPC.ChannelList", nil, cReply)
   357  	if err != nil {
   358  		return err
   359  	}
   360  	if len(cReply.Channels) > 0 {
   361  		fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Channels:"))
   362  	}
   363  
   364  	if cmd == "chans" || displayAllCommands {
   365  		err := lc.Call("LitRPC.ChannelList", nil, cReply)
   366  		if err != nil {
   367  			return err
   368  		}
   369  
   370  		if len(cReply.Channels) > 0 {
   371  			if displayAllCommands {
   372  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Channels:"))
   373  			}
   374  
   375  			coinDaemonConnected := map[uint32]bool{}
   376  			for _, walBal := range bReply.Balances {
   377  				coinDaemonConnected[walBal.CoinType] = true
   378  			}
   379  
   380  			sort.Slice(cReply.Channels, func(i, j int) bool {
   381  				return cReply.Channels[i].Height < cReply.Channels[j].Height
   382  			})
   383  
   384  			var closedChannels []litrpc.ChannelInfo
   385  			var openChannels []litrpc.ChannelInfo
   386  			var disabledChannels []litrpc.ChannelInfo
   387  			var unconfirmedChannels []litrpc.ChannelInfo
   388  			for _, c := range cReply.Channels {
   389  				_, ok := coinDaemonConnected[c.CoinType]
   390  				if !ok {
   391  					disabledChannels = append(disabledChannels, c)
   392  				} else if c.Closed {
   393  					closedChannels = append(closedChannels, c)
   394  				} else if c.Height == -1 {
   395  					unconfirmedChannels = append(unconfirmedChannels, c)
   396  				} else {
   397  					openChannels = append(openChannels, c)
   398  				}
   399  			}
   400  
   401  			printChannel := func(c litrpc.ChannelInfo) {
   402  				fmt.Fprintf(
   403  					color.Output,
   404  					"\t\t%s (peer %d) type %d cap: %s bal: %s\n\t\t\t%s\n\t\t\th: %d state: %d\n\t\t\tdata: %x\n\t\t\tpkh: %x\n",
   405  					lnutil.White(c.CIdx), c.PeerIdx, c.CoinType, lnutil.SatoshiColor(c.Capacity), lnutil.SatoshiColor(c.MyBalance),
   406  					lnutil.OutPoint(c.OutPoint),
   407  					c.Height, c.StateNum, c.Data, c.Pkh)
   408  
   409  				var nHTLCs int
   410  				for _, h := range c.HTLCs {
   411  					if !h.Cleared && !h.ClearedOnChain {
   412  						nHTLCs++
   413  					}
   414  				}
   415  
   416  				if len(c.HTLCs) > 0 {
   417  					fmt.Fprintf(color.Output, "\t\t\t%s\n", lnutil.Header("HTLCs:"))
   418  				}
   419  
   420  				for _, h := range c.HTLCs {
   421  					walHeight := walHeights[c.CoinType]
   422  					locktime := int32(h.Locktime) - walHeight
   423  
   424  					if h.Cleared || h.ClearedOnChain {
   425  						// Don't bother showing cleared HTLCs that are > 2* the default lock time old
   426  						if locktime < -2*consts.DefaultLockTime {
   427  							continue
   428  						}
   429  
   430  						fmt.Fprintf(color.Output, lnutil.Red("\tCleared:      "))
   431  					} else if h.Clearing || h.InProg {
   432  						c := color.New(color.FgYellow)
   433  						c.Printf("\t\t\t\tIn progress:  ")
   434  					} else {
   435  						fmt.Fprintf(color.Output, lnutil.Green("\tUncleared:    "))
   436  					}
   437  
   438  					fmt.Fprintf(color.Output,
   439  						"%s incoming: %t amt: %s RHash: %x R: %x locktime: %d\n",
   440  						lnutil.White(h.Idx), h.Incoming, lnutil.SatoshiColor(h.Amt), h.RHash,
   441  						h.R,
   442  						locktime)
   443  				}
   444  			}
   445  
   446  			printChannels := func(cs []litrpc.ChannelInfo, title string) {
   447  				if len(cs) > 0 {
   448  					fmt.Fprintf(color.Output, "\t\t%s\n", title)
   449  					for _, c := range cs {
   450  						printChannel(c)
   451  					}
   452  				}
   453  			}
   454  
   455  			printChannels(openChannels, lnutil.Green("Open:"))
   456  			printChannels(unconfirmedChannels, lnutil.Yellow("Unconfirmed:"))
   457  			printChannels(closedChannels, lnutil.Red("Closed:"))
   458  			printChannels(disabledChannels, lnutil.Red("Disabled (coin daemon unavailable):"))
   459  		}
   460  	}
   461  
   462  	if cmd == "dualfunds" || displayAllCommands {
   463  		err := lc.Call("LitRPC.PendingDualFund", nil, dfReply)
   464  		if err != nil {
   465  			return err
   466  		}
   467  
   468  		if dfReply.Pending {
   469  			if displayAllCommands {
   470  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Pending Dualfunds:"))
   471  			}
   472  			fmt.Fprintf(
   473  				color.Output, "\t%s %d\t%s %d\t%s %s\t%s %s\n\n",
   474  				lnutil.Header("Peer:"), dfReply.PeerIdx,
   475  				lnutil.Header("Type:"), dfReply.CoinType,
   476  				lnutil.Header("Their Amt:"), lnutil.SatoshiColor(dfReply.TheirAmount),
   477  				lnutil.Header("Req Amt:"), lnutil.SatoshiColor(dfReply.RequestedAmount),
   478  			)
   479  		}
   480  	}
   481  
   482  	if cmd == "txos" || displayAllCommands {
   483  		err := lc.Call("LitRPC.TxoList", nil, tReply)
   484  
   485  		if err != nil {
   486  			return err
   487  		}
   488  
   489  		if len(tReply.Txos) > 0 {
   490  			if displayAllCommands {
   491  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Txos:"))
   492  			}
   493  			for i, t := range tReply.Txos {
   494  				fmt.Fprintf(color.Output, "%d %s h:%d amt:%s %s %s",
   495  					i+1, lnutil.OutPoint(t.OutPoint), t.Height,
   496  					lnutil.SatoshiColor(t.Amt), t.KeyPath, t.CoinType)
   497  				if t.Delay != 0 {
   498  					fmt.Fprintf(color.Output, " delay: %d", t.Delay)
   499  				}
   500  				if !t.Witty {
   501  					fmt.Fprintf(color.Output, " non-witness")
   502  				}
   503  				fmt.Fprintf(color.Output, "\n")
   504  			}
   505  		}
   506  	}
   507  
   508  	if cmd == "rcauth" || displayAllCommands {
   509  		err := lc.Call("LitRPC.ListPendingRemoteControlAuthRequests", nil, rcReply)
   510  		if err != nil {
   511  			return err
   512  		}
   513  		if len(rcReply.PubKeys) > 0 {
   514  			fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Nodes requesting remote control authorization:"))
   515  			for _, pubKey := range rcReply.PubKeys {
   516  				fmt.Fprintf(color.Output, "%x\n", pubKey)
   517  			}
   518  		}
   519  	}
   520  
   521  	if cmd == "ports" || displayAllCommands {
   522  		err := lc.Call("LitRPC.GetListeningPorts", nil, lReply)
   523  		if err != nil {
   524  			return err
   525  		}
   526  
   527  		if len(lReply.LisIpPorts) > 0 {
   528  			if displayAllCommands {
   529  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Listening Ports:"))
   530  			}
   531  			fmt.Fprintf(color.Output,
   532  				"Listening for connections on port(s) %v with key %s\n",
   533  				lnutil.White(lReply.LisIpPorts), lReply.Adr)
   534  		}
   535  	}
   536  
   537  	if cmd == "addrs" || displayAllCommands {
   538  		err := lc.Call("LitRPC.Address", nil, aReply)
   539  		if err != nil {
   540  			return err
   541  		}
   542  
   543  		if len(aReply.WitAddresses) > 0 {
   544  			if displayAllCommands {
   545  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Addresses:"))
   546  			}
   547  			for i, a := range aReply.WitAddresses {
   548  				fmt.Fprintf(color.Output, "%d %s (%s)\n", i+1,
   549  					lnutil.Address(a), lnutil.Address(aReply.LegacyAddresses[i]))
   550  			}
   551  		}
   552  
   553  	}
   554  
   555  	if cmd == "bals" || displayAllCommands {
   556  		if len(bReply.Balances) > 0 {
   557  			if displayAllCommands {
   558  				fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Balances:"))
   559  			}
   560  			for _, walBal := range bReply.Balances {
   561  				fmt.Fprintf(
   562  					color.Output, "%s %d\t%s %d\t%s %d\t%s %s\t%s %s %s %s\n",
   563  					lnutil.Header("Type:"), walBal.CoinType,
   564  					lnutil.Header("Sync Height:"), walBal.SyncHeight,
   565  					lnutil.Header("FeeRate:"), walBal.FeeRate,
   566  					lnutil.Header("Utxo:"), lnutil.SatoshiColor(walBal.TxoTotal),
   567  					lnutil.Header("WitConf:"), lnutil.SatoshiColor(walBal.MatureWitty),
   568  					lnutil.Header("Channel:"), lnutil.SatoshiColor(walBal.ChanTotal),
   569  				)
   570  			}
   571  		}
   572  	}
   573  
   574  	if cmd == "pays" || displayAllCommands {
   575  		err = lc.Call("LitRPC.ListMultihopPayments", nil, mhReply)
   576  		if err != nil {
   577  			return err
   578  		}
   579  
   580  		if len(mhReply.Payments) > 0 {
   581  			fmt.Fprintf(color.Output, "\t%s\n", lnutil.Header("Multihop Payments:"))
   582  		}
   583  
   584  		for _, p := range mhReply.Payments {
   585  			if p.Succeeded {
   586  				fmt.Fprintf(color.Output, lnutil.Green("Completed: "))
   587  			} else {
   588  				c := color.New(color.FgYellow)
   589  				c.Printf("Pending:   ")
   590  			}
   591  
   592  			path := p.Path[0]
   593  			for i := 1; i < len(p.Path); i++ {
   594  				path += " -> " + p.Path[i]
   595  			}
   596  
   597  			fmt.Fprintf(color.Output,
   598  				"amt: %s RHash: %x R: %x path: %s \n",
   599  				lnutil.SatoshiColor(p.Amt), p.RHash,
   600  				p.R,
   601  				path)
   602  		}
   603  	}
   604  
   605  	return nil
   606  }
   607  
   608  func (lc *litAfClient) Stop(textArgs []string) error {
   609  	if len(textArgs) > 0 && textArgs[0] == "-h" {
   610  		fmt.Fprintf(color.Output, offCommand.Format)
   611  		fmt.Fprintf(color.Output, offCommand.Description)
   612  		return nil
   613  	}
   614  
   615  	reply := new(litrpc.StatusReply)
   616  
   617  	err := lc.Call("LitRPC.Stop", nil, reply)
   618  	if err != nil {
   619  		return err
   620  	}
   621  
   622  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   623  
   624  	//lc.rpccon.Close()
   625  	return fmt.Errorf("stopped remote lit node")
   626  }
   627  
   628  func printHelp(commands []*Command) {
   629  	for _, command := range commands {
   630  		fmt.Fprintf(color.Output, "%s\t%s", command.Format, command.ShortDescription)
   631  	}
   632  }
   633  
   634  func printCointypes() {
   635  	for k, v := range coinparam.RegisteredNets {
   636  		fmt.Fprintf(color.Output, "CoinType: %s\n", strconv.Itoa(int(k)))
   637  		fmt.Fprintf(color.Output, "└────── Name: %-13sBech32Prefix: %s\n\n", v.Name+",", v.Bech32Prefix)
   638  	}
   639  }
   640  
   641  func (lc *litAfClient) Help(textArgs []string) error {
   642  	if len(textArgs) == 0 {
   643  
   644  		fmt.Fprintf(color.Output, lnutil.Header("Commands:\n"))
   645  		listofCommands := []*Command{helpCommand, sayCommand, lsCommand, addressCommand, sendCommand, fanCommand, sweepCommand, lisCommand, conCommand, dlcCommand, fundCommand, dualFundCommand, watchCommand, pushCommand, closeCommand, breakCommand, addHTLCCommand, clearHTLCCommand, rcAuthCommand, rcRequestCommand, historyCommand, offCommand, exitCommand}
   646  		printHelp(listofCommands)
   647  		fmt.Fprintf(color.Output, "\n\n")
   648  		fmt.Fprintf(color.Output, lnutil.Header("Coins:\n"))
   649  		printCointypes()
   650  
   651  		return nil
   652  	}
   653  
   654  	if textArgs[0] == "help" || textArgs[0] == "-h" {
   655  		fmt.Fprintf(color.Output, helpCommand.Format)
   656  		fmt.Fprintf(color.Output, helpCommand.Description)
   657  		return nil
   658  	}
   659  	res := make([]string, 0)
   660  	res = append(res, textArgs[0])
   661  	res = append(res, "-h")
   662  	return lc.Shellparse(res)
   663  }