@@ -9,21 +9,55 @@ import (
9
9
"strings"
10
10
11
11
"github.com/btcsuite/btcd/chaincfg/chainhash"
12
+ "github.com/lightninglabs/loop/labels"
12
13
"github.com/lightninglabs/loop/looprpc"
14
+ "github.com/lightninglabs/loop/staticaddr/loopin"
15
+ "github.com/lightninglabs/loop/swapserverrpc"
16
+ "github.com/lightningnetwork/lnd/routing/route"
13
17
"github.com/urfave/cli"
14
18
)
15
19
16
20
var staticAddressCommands = cli.Command {
17
21
Name : "static" ,
18
22
ShortName : "s" ,
19
- Usage : "manage static loop-in addresses" ,
20
- Category : "StaticAddress" ,
23
+ Usage : "perform on-chain to off-chain swaps using static addresses." ,
21
24
Subcommands : []cli.Command {
22
25
newStaticAddressCommand ,
23
26
listUnspentCommand ,
24
27
withdrawalCommand ,
25
28
summaryCommand ,
26
29
},
30
+ Description : `
31
+ Requests a loop-in swap based on static address deposits. After the
32
+ creation of a static address funds can be send to it. Once the funds are
33
+ confirmed on-chain they can be swapped instantaneously. If deposited
34
+ funds are not needed they can we withdrawn back to the local lnd wallet.
35
+ ` ,
36
+ Flags : []cli.Flag {
37
+ cli.StringSliceFlag {
38
+ Name : "utxo" ,
39
+ Usage : "specify the utxos of deposits as " +
40
+ "outpoints(tx:idx) that should be looped in." ,
41
+ },
42
+ cli.BoolFlag {
43
+ Name : "all" ,
44
+ Usage : "loop in all static address deposits." ,
45
+ },
46
+ cli.DurationFlag {
47
+ Name : "payment_timeout" ,
48
+ Usage : "the maximum time in seconds that the server " +
49
+ "is allowed to take for the swap payment. " +
50
+ "The client can retry the swap with adjusted " +
51
+ "parameters after the payment timed out." ,
52
+ },
53
+ lastHopFlag ,
54
+ labelFlag ,
55
+ routeHintsFlag ,
56
+ privateFlag ,
57
+ forceFlag ,
58
+ verboseFlag ,
59
+ },
60
+ Action : staticAddressLoopIn ,
27
61
}
28
62
29
63
var newStaticAddressCommand = cli.Command {
@@ -169,10 +203,11 @@ func withdraw(ctx *cli.Context) error {
169
203
return fmt .Errorf ("unknown withdrawal request" )
170
204
}
171
205
172
- resp , err := client .WithdrawDeposits (ctxb , & looprpc.WithdrawDepositsRequest {
173
- Outpoints : outpoints ,
174
- All : isAllSelected ,
175
- })
206
+ resp , err := client .WithdrawDeposits (ctxb ,
207
+ & looprpc.WithdrawDepositsRequest {
208
+ Outpoints : outpoints ,
209
+ All : isAllSelected ,
210
+ })
176
211
if err != nil {
177
212
return err
178
213
}
@@ -194,10 +229,14 @@ var summaryCommand = cli.Command{
194
229
cli.StringFlag {
195
230
Name : "filter" ,
196
231
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]." ,
232
+ "the specified state. Leaving out the filter " +
233
+ "returns all deposits.\n The state can be one " +
234
+ "of the following: \n " +
235
+ "deposited\n withdrawing\n withdrawn\n " +
236
+ "looping_in\n looped_in\n " +
237
+ "publish_expired_deposit\n " +
238
+ "sweep_htlc_timeout\n htlc_timeout_swept\n " +
239
+ "wait_for_expiry_sweep\n expired\n failed\n ." ,
201
240
},
202
241
},
203
242
Action : summary ,
@@ -229,18 +268,27 @@ func summary(ctx *cli.Context) error {
229
268
case "withdrawn" :
230
269
filterState = looprpc .DepositState_WITHDRAWN
231
270
271
+ case "looping_in" :
272
+ filterState = looprpc .DepositState_LOOPING_IN
273
+
274
+ case "looped_in" :
275
+ filterState = looprpc .DepositState_LOOPED_IN
276
+
232
277
case "publish_expired_deposit" :
233
278
filterState = looprpc .DepositState_PUBLISH_EXPIRED
234
279
280
+ case "sweep_htlc_timeout" :
281
+ filterState = looprpc .DepositState_SWEEP_HTLC_TIMEOUT
282
+
283
+ case "htlc_timeout_swept" :
284
+ filterState = looprpc .DepositState_HTLC_TIMEOUT_SWEPT
285
+
235
286
case "wait_for_expiry_sweep" :
236
287
filterState = looprpc .DepositState_WAIT_FOR_EXPIRY_SWEEP
237
288
238
289
case "expired" :
239
290
filterState = looprpc .DepositState_EXPIRED
240
291
241
- case "failed" :
242
- filterState = looprpc .DepositState_FAILED_STATE
243
-
244
292
default :
245
293
filterState = looprpc .DepositState_UNKNOWN_STATE
246
294
}
@@ -297,3 +345,173 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
297
345
OutputIndex : uint32 (outputIndex ),
298
346
}, nil
299
347
}
348
+
349
+ func staticAddressLoopIn (ctx * cli.Context ) error {
350
+ if ctx .NumFlags () == 0 && ctx .NArg () == 0 {
351
+ return cli .ShowAppHelp (ctx )
352
+ }
353
+
354
+ client , cleanup , err := getClient (ctx )
355
+ if err != nil {
356
+ return err
357
+ }
358
+ defer cleanup ()
359
+
360
+ var (
361
+ ctxb = context .Background ()
362
+ isAllSelected = ctx .IsSet ("all" )
363
+ isUtxoSelected = ctx .IsSet ("utxo" )
364
+ label = ctx .String ("static-loop-in" )
365
+ hints []* swapserverrpc.RouteHint
366
+ lastHop []byte
367
+ paymentTimeoutSeconds = uint32 (loopin .DefaultPaymentTimeoutSeconds )
368
+ )
369
+
370
+ // Validate our label early so that we can fail before getting a quote.
371
+ if err := labels .Validate (label ); err != nil {
372
+ return err
373
+ }
374
+
375
+ // Private and route hints are mutually exclusive as setting private
376
+ // means we retrieve our own route hints from the connected node.
377
+ hints , err = validateRouteHints (ctx )
378
+ if err != nil {
379
+ return err
380
+ }
381
+
382
+ if ctx .IsSet (lastHopFlag .Name ) {
383
+ lastHopVertex , err := route .NewVertexFromStr (
384
+ ctx .String (lastHopFlag .Name ),
385
+ )
386
+ if err != nil {
387
+ return err
388
+ }
389
+
390
+ lastHop = lastHopVertex [:]
391
+ }
392
+
393
+ // Get the amount we need to quote for.
394
+ summaryResp , err := client .GetStaticAddressSummary (
395
+ ctxb , & looprpc.StaticAddressSummaryRequest {
396
+ StateFilter : looprpc .DepositState_DEPOSITED ,
397
+ },
398
+ )
399
+ if err != nil {
400
+ return err
401
+ }
402
+
403
+ var depositOutpoints []string
404
+ switch {
405
+ case isAllSelected == isUtxoSelected :
406
+ return errors .New ("must select either all or some utxos" )
407
+
408
+ case isAllSelected :
409
+ depositOutpoints = depositsToOutpoints (
410
+ summaryResp .FilteredDeposits ,
411
+ )
412
+
413
+ case isUtxoSelected :
414
+ depositOutpoints = ctx .StringSlice ("utxo" )
415
+
416
+ default :
417
+ return fmt .Errorf ("unknown quote request" )
418
+ }
419
+
420
+ if containsDuplicates (depositOutpoints ) {
421
+ return errors .New ("duplicate outpoints detected" )
422
+ }
423
+
424
+ quoteReq := & looprpc.QuoteRequest {
425
+ LoopInRouteHints : hints ,
426
+ LoopInLastHop : lastHop ,
427
+ Private : ctx .Bool (privateFlag .Name ),
428
+ DepositOutpoints : depositOutpoints ,
429
+ }
430
+ quote , err := client .GetLoopInQuote (ctxb , quoteReq )
431
+ if err != nil {
432
+ return err
433
+ }
434
+
435
+ limits := getInLimits (quote )
436
+
437
+ // populate the quote request with the sum of selected deposits and
438
+ // prompt the user for acceptance.
439
+ quoteReq .Amt , err = sumDeposits (
440
+ depositOutpoints , summaryResp .FilteredDeposits ,
441
+ )
442
+ if err != nil {
443
+ return err
444
+ }
445
+
446
+ if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
447
+ err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
448
+ if err != nil {
449
+ return err
450
+ }
451
+ }
452
+
453
+ if ctx .IsSet ("payment_timeout" ) {
454
+ paymentTimeoutSeconds = uint32 (ctx .Duration ("payment_timeout" ).Seconds ())
455
+ }
456
+
457
+ req := & looprpc.StaticAddressLoopInRequest {
458
+ Outpoints : depositOutpoints ,
459
+ MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
460
+ LastHop : lastHop ,
461
+ Label : ctx .String (labelFlag .Name ),
462
+ Initiator : defaultInitiator ,
463
+ RouteHints : hints ,
464
+ Private : ctx .Bool ("private" ),
465
+ PaymentTimeoutSeconds : paymentTimeoutSeconds ,
466
+ }
467
+
468
+ resp , err := client .StaticAddressLoopIn (ctxb , req )
469
+ if err != nil {
470
+ return err
471
+ }
472
+
473
+ printRespJSON (resp )
474
+
475
+ return nil
476
+ }
477
+
478
+ func containsDuplicates (outpoints []string ) bool {
479
+ found := make (map [string ]struct {})
480
+ for _ , outpoint := range outpoints {
481
+ if _ , ok := found [outpoint ]; ok {
482
+ return true
483
+ }
484
+ found [outpoint ] = struct {}{}
485
+ }
486
+
487
+ return false
488
+ }
489
+
490
+ func sumDeposits (outpoints []string , deposits []* looprpc.Deposit ) (int64 ,
491
+ error ) {
492
+
493
+ var sum int64
494
+ depositMap := make (map [string ]* looprpc.Deposit )
495
+ for _ , deposit := range deposits {
496
+ depositMap [deposit .Outpoint ] = deposit
497
+ }
498
+
499
+ for _ , outpoint := range outpoints {
500
+ if _ , ok := depositMap [outpoint ]; ! ok {
501
+ return 0 , fmt .Errorf ("deposit %v not found" , outpoint )
502
+ }
503
+
504
+ sum += depositMap [outpoint ].Value
505
+ }
506
+
507
+ return sum , nil
508
+ }
509
+
510
+ func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
511
+ outpoints := make ([]string , 0 , len (deposits ))
512
+ for _ , deposit := range deposits {
513
+ outpoints = append (outpoints , deposit .Outpoint )
514
+ }
515
+
516
+ return outpoints
517
+ }
0 commit comments