mirror of https://github.com/lightninglabs/loop
loop: add instantout cmd
parent
7cafbe957d
commit
8c7c7cf8b5
@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/lightninglabs/loop/instantout/reservation"
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var instantOutCommand = cli.Command{
|
||||
Name: "instantout",
|
||||
Usage: "perform an instant off-chain to on-chain swap (looping out)",
|
||||
Description: `
|
||||
Attempts to instantly loop out into the backing lnd's wallet. The amount
|
||||
will be chosen via the cli.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "channel",
|
||||
Usage: "the comma-separated list of short " +
|
||||
"channel IDs of the channels to loop out",
|
||||
},
|
||||
},
|
||||
Action: instantOut,
|
||||
}
|
||||
|
||||
func instantOut(ctx *cli.Context) error {
|
||||
// Parse outgoing channel set. Don't string split if the flag is empty.
|
||||
// Otherwise, strings.Split returns a slice of length one with an empty
|
||||
// element.
|
||||
var outgoingChanSet []uint64
|
||||
if ctx.IsSet("channel") {
|
||||
chanStrings := strings.Split(ctx.String("channel"), ",")
|
||||
for _, chanString := range chanStrings {
|
||||
chanID, err := strconv.ParseUint(chanString, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing channel id "+
|
||||
"\"%v\"", chanString)
|
||||
}
|
||||
outgoingChanSet = append(outgoingChanSet, chanID)
|
||||
}
|
||||
}
|
||||
|
||||
// First set up the swap client itself.
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
// Now we fetch all the confirmed reservations.
|
||||
reservations, err := client.ListReservations(
|
||||
context.Background(), &looprpc.ListReservationsRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
confirmedReservations []*looprpc.ClientReservation
|
||||
totalAmt int64
|
||||
idx int
|
||||
)
|
||||
|
||||
for _, res := range reservations.Reservations {
|
||||
if res.State != string(reservation.Confirmed) {
|
||||
continue
|
||||
}
|
||||
|
||||
confirmedReservations = append(confirmedReservations, res)
|
||||
}
|
||||
|
||||
if len(confirmedReservations) == 0 {
|
||||
fmt.Printf("No confirmed reservations found \n")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Available reservations: \n\n")
|
||||
for _, res := range confirmedReservations {
|
||||
idx++
|
||||
fmt.Printf("Reservation %v: %v \n", idx, res.Amount)
|
||||
totalAmt += int64(res.Amount)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Max amount to instant out: %v\n", totalAmt)
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("Select reservations for instantout (e.g. '1,2,3')")
|
||||
fmt.Println("Type 'ALL' to use all available reservations.")
|
||||
|
||||
var answer string
|
||||
fmt.Scanln(&answer)
|
||||
|
||||
// Parse
|
||||
var selectedReservations [][]byte
|
||||
switch answer {
|
||||
case "ALL":
|
||||
for _, res := range confirmedReservations {
|
||||
selectedReservations = append(
|
||||
selectedReservations,
|
||||
res.ReservationId,
|
||||
)
|
||||
}
|
||||
|
||||
case "":
|
||||
return fmt.Errorf("no reservations selected")
|
||||
|
||||
default:
|
||||
selectedIndexes := strings.Split(answer, ",")
|
||||
selectedIndexMap := make(map[int]struct{})
|
||||
for _, idxStr := range selectedIndexes {
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if idx < 0 {
|
||||
return fmt.Errorf("invalid index %v", idx)
|
||||
}
|
||||
|
||||
if idx > len(confirmedReservations) {
|
||||
return fmt.Errorf("invalid index %v", idx)
|
||||
}
|
||||
if _, ok := selectedIndexMap[idx]; ok {
|
||||
return fmt.Errorf("duplicate index %v", idx)
|
||||
}
|
||||
|
||||
selectedReservations = append(
|
||||
selectedReservations,
|
||||
confirmedReservations[idx-1].ReservationId,
|
||||
)
|
||||
|
||||
selectedIndexMap[idx] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Starting instant swap out")
|
||||
|
||||
// Now we can request the instant out swap.
|
||||
instantOutRes, err := client.InstantOut(
|
||||
context.Background(),
|
||||
&looprpc.InstantOutRequest{
|
||||
ReservationIds: selectedReservations,
|
||||
OutgoingChanSet: outgoingChanSet,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Instant out swap initiated with ID: %x, State: %v \n",
|
||||
instantOutRes.InstantOutHash, instantOutRes.State)
|
||||
|
||||
if instantOutRes.SweepTxId != "" {
|
||||
fmt.Printf("Sweepless sweep tx id: %v \n",
|
||||
instantOutRes.SweepTxId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue