From 6328b2e9898e7c04a1e854b39609ab8809fe3bc4 Mon Sep 17 00:00:00 2001
From: Joost Jager <joost.jager@gmail.com>
Date: Thu, 29 Aug 2019 13:03:37 +0200
Subject: [PATCH] lncli: add BuildRoute function

---
 cmd/lncli/cmd_build_route.go  | 94 +++++++++++++++++++++++++++++++++++
 cmd/lncli/routerrpc_active.go |  6 ++-
 2 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 cmd/lncli/cmd_build_route.go

diff --git a/cmd/lncli/cmd_build_route.go b/cmd/lncli/cmd_build_route.go
new file mode 100644
index 000000000..64cda08fa
--- /dev/null
+++ b/cmd/lncli/cmd_build_route.go
@@ -0,0 +1,94 @@
+// +build routerrpc
+
+package main
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/lightningnetwork/lnd"
+	"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
+	"github.com/lightningnetwork/lnd/routing/route"
+	"github.com/urfave/cli"
+)
+
+var buildRouteCommand = cli.Command{
+	Name:     "buildroute",
+	Category: "Payments",
+	Usage:    "Build a route from a list of hop pubkeys.",
+	Action:   actionDecorator(buildRoute),
+	Flags: []cli.Flag{
+		cli.Int64Flag{
+			Name: "amt",
+			Usage: "the amount to send expressed in satoshis. If" +
+				"not set, the minimum routable amount is used",
+		},
+		cli.Int64Flag{
+			Name: "final_cltv_delta",
+			Usage: "number of blocks the last hop has to reveal " +
+				"the preimage",
+			Value: lnd.DefaultBitcoinTimeLockDelta,
+		},
+		cli.StringFlag{
+			Name:  "hops",
+			Usage: "comma separated hex pubkeys",
+		},
+		cli.Uint64Flag{
+			Name: "outgoing_chan_id",
+			Usage: "short channel id of the outgoing channel to " +
+				"use for the first hop of the payment",
+			Value: 0,
+		},
+	},
+}
+
+func buildRoute(ctx *cli.Context) error {
+	conn := getClientConn(ctx, false)
+	defer conn.Close()
+
+	client := routerrpc.NewRouterClient(conn)
+
+	if !ctx.IsSet("hops") {
+		return errors.New("hops required")
+	}
+
+	// Build list of hop addresses for the rpc.
+	hops := strings.Split(ctx.String("hops"), ",")
+	rpcHops := make([][]byte, 0, len(hops))
+	for _, k := range hops {
+		pubkey, err := route.NewVertexFromStr(k)
+		if err != nil {
+			return fmt.Errorf("error parsing %v: %v", k, err)
+		}
+		rpcHops = append(rpcHops, pubkey[:])
+	}
+
+	var amtMsat int64
+	hasAmt := ctx.IsSet("amt")
+	if hasAmt {
+		amtMsat = ctx.Int64("amt") * 1000
+		if amtMsat == 0 {
+			return fmt.Errorf("non-zero amount required")
+		}
+	}
+
+	// Call BuildRoute rpc.
+	req := &routerrpc.BuildRouteRequest{
+		AmtMsat:        amtMsat,
+		FinalCltvDelta: int32(ctx.Int64("final_cltv_delta")),
+		HopPubkeys:     rpcHops,
+		OutgoingChanId: ctx.Uint64("outgoing_chan_id"),
+	}
+
+	rpcCtx := context.Background()
+	route, err := client.BuildRoute(rpcCtx, req)
+	if err != nil {
+		return err
+	}
+
+	printJSON(route)
+
+	return nil
+}
diff --git a/cmd/lncli/routerrpc_active.go b/cmd/lncli/routerrpc_active.go
index f323aed1e..4d2172679 100644
--- a/cmd/lncli/routerrpc_active.go
+++ b/cmd/lncli/routerrpc_active.go
@@ -6,5 +6,9 @@ import "github.com/urfave/cli"
 
 // routerCommands will return nil for non-routerrpc builds.
 func routerCommands() []cli.Command {
-	return []cli.Command{queryMissionControlCommand, resetMissionControlCommand}
+	return []cli.Command{
+		queryMissionControlCommand,
+		resetMissionControlCommand,
+		buildRouteCommand,
+	}
 }