Skip to content

Commit 5ba328a

Browse files
committed
cmd: static address loop-in
1 parent 6f9349a commit 5ba328a

File tree

3 files changed

+195
-5
lines changed

3 files changed

+195
-5
lines changed

cmd/loop/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ func displayInDetails(req *looprpc.QuoteRequest,
280280
"wallet.\n\n")
281281
}
282282

283+
if req.DepositOutpoints != nil {
284+
fmt.Printf("On-chain fees for static address loop-ins is not " +
285+
"included.\nThey were already paid when the deposits " +
286+
"were created.\n\n")
287+
}
288+
283289
printQuoteInResp(req, resp, verbose)
284290

285291
fmt.Printf("\nCONTINUE SWAP? (y/n): ")

cmd/loop/quote.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,11 @@ func printQuoteInResp(req *looprpc.QuoteRequest,
228228

229229
totalFee := resp.HtlcPublishFeeSat + resp.SwapFeeSat
230230

231-
fmt.Printf(satAmtFmt, "Send on-chain:", req.Amt)
231+
if req.DepositOutpoints != nil {
232+
fmt.Printf(satAmtFmt, "Previously deposited on-chain:", req.Amt)
233+
} else {
234+
fmt.Printf(satAmtFmt, "Send on-chain:", req.Amt)
235+
}
232236
fmt.Printf(satAmtFmt, "Receive off-chain:", req.Amt-totalFee)
233237

234238
switch {

cmd/loop/staticaddr.go

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import (
99
"strings"
1010

1111
"github.com/btcsuite/btcd/chaincfg/chainhash"
12+
"github.com/lightninglabs/loop/labels"
1213
"github.com/lightninglabs/loop/looprpc"
14+
"github.com/lightninglabs/loop/swapserverrpc"
15+
"github.com/lightningnetwork/lnd/routing/route"
1316
"github.com/urfave/cli"
1417
)
1518

@@ -24,6 +27,27 @@ var staticAddressCommands = cli.Command{
2427
withdrawalCommand,
2528
summaryCommand,
2629
},
30+
Description: `
31+
Requests a loop-in swap based on static address deposits.
32+
`,
33+
Flags: []cli.Flag{
34+
cli.StringSliceFlag{
35+
Name: "utxo",
36+
Usage: "specify the utxos of deposits as " +
37+
"outpoints(tx:idx) that should be looped in.",
38+
},
39+
cli.BoolFlag{
40+
Name: "all",
41+
Usage: "loop in all static address deposits.",
42+
},
43+
lastHopFlag,
44+
labelFlag,
45+
routeHintsFlag,
46+
privateFlag,
47+
forceFlag,
48+
verboseFlag,
49+
},
50+
Action: staticAddressLoopIn,
2751
}
2852

2953
var newStaticAddressCommand = cli.Command{
@@ -194,10 +218,14 @@ var summaryCommand = cli.Command{
194218
cli.StringFlag{
195219
Name: "filter",
196220
Usage: "specify a filter to only display deposits in " +
197-
"the specified state. The state can be one " +
198-
"of [deposited|withdrawing|withdrawn|" +
199-
"publish_expired_deposit|" +
200-
"wait_for_expiry_sweep|expired|failed].",
221+
"the specified state. Leaving out the filter " +
222+
"returns all deposits.\nThe state can be one " +
223+
"of the following: \n" +
224+
"deposited\nwithdrawing\nwithdrawn\n" +
225+
"loopingin\nloopedin\n" +
226+
"publish_expired_deposit\n" +
227+
"sweep_htlc_timeout\nhtlc_timeout_swept\n" +
228+
"wait_for_expiry_sweep\nexpired\nfailed\n.",
201229
},
202230
},
203231
Action: summary,
@@ -229,9 +257,21 @@ func summary(ctx *cli.Context) error {
229257
case "withdrawn":
230258
filterState = looprpc.DepositState_WITHDRAWN
231259

260+
case "loopingin":
261+
filterState = looprpc.DepositState_LOOPING_IN
262+
263+
case "loopedin":
264+
filterState = looprpc.DepositState_LOOPED_IN
265+
232266
case "publish_expired_deposit":
233267
filterState = looprpc.DepositState_PUBLISH_EXPIRED
234268

269+
case "sweep_htlc_timeout":
270+
filterState = looprpc.DepositState_SWEEP_HTLC_TIMEOUT
271+
272+
case "htlc_timeout_swept":
273+
filterState = looprpc.DepositState_HTLC_TIMEOUT_SWEPT
274+
235275
case "wait_for_expiry_sweep":
236276
filterState = looprpc.DepositState_WAIT_FOR_EXPIRY_SWEEP
237277

@@ -297,3 +337,143 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
297337
OutputIndex: uint32(outputIndex),
298338
}, nil
299339
}
340+
341+
func staticAddressLoopIn(ctx *cli.Context) error {
342+
if ctx.NArg() > 0 {
343+
return cli.ShowCommandHelp(ctx, "static")
344+
}
345+
346+
client, cleanup, err := getClient(ctx)
347+
if err != nil {
348+
return err
349+
}
350+
defer cleanup()
351+
352+
var (
353+
ctxb = context.Background()
354+
isAllSelected = ctx.IsSet("all")
355+
isUtxoSelected = ctx.IsSet("utxo")
356+
label = ctx.String("static-loop-in")
357+
hints []*swapserverrpc.RouteHint
358+
lastHop []byte
359+
)
360+
361+
// Validate our label early so that we can fail before getting a quote.
362+
if err := labels.Validate(label); err != nil {
363+
return err
364+
}
365+
366+
// Private and route hints are mutually exclusive as setting private
367+
// means we retrieve our own route hints from the connected node.
368+
hints, err = validateRouteHints(ctx)
369+
if err != nil {
370+
return err
371+
}
372+
373+
if ctx.IsSet(lastHopFlag.Name) {
374+
lastHopVertex, err := route.NewVertexFromStr(
375+
ctx.String(lastHopFlag.Name),
376+
)
377+
if err != nil {
378+
return err
379+
}
380+
381+
lastHop = lastHopVertex[:]
382+
}
383+
384+
// Get the amount we need to quote for.
385+
summaryResp, err := client.GetStaticAddressSummary(
386+
ctxb, &looprpc.StaticAddressSummaryRequest{
387+
StateFilter: looprpc.DepositState_DEPOSITED,
388+
},
389+
)
390+
if err != nil {
391+
return err
392+
}
393+
394+
var depositOutpoints []string
395+
switch {
396+
case isAllSelected == isUtxoSelected:
397+
return errors.New("must select either all or some utxos")
398+
399+
case isAllSelected:
400+
depositOutpoints = depositsToOutpoints(
401+
summaryResp.FilteredDeposits,
402+
)
403+
404+
case isUtxoSelected:
405+
depositOutpoints = ctx.StringSlice("utxo")
406+
407+
default:
408+
return fmt.Errorf("unknown quote request")
409+
}
410+
411+
quoteReq := &looprpc.QuoteRequest{
412+
LoopInRouteHints: hints,
413+
LoopInLastHop: lastHop,
414+
Private: ctx.Bool(privateFlag.Name),
415+
DepositOutpoints: depositOutpoints,
416+
}
417+
quote, err := client.GetLoopInQuote(ctxb, quoteReq)
418+
if err != nil {
419+
return err
420+
}
421+
422+
limits := getInLimits(quote)
423+
424+
// populate the quote request with the sum of selected deposits and
425+
// prompt the user for acceptance.
426+
quoteReq.Amt = sumDeposits(
427+
depositOutpoints, summaryResp.FilteredDeposits,
428+
)
429+
if !(ctx.Bool("force") || ctx.Bool("f")) {
430+
err = displayInDetails(quoteReq, quote, ctx.Bool("verbose"))
431+
if err != nil {
432+
return err
433+
}
434+
}
435+
436+
req := &looprpc.StaticAddressLoopInRequest{
437+
Outpoints: depositOutpoints,
438+
MaxSwapFee: int64(limits.maxSwapFee),
439+
LastHop: lastHop,
440+
Label: ctx.String(labelFlag.Name),
441+
Initiator: defaultInitiator,
442+
RouteHints: hints,
443+
Private: ctx.Bool("private"),
444+
}
445+
446+
resp, err := client.StaticAddressLoopIn(ctxb, req)
447+
if err != nil {
448+
return err
449+
}
450+
451+
fmt.Printf("Static loop-in response from the server: %v\n", resp)
452+
453+
return nil
454+
}
455+
456+
func sumDeposits(outpoints []string, deposits []*looprpc.Deposit) int64 {
457+
var sum int64
458+
depositMap := make(map[string]*looprpc.Deposit)
459+
for _, deposit := range deposits {
460+
depositMap[deposit.Outpoint] = deposit
461+
}
462+
463+
for _, outpoint := range outpoints {
464+
if deposit, ok := depositMap[outpoint]; ok {
465+
sum += deposit.Value
466+
}
467+
}
468+
469+
return sum
470+
}
471+
472+
func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
473+
outpoints := make([]string, 0, len(deposits))
474+
for _, deposit := range deposits {
475+
outpoints = append(outpoints, deposit.Outpoint)
476+
}
477+
478+
return outpoints
479+
}

0 commit comments

Comments
 (0)