Skip to content

Commit c6e8664

Browse files
authored
Merge pull request #651 from sputn1ck/instantloopout_4
[4/?] Instant loop out: Add instant loop outs
2 parents d7860e7 + f725f07 commit c6e8664

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5917
-675
lines changed

cmd/loop/instantout.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
fmt.Println("Starting instant swap out")
142+
143+
// Now we can request the instant out swap.
144+
instantOutRes, err := client.InstantOut(
145+
context.Background(),
146+
&looprpc.InstantOutRequest{
147+
ReservationIds: selectedReservations,
148+
OutgoingChanSet: outgoingChanSet,
149+
},
150+
)
151+
152+
if err != nil {
153+
return err
154+
}
155+
156+
fmt.Printf("Instant out swap initiated with ID: %x, State: %v \n",
157+
instantOutRes.InstantOutHash, instantOutRes.State)
158+
159+
if instantOutRes.SweepTxId != "" {
160+
fmt.Printf("Sweepless sweep tx id: %v \n",
161+
instantOutRes.SweepTxId)
162+
}
163+
164+
return nil
165+
}

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)

fsm/example_fsm.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
stateDiagram-v2
33
[*] --> InitFSM: OnRequestStuff
44
InitFSM
5-
InitFSM --> StuffFailed: OnError
65
InitFSM --> StuffSentOut: OnStuffSentOut
6+
InitFSM --> StuffFailed: OnError
77
StuffFailed
88
StuffSentOut
9-
StuffSentOut --> StuffFailed: OnError
109
StuffSentOut --> StuffSuccess: OnStuffSuccess
10+
StuffSentOut --> StuffFailed: OnError
1111
StuffSuccess
1212
```

fsm/stateparser/stateparser.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sort"
1111

1212
"github.com/lightninglabs/loop/fsm"
13+
"github.com/lightninglabs/loop/instantout"
1314
"github.com/lightninglabs/loop/instantout/reservation"
1415
)
1516

@@ -49,6 +50,13 @@ func run() error {
4950
return err
5051
}
5152

53+
case "instantout":
54+
instantout := &instantout.FSM{}
55+
err = writeMermaidFile(fp, instantout.GetV1ReservationStates())
56+
if err != nil {
57+
return err
58+
}
59+
5260
default:
5361
fmt.Println("Missing or wrong argument: fsm must be one of:")
5462
fmt.Println("\treservations")

0 commit comments

Comments
 (0)