Skip to content

Commit 4f2b10f

Browse files
committed
loop: add instantout cmd
1 parent 0e84414 commit 4f2b10f

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

cmd/loop/instantout.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/lightninglabs/loop/instantout/reservation"
10+
"github.com/lightninglabs/loop/looprpc"
11+
"github.com/urfave/cli"
12+
)
13+
14+
var instantOutCommand = cli.Command{
15+
Name: "instantout",
16+
Usage: "perform an instant off-chain to on-chain swap (looping out)",
17+
Description: `
18+
Attempts to instantly loop out into the backing lnd's wallet. The amount
19+
will be chosen via the cli.
20+
`,
21+
Flags: []cli.Flag{
22+
cli.StringFlag{
23+
Name: "channel",
24+
Usage: "the comma-separated list of short " +
25+
"channel IDs of the channels to loop out",
26+
},
27+
},
28+
Action: instantOut,
29+
}
30+
31+
func instantOut(ctx *cli.Context) error {
32+
// Parse outgoing channel set. Don't string split if the flag is empty.
33+
// Otherwise, strings.Split returns a slice of length one with an empty
34+
// element.
35+
var outgoingChanSet []uint64
36+
if ctx.IsSet("channel") {
37+
chanStrings := strings.Split(ctx.String("channel"), ",")
38+
for _, chanString := range chanStrings {
39+
chanID, err := strconv.ParseUint(chanString, 10, 64)
40+
if err != nil {
41+
return fmt.Errorf("error parsing channel id "+
42+
"\"%v\"", chanString)
43+
}
44+
outgoingChanSet = append(outgoingChanSet, chanID)
45+
}
46+
}
47+
48+
// First set up the swap client itself.
49+
client, cleanup, err := getClient(ctx)
50+
if err != nil {
51+
return err
52+
}
53+
defer cleanup()
54+
55+
// Now we fetch all the confirmed reservations.
56+
reservations, err := client.ListReservations(
57+
context.Background(), &looprpc.ListReservationsRequest{},
58+
)
59+
if err != nil {
60+
return err
61+
}
62+
63+
var (
64+
confirmedReservations []*looprpc.ClientReservation
65+
totalAmt int64
66+
idx int
67+
)
68+
69+
for _, res := range reservations.Reservations {
70+
if res.State != string(reservation.Confirmed) {
71+
continue
72+
}
73+
74+
confirmedReservations = append(confirmedReservations, res)
75+
}
76+
77+
if len(confirmedReservations) == 0 {
78+
fmt.Printf("No confirmed reservations found \n")
79+
return nil
80+
}
81+
82+
fmt.Printf("Available reservations: \n\n")
83+
for _, res := range confirmedReservations {
84+
idx++
85+
fmt.Printf("Reservation %v: %v \n", idx, res.Amount)
86+
totalAmt += int64(res.Amount)
87+
}
88+
89+
fmt.Println()
90+
fmt.Printf("Max amount to instant out: %v\n", totalAmt)
91+
fmt.Println()
92+
93+
fmt.Println("Select reservations for instantout (e.g. '1,2,3')")
94+
fmt.Println("Type 'ALL' to use all available reservations.")
95+
96+
var answer string
97+
fmt.Scanln(&answer)
98+
99+
// Parse
100+
var selectedReservations [][]byte
101+
switch answer {
102+
case "ALL":
103+
for _, res := range confirmedReservations {
104+
selectedReservations = append(
105+
selectedReservations,
106+
res.ReservationId,
107+
)
108+
}
109+
110+
case "":
111+
return fmt.Errorf("no reservations selected")
112+
113+
default:
114+
selectedIndexes := strings.Split(answer, ",")
115+
selectedIndexMap := make(map[int]struct{})
116+
for _, idxStr := range selectedIndexes {
117+
idx, err := strconv.Atoi(idxStr)
118+
if err != nil {
119+
return err
120+
}
121+
if idx < 0 {
122+
return fmt.Errorf("invalid index %v", idx)
123+
}
124+
125+
if idx > len(confirmedReservations) {
126+
return fmt.Errorf("invalid index %v", idx)
127+
}
128+
if _, ok := selectedIndexMap[idx]; ok {
129+
return fmt.Errorf("duplicate index %v", idx)
130+
}
131+
132+
selectedReservations = append(
133+
selectedReservations,
134+
confirmedReservations[idx-1].ReservationId,
135+
)
136+
137+
selectedIndexMap[idx] = struct{}{}
138+
}
139+
140+
}
141+
142+
fmt.Println("Starting instant swap out")
143+
144+
// Now we can request the instant out swap.
145+
instantOutRes, err := client.InstantOut(
146+
context.Background(),
147+
&looprpc.InstantOutRequest{
148+
ReservationIds: selectedReservations,
149+
OutgoingChanSet: outgoingChanSet,
150+
},
151+
)
152+
153+
if err != nil {
154+
return err
155+
}
156+
157+
fmt.Printf("Instant out swap initiated with ID: %x, State: %v \n",
158+
instantOutRes.InstantOutHash, instantOutRes.State)
159+
160+
if instantOutRes.SweepTxId != "" {
161+
fmt.Printf("Sweepless sweep tx id: %v \n",
162+
instantOutRes.SweepTxId)
163+
}
164+
165+
return nil
166+
}

cmd/loop/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ func main() {
148148
listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand,
149149
setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand,
150150
getInfoCommand, abandonSwapCommand, reservationsCommands,
151+
instantOutCommand,
151152
}
152153

153154
err := app.Run(os.Args)

0 commit comments

Comments
 (0)