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

     1  package main
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"strconv"
     7  
     8  	"github.com/fatih/color"
     9  	"github.com/mit-dci/lit/litrpc"
    10  	"github.com/mit-dci/lit/lnutil"
    11  )
    12  
    13  var fundCommand = &Command{
    14  	Format: fmt.Sprintf("%s%s%s\n", lnutil.White("fund"),
    15  		lnutil.ReqColor("peer", "coinType", "capacity", "initialSend"), lnutil.OptColor("data")),
    16  	Description: fmt.Sprintf("%s\n%s\n%s\n%s\n",
    17  		"Establish and fund a new lightning channel with the given peer.",
    18  		"The capacity is the amount of satoshi we insert into the channel,",
    19  		"and initialSend is the amount we initially hand over to the other party.",
    20  		"data is an optional field that can contain 32 bytes of hex to send as part of the channel fund",
    21  	),
    22  	ShortDescription: "Establish and fund a new lightning channel with the given peer.\n",
    23  }
    24  
    25  var dualFundCommand = &Command{
    26  	Format: fmt.Sprintf("%s%s%s\n", lnutil.White("dualfund"),
    27  		lnutil.ReqColor("subcommand"), lnutil.OptColor("parameters...")),
    28  	Description: fmt.Sprintf("%s\n%s\n",
    29  		"Commands for establishing and mutually funding a new lightning channel with the given peer.",
    30  		"Subcommands: start, accept, decline"),
    31  
    32  	ShortDescription: "Commands for dual funding\n",
    33  }
    34  
    35  var dualFundStartCommand = &Command{
    36  	Format: fmt.Sprintf("%s%s\n", lnutil.White("dualfund start"),
    37  		lnutil.ReqColor("peer", "coinType", "ourAmount", "theirAmount")),
    38  	Description: fmt.Sprintf("%s\n%s\n%s\n",
    39  		"Establish and mutually fund a new lightning channel with the given peer.",
    40  		"The capacity is the sum of the amounts both peers insert into the channel,",
    41  		"each party will end up with their funded amount in the channel."),
    42  	ShortDescription: "Establish and mutually fund a new lightning channel with the given peer.\n",
    43  }
    44  
    45  var dualFundDeclineCommand = &Command{
    46  	Format:           fmt.Sprintf("%s\n", lnutil.White("dualfund decline")),
    47  	Description:      "Declines the pending dual funding request received from another peer (if any)\n",
    48  	ShortDescription: "Declines the pending dual funding request received from another peer (if any)\n",
    49  }
    50  
    51  var dualFundAcceptCommand = &Command{
    52  	Format:           fmt.Sprintf("%s\n", lnutil.White("dualfund accept")),
    53  	Description:      "Accepts the pending dual funding request received from another peer (if any)\n",
    54  	ShortDescription: "Accepts the pending dual funding request received from another peer (if any)\n",
    55  }
    56  
    57  var watchCommand = &Command{
    58  	Format: fmt.Sprintf("%s%s\n", lnutil.White("watch"),
    59  		lnutil.ReqColor("channel idx", "watchPeerIdx")),
    60  	Description: fmt.Sprintf("%s\n%s\n",
    61  		"Send channel data to a watcher",
    62  		"The watcher can defend your channel while you're offline."),
    63  	ShortDescription: "Send channel watch data to watcher.\n",
    64  }
    65  
    66  var pushCommand = &Command{
    67  	Format: fmt.Sprintf("%s%s%s%s\n", lnutil.White("push"), lnutil.ReqColor("channel idx", "amount"), lnutil.OptColor("times"), lnutil.OptColor("data")),
    68  	Description: fmt.Sprintf("%s\n%s\n%s\n",
    69  		"Push the given amount (in satoshis) to the other party on the given channel.",
    70  		"Optionally, the push operation can be associated with a 32 byte value hex encoded.",
    71  		"Optionally, the push operation can be repeated <times> number of times."),
    72  	ShortDescription: "Push the given amount (in satoshis) to the other party on the given channel.\n",
    73  }
    74  
    75  var closeCommand = &Command{
    76  	Format: fmt.Sprintf("%s%s\n", lnutil.White("close"), lnutil.ReqColor("channel idx")),
    77  	Description: fmt.Sprintf("%s\n%s\n%s%s\n",
    78  		"Cooperatively close the channel with the given index by asking",
    79  		"the other party to finalize the channel pay-out.",
    80  		"See also: ", lnutil.White("break")),
    81  	ShortDescription: "Cooperatively close the channel with the given index by asking\n",
    82  }
    83  
    84  var breakCommand = &Command{
    85  	Format: fmt.Sprintf("%s%s\n", lnutil.White("break"), lnutil.ReqColor("channel idx")),
    86  	Description: fmt.Sprintf("%s\n%s\n%s%s\n",
    87  		"Forcibly break the given channel. Note that you need to wait",
    88  		"a set number of blocks before you can use the money.",
    89  		"See also: ", lnutil.White("stop")),
    90  	ShortDescription: "Forcibly break the given channel.\n",
    91  }
    92  
    93  var historyCommand = &Command{
    94  	Format:           fmt.Sprintf("%s\n", lnutil.White("history")),
    95  	Description:      "Show all the metadata for justice txs",
    96  	ShortDescription: "Show all the metadata for justice txs.\n",
    97  }
    98  
    99  var addHTLCCommand = &Command{
   100  	Format: fmt.Sprintf("%s%s%s\n", lnutil.White("add"), lnutil.ReqColor("channel idx", "amount", "locktime", "RHash"), lnutil.OptColor("data")),
   101  	Description: fmt.Sprintf("%s\n%s\n",
   102  		"Add an HTLC of the given amount (in satoshis) to the given channel. Locktime specifies the number of blocks the HTLC stays active before timing out",
   103  		"Optionally, the push operation can be associated with a 32 byte value hex encoded."),
   104  	ShortDescription: "Add HTLC of the given amount (in satoshis) to the given channel.\n",
   105  }
   106  
   107  var clearHTLCCommand = &Command{
   108  	Format: fmt.Sprintf("%s%s%s\n", lnutil.White("clear"), lnutil.ReqColor("channel idx", "HTLC idx", "R"), lnutil.OptColor("data")),
   109  	Description: fmt.Sprintf("%s\n%s\n%s\n",
   110  		"Clear an HTLC of the given index from the given channel.",
   111  		"Optionally, the push operation can be associated with a 32 byte value hex encoded.",
   112  		"Set R to zero to timeout the HTLC"),
   113  	ShortDescription: "Clear HTLC of the given index from the given channel.\n",
   114  }
   115  
   116  var claimHTLCCommand = &Command{
   117  	Format:           fmt.Sprintf("%s%s\n", lnutil.White("claim"), lnutil.ReqColor("R")),
   118  	Description:      "Claim any on-chain HTLC that matches the given preimage. Use this to claim an HTLC after the channel is broken.\n",
   119  	ShortDescription: "Clear HTLC of the given index from the given channel.\n",
   120  }
   121  
   122  var paymultihopCommand = &Command{
   123  	Format: fmt.Sprintf("%s%s\n", lnutil.White("paymultihop"), lnutil.ReqColor("dest cointype amount")),
   124  	Description: fmt.Sprintf("%s\n%s%s\n%s%s\n%s%s\n",
   125  		"Tries to pay using a multi-hop payment. Will fail if no route available",
   126  		lnutil.White("dest"), ": Destination address",
   127  		lnutil.White("cointype"), ": Coin type to pay",
   128  		lnutil.White("amount"), ": Amount to pay"),
   129  	ShortDescription: "Pay via multi-hop.\n",
   130  }
   131  
   132  func (lc *litAfClient) History(textArgs []string) error {
   133  	if len(textArgs) > 0 && textArgs[0] == "-h" {
   134  		fmt.Fprintf(color.Output, historyCommand.Format)
   135  		fmt.Fprintf(color.Output, historyCommand.Description)
   136  		return nil
   137  	}
   138  
   139  	args := new(litrpc.StateDumpArgs)
   140  	reply := new(litrpc.StateDumpReply)
   141  
   142  	err := lc.Call("LitRPC.StateDump", args, reply)
   143  	if err != nil {
   144  		return err
   145  	}
   146  
   147  	for _, tx := range reply.Txs {
   148  		fmt.Fprintf(color.Output, "Pkh: %x, Idx: %d, Sig: %x, Txid: %x, Data: %x, Amt: %d\n", tx.Pkh, tx.Idx, tx.Sig, tx.Txid, tx.Data, tx.Amt)
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  // CheckHelpCommand checks whether the user wants help regarding the command
   155  // or passed invalid arguments. Also checks for expected length of command
   156  // and returns and error if the expected length is different.
   157  func CheckHelpCommand(command *Command, textArgs []string, expectedLength int) (bool, error) {
   158  	if len(textArgs) > 0 && textArgs[0] == "-h" {
   159  		fmt.Fprintf(color.Output, command.Format)
   160  		fmt.Fprintf(color.Output, command.Description)
   161  		return true, nil // stop Execution if the guy just wants help
   162  	}
   163  	if len(textArgs) < expectedLength {
   164  		// if number of args are less than expected, return
   165  		return true, fmt.Errorf(command.Format) // stop execution in case of err
   166  	}
   167  	return false, nil
   168  }
   169  
   170  func (lc *litAfClient) FundChannel(textArgs []string) error {
   171  	stopEx, err := CheckHelpCommand(fundCommand, textArgs, 4)
   172  	if err != nil || stopEx {
   173  		return err
   174  	}
   175  	args := new(litrpc.FundArgs)
   176  	reply := new(litrpc.FundReply)
   177  
   178  	peer, err := strconv.Atoi(textArgs[0])
   179  	if err != nil {
   180  		return err
   181  	}
   182  	coinType, err := strconv.Atoi(textArgs[1])
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	cCap, err := strconv.Atoi(textArgs[2])
   188  	if err != nil {
   189  		return err
   190  	}
   191  	iSend, err := strconv.Atoi(textArgs[3])
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	if len(textArgs) > 4 {
   197  		data, err := hex.DecodeString(textArgs[4])
   198  		if err != nil {
   199  			// Wasn't valid hex, copy directly and truncate
   200  			copy(args.Data[:], textArgs[3])
   201  		} else {
   202  			copy(args.Data[:], data[:])
   203  		}
   204  	}
   205  
   206  	args.Peer = uint32(peer)
   207  	args.CoinType = uint32(coinType)
   208  	args.Capacity = int64(cCap)
   209  	args.InitialSend = int64(iSend)
   210  
   211  	err = lc.Call("LitRPC.FundChannel", args, reply)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	fmt.Fprintf(color.Output, "funded channel %d (height: %d)\n", reply.ChanIdx, reply.FundHeight)
   217  	return nil
   218  }
   219  
   220  func (lc *litAfClient) DualFund(textArgs []string) error {
   221  	stopEx, err := CheckHelpCommand(dualFundCommand, textArgs, 2)
   222  	if err != nil || stopEx {
   223  		return err
   224  	}
   225  	return nil
   226  }
   227  
   228  // Mutually fund a channel
   229  func (lc *litAfClient) DualFundChannel(textArgs []string) error {
   230  	stopEx, err := CheckHelpCommand(dualFundStartCommand, textArgs, 4)
   231  	if err != nil || stopEx {
   232  		return err
   233  	}
   234  
   235  	args := new(litrpc.DualFundArgs)
   236  	reply := new(litrpc.StatusReply)
   237  
   238  	peer, err := strconv.Atoi(textArgs[0])
   239  	if err != nil {
   240  		return err
   241  	}
   242  	coinType, err := strconv.Atoi(textArgs[1])
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	ourAmt, err := strconv.Atoi(textArgs[2])
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	theirAmt, err := strconv.Atoi(textArgs[3])
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	args.Peer = uint32(peer)
   258  	args.CoinType = uint32(coinType)
   259  	args.OurAmount = int64(ourAmt)
   260  	args.TheirAmount = int64(theirAmt)
   261  
   262  	err = lc.Call("LitRPC.DualFundChannel", args, reply)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   268  	return nil
   269  }
   270  
   271  func (lc *litAfClient) dualFundRespond(aor bool) error {
   272  	reply := new(litrpc.StatusReply)
   273  	args := new(litrpc.DualFundRespondArgs)
   274  	args.AcceptOrDecline = aor
   275  	err := lc.Call("LitRPC.DualFundRespond", args, reply)
   276  	if err != nil {
   277  		return err
   278  	}
   279  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   280  	return nil
   281  }
   282  
   283  // Decline mutual funding of a channel
   284  func (lc *litAfClient) DualFundDecline(textArgs []string) error {
   285  	stopEx, err := CheckHelpCommand(dualFundDeclineCommand, textArgs, 0)
   286  	if err != nil || stopEx {
   287  		return err
   288  	}
   289  
   290  	return lc.dualFundRespond(false)
   291  }
   292  
   293  // Accept mutual funding of a channel
   294  func (lc *litAfClient) DualFundAccept(textArgs []string) error {
   295  	stopEx, err := CheckHelpCommand(dualFundAcceptCommand, textArgs, 0)
   296  	if err != nil || stopEx {
   297  		return err
   298  	}
   299  
   300  	return lc.dualFundRespond(true)
   301  }
   302  
   303  // Request close of a channel.  Need to pass in peer, channel index
   304  func (lc *litAfClient) CloseChannel(textArgs []string) error {
   305  	stopEx, err := CheckHelpCommand(closeCommand, textArgs, 1)
   306  	if err != nil || stopEx {
   307  		return err
   308  	}
   309  
   310  	args := new(litrpc.ChanArgs)
   311  	reply := new(litrpc.StatusReply)
   312  
   313  	cIdx, err := strconv.Atoi(textArgs[0])
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	args.ChanIdx = uint32(cIdx)
   319  
   320  	err = lc.Call("LitRPC.CloseChannel", args, reply)
   321  	if err != nil {
   322  		return err
   323  	}
   324  
   325  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   326  	return nil
   327  }
   328  
   329  // Almost exactly the same as CloseChannel.  Maybe make "break" a bool...?
   330  func (lc *litAfClient) BreakChannel(textArgs []string) error {
   331  	stopEx, err := CheckHelpCommand(breakCommand, textArgs, 1)
   332  	if err != nil || stopEx {
   333  		return err
   334  	}
   335  
   336  	args := new(litrpc.ChanArgs)
   337  	reply := new(litrpc.StatusReply)
   338  
   339  	cIdx, err := strconv.Atoi(textArgs[0])
   340  	if err != nil {
   341  		return err
   342  	}
   343  
   344  	args.ChanIdx = uint32(cIdx)
   345  
   346  	err = lc.Call("LitRPC.BreakChannel", args, reply)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   352  	return nil
   353  }
   354  
   355  // Push is the shell command which calls PushChannel
   356  func (lc *litAfClient) Push(textArgs []string) error {
   357  	stopEx, err := CheckHelpCommand(pushCommand, textArgs, 2)
   358  	if err != nil || stopEx {
   359  		return err
   360  	}
   361  
   362  	args := new(litrpc.PushArgs)
   363  	reply := new(litrpc.PushReply)
   364  
   365  	// this stuff is all the same as in cclose, should put into a function...
   366  	cIdx, err := strconv.Atoi(textArgs[0])
   367  	if err != nil {
   368  		return err
   369  	}
   370  	amt, err := strconv.Atoi(textArgs[1])
   371  	if err != nil {
   372  		return err
   373  	}
   374  
   375  	times := int(1)
   376  	if len(textArgs) > 2 {
   377  		times, err = strconv.Atoi(textArgs[2])
   378  		if err != nil {
   379  			return err
   380  		}
   381  	}
   382  
   383  	if len(textArgs) > 3 {
   384  		data, err := hex.DecodeString(textArgs[3])
   385  		if err != nil {
   386  			// Wasn't valid hex, copy directly and truncate
   387  			copy(args.Data[:], textArgs[3])
   388  		} else {
   389  			copy(args.Data[:], data[:])
   390  		}
   391  	}
   392  
   393  	args.ChanIdx = uint32(cIdx)
   394  	args.Amt = int64(amt)
   395  
   396  	for times > 0 {
   397  		err := lc.Call("LitRPC.Push", args, reply)
   398  		if err != nil {
   399  			return err
   400  		}
   401  		fmt.Fprintf(color.Output, "Pushed %s at state %s\n", lnutil.SatoshiColor(int64(amt)), lnutil.White(reply.StateIndex))
   402  		times--
   403  	}
   404  
   405  	return nil
   406  }
   407  
   408  func (lc *litAfClient) Dump(textArgs []string) error {
   409  	pReply := new(litrpc.DumpReply)
   410  	pArgs := new(litrpc.NoArgs)
   411  
   412  	err := lc.Call("LitRPC.DumpPrivs", pArgs, pReply)
   413  	if err != nil {
   414  		return err
   415  	}
   416  	fmt.Fprintf(color.Output, "Private keys for all channels and utxos:\n")
   417  
   418  	// Display DumpPriv info
   419  	for i, t := range pReply.Privs {
   420  		fmt.Fprintf(color.Output, "%d %s h:%d amt:%s %s ",
   421  			i, lnutil.OutPoint(t.OutPoint), t.Height,
   422  			lnutil.SatoshiColor(t.Amt), t.CoinType)
   423  		if t.Delay != 0 {
   424  			fmt.Fprintf(color.Output, " delay: %d", t.Delay)
   425  		}
   426  		if !t.Witty {
   427  			fmt.Fprintf(color.Output, " non-witness")
   428  		}
   429  		if len(t.PairKey) > 1 {
   430  			fmt.Fprintf(
   431  				color.Output, "\nPair Pubkey: %s", lnutil.Green(t.PairKey))
   432  		}
   433  		fmt.Fprintf(color.Output, "\n\tprivkey: %s", lnutil.Red(t.WIF))
   434  		fmt.Fprintf(color.Output, "\n")
   435  	}
   436  
   437  	return nil
   438  }
   439  
   440  func (lc *litAfClient) Watch(textArgs []string) error {
   441  	stopEx, err := CheckHelpCommand(watchCommand, textArgs, 2)
   442  	if err != nil || stopEx {
   443  		return err
   444  	}
   445  
   446  	args := new(litrpc.WatchArgs)
   447  	reply := new(litrpc.WatchReply)
   448  
   449  	cIdx, err := strconv.Atoi(textArgs[0])
   450  	if err != nil {
   451  		return err
   452  	}
   453  
   454  	peer, err := strconv.Atoi(textArgs[1])
   455  	if err != nil {
   456  		return err
   457  	}
   458  
   459  	args.ChanIdx = uint32(cIdx)
   460  	args.SendToPeer = uint32(peer)
   461  
   462  	err = lc.Call("LitRPC.Watch", args, reply)
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	fmt.Fprintf(color.Output, "Send channel %d data to peer %d\n",
   468  		args.ChanIdx, args.SendToPeer)
   469  
   470  	return nil
   471  }
   472  
   473  // Add is the shell command which calls AddHTLC
   474  func (lc *litAfClient) AddHTLC(textArgs []string) error {
   475  	stopEx, err := CheckHelpCommand(addHTLCCommand, textArgs, 3)
   476  
   477  	if err != nil || stopEx {
   478  		return err
   479  	}
   480  
   481  	args := new(litrpc.AddHTLCArgs)
   482  	reply := new(litrpc.AddHTLCReply)
   483  
   484  	// this stuff is all the same as in cclose, should put into a function...
   485  	cIdx, err := strconv.Atoi(textArgs[0])
   486  	if err != nil {
   487  		return err
   488  	}
   489  	amt, err := strconv.Atoi(textArgs[1])
   490  	if err != nil {
   491  		return err
   492  	}
   493  	locktime, err := strconv.Atoi(textArgs[2])
   494  	if err != nil {
   495  		return err
   496  	}
   497  
   498  	RHash, err := hex.DecodeString(textArgs[3])
   499  	if err != nil {
   500  		return err
   501  	}
   502  	copy(args.RHash[:], RHash[:])
   503  
   504  	if len(textArgs) > 4 {
   505  		data, err := hex.DecodeString(textArgs[4])
   506  		if err != nil {
   507  			// Wasn't valid hex, copy directly and truncate
   508  			copy(args.Data[:], textArgs[4])
   509  		} else {
   510  			copy(args.Data[:], data[:])
   511  		}
   512  	}
   513  
   514  	args.ChanIdx = uint32(cIdx)
   515  	args.Amt = int64(amt)
   516  	args.LockTime = uint32(locktime)
   517  
   518  	err = lc.Call("LitRPC.AddHTLC", args, reply)
   519  	if err != nil {
   520  		return err
   521  	}
   522  	fmt.Fprintf(color.Output, "Added HTLC %s at state %s idx %s\n", lnutil.SatoshiColor(int64(amt)), lnutil.White(reply.StateIndex), lnutil.White(reply.HTLCIndex))
   523  
   524  	return nil
   525  }
   526  
   527  // Clear is the shell command which calls ClearHTLC
   528  func (lc *litAfClient) ClearHTLC(textArgs []string) error {
   529  	stopEx, err := CheckHelpCommand(clearHTLCCommand, textArgs, 3)
   530  	if err != nil || stopEx {
   531  		return err
   532  	}
   533  
   534  	args := new(litrpc.ClearHTLCArgs)
   535  	reply := new(litrpc.ClearHTLCReply)
   536  
   537  	// this stuff is all the same as in cclose, should put into a function...
   538  	cIdx, err := strconv.Atoi(textArgs[0])
   539  	if err != nil {
   540  		return err
   541  	}
   542  	HTLCIdx, err := strconv.Atoi(textArgs[1])
   543  	if err != nil {
   544  		return err
   545  	}
   546  
   547  	R, err := hex.DecodeString(textArgs[2])
   548  	if err != nil {
   549  		return err
   550  	}
   551  	copy(args.R[:], R[:])
   552  
   553  	if len(textArgs) > 3 {
   554  		data, err := hex.DecodeString(textArgs[3])
   555  		if err != nil {
   556  			// Wasn't valid hex, copy directly and truncate
   557  			copy(args.Data[:], textArgs[3])
   558  		} else {
   559  			copy(args.Data[:], data[:])
   560  		}
   561  	}
   562  
   563  	args.ChanIdx = uint32(cIdx)
   564  	args.HTLCIdx = uint32(HTLCIdx)
   565  
   566  	err = lc.Call("LitRPC.ClearHTLC", args, reply)
   567  	if err != nil {
   568  		return err
   569  	}
   570  	fmt.Fprintf(color.Output, "Cleared HTLC %s at state %s\n", lnutil.White(HTLCIdx), lnutil.White(reply.StateIndex))
   571  
   572  	return nil
   573  }
   574  
   575  // Clear is the shell command which calls ClearHTLC
   576  func (lc *litAfClient) ClaimHTLC(textArgs []string) error {
   577  	stopEx, err := CheckHelpCommand(claimHTLCCommand, textArgs, 1)
   578  	if err != nil || stopEx {
   579  		return err
   580  	}
   581  
   582  	args := new(litrpc.ClaimHTLCArgs)
   583  	reply := new(litrpc.TxidsReply)
   584  
   585  	R, err := hex.DecodeString(textArgs[0])
   586  	if err != nil {
   587  		return err
   588  	}
   589  	copy(args.R[:], R[:])
   590  
   591  	err = lc.Call("LitRPC.ClaimHTLC", args, reply)
   592  	if err != nil {
   593  		return err
   594  	}
   595  	for _, txid := range reply.Txids {
   596  		fmt.Fprintf(color.Output, "Claimed HTLC with txid %s\n", lnutil.White(txid))
   597  	}
   598  
   599  	return nil
   600  }
   601  
   602  func (lc *litAfClient) PayMultihop(textArgs []string) error {
   603  	if len(textArgs) > 0 && textArgs[0] == "-h" {
   604  		fmt.Fprintf(color.Output, paymultihopCommand.Format)
   605  		fmt.Fprintf(color.Output, paymultihopCommand.Description)
   606  		return nil
   607  	}
   608  
   609  	args := new(litrpc.PayMultihopArgs)
   610  	reply := new(litrpc.StatusReply)
   611  
   612  	if len(textArgs) < 3 {
   613  		return fmt.Errorf("need args: paymultihop dest destCoinType originCoinType amount")
   614  	}
   615  
   616  	args.DestLNAdr = textArgs[0]
   617  	destcointype, err := strconv.Atoi(textArgs[1])
   618  	if err != nil {
   619  		return err
   620  	}
   621  	args.DestCoinType = uint32(destcointype)
   622  
   623  	origincointype, err := strconv.Atoi(textArgs[2])
   624  	if err != nil {
   625  		return err
   626  	}
   627  	args.OriginCoinType = uint32(origincointype)
   628  
   629  	amount, err := strconv.Atoi(textArgs[3])
   630  	if err != nil {
   631  		return err
   632  	}
   633  	args.Amt = int64(amount)
   634  
   635  	err = lc.Call("LitRPC.PayMultihop", args, reply)
   636  	if err != nil {
   637  		return err
   638  	}
   639  
   640  	fmt.Fprintf(color.Output, "%s\n", reply.Status)
   641  
   642  	return nil
   643  }