@@ -9,21 +9,55 @@ 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/staticaddr/loopin"
15+ "github.com/lightninglabs/loop/swapserverrpc"
16+ "github.com/lightningnetwork/lnd/routing/route"
1317 "github.com/urfave/cli"
1418)
1519
1620var staticAddressCommands = cli.Command {
1721 Name : "static" ,
1822 ShortName : "s" ,
19- Usage : "manage static loop-in addresses" ,
20- Category : "StaticAddress" ,
23+ Usage : "perform on-chain to off-chain swaps using static addresses." ,
2124 Subcommands : []cli.Command {
2225 newStaticAddressCommand ,
2326 listUnspentCommand ,
2427 withdrawalCommand ,
2528 summaryCommand ,
2629 },
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 ,
2761}
2862
2963var newStaticAddressCommand = cli.Command {
@@ -194,10 +228,14 @@ var summaryCommand = cli.Command{
194228 cli.StringFlag {
195229 Name : "filter" ,
196230 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]." ,
231+ "the specified state. Leaving out the filter " +
232+ "returns all deposits.\n The state can be one " +
233+ "of the following: \n " +
234+ "deposited\n withdrawing\n withdrawn\n " +
235+ "looping_in\n looped_in\n " +
236+ "publish_expired_deposit\n " +
237+ "sweep_htlc_timeout\n htlc_timeout_swept\n " +
238+ "wait_for_expiry_sweep\n expired\n failed\n ." ,
201239 },
202240 },
203241 Action : summary ,
@@ -229,9 +267,21 @@ func summary(ctx *cli.Context) error {
229267 case "withdrawn" :
230268 filterState = looprpc .DepositState_WITHDRAWN
231269
270+ case "looping_in" :
271+ filterState = looprpc .DepositState_LOOPING_IN
272+
273+ case "looped_in" :
274+ filterState = looprpc .DepositState_LOOPED_IN
275+
232276 case "publish_expired_deposit" :
233277 filterState = looprpc .DepositState_PUBLISH_EXPIRED
234278
279+ case "sweep_htlc_timeout" :
280+ filterState = looprpc .DepositState_SWEEP_HTLC_TIMEOUT
281+
282+ case "htlc_timeout_swept" :
283+ filterState = looprpc .DepositState_HTLC_TIMEOUT_SWEPT
284+
235285 case "wait_for_expiry_sweep" :
236286 filterState = looprpc .DepositState_WAIT_FOR_EXPIRY_SWEEP
237287
@@ -297,3 +347,173 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
297347 OutputIndex : uint32 (outputIndex ),
298348 }, nil
299349}
350+
351+ func staticAddressLoopIn (ctx * cli.Context ) error {
352+ if ctx .NumFlags () == 0 && ctx .NArg () == 0 {
353+ return cli .ShowAppHelp (ctx )
354+ }
355+
356+ client , cleanup , err := getClient (ctx )
357+ if err != nil {
358+ return err
359+ }
360+ defer cleanup ()
361+
362+ var (
363+ ctxb = context .Background ()
364+ isAllSelected = ctx .IsSet ("all" )
365+ isUtxoSelected = ctx .IsSet ("utxo" )
366+ label = ctx .String ("static-loop-in" )
367+ hints []* swapserverrpc.RouteHint
368+ lastHop []byte
369+ paymentTimeoutSeconds = uint32 (loopin .DefaultPaymentTimeoutSeconds )
370+ )
371+
372+ // Validate our label early so that we can fail before getting a quote.
373+ if err := labels .Validate (label ); err != nil {
374+ return err
375+ }
376+
377+ // Private and route hints are mutually exclusive as setting private
378+ // means we retrieve our own route hints from the connected node.
379+ hints , err = validateRouteHints (ctx )
380+ if err != nil {
381+ return err
382+ }
383+
384+ if ctx .IsSet (lastHopFlag .Name ) {
385+ lastHopVertex , err := route .NewVertexFromStr (
386+ ctx .String (lastHopFlag .Name ),
387+ )
388+ if err != nil {
389+ return err
390+ }
391+
392+ lastHop = lastHopVertex [:]
393+ }
394+
395+ // Get the amount we need to quote for.
396+ summaryResp , err := client .GetStaticAddressSummary (
397+ ctxb , & looprpc.StaticAddressSummaryRequest {
398+ StateFilter : looprpc .DepositState_DEPOSITED ,
399+ },
400+ )
401+ if err != nil {
402+ return err
403+ }
404+
405+ var depositOutpoints []string
406+ switch {
407+ case isAllSelected == isUtxoSelected :
408+ return errors .New ("must select either all or some utxos" )
409+
410+ case isAllSelected :
411+ depositOutpoints = depositsToOutpoints (
412+ summaryResp .FilteredDeposits ,
413+ )
414+
415+ case isUtxoSelected :
416+ depositOutpoints = ctx .StringSlice ("utxo" )
417+
418+ default :
419+ return fmt .Errorf ("unknown quote request" )
420+ }
421+
422+ if containsDuplicates (depositOutpoints ) {
423+ return errors .New ("duplicate outpoints detected" )
424+ }
425+
426+ quoteReq := & looprpc.QuoteRequest {
427+ LoopInRouteHints : hints ,
428+ LoopInLastHop : lastHop ,
429+ Private : ctx .Bool (privateFlag .Name ),
430+ DepositOutpoints : depositOutpoints ,
431+ }
432+ quote , err := client .GetLoopInQuote (ctxb , quoteReq )
433+ if err != nil {
434+ return err
435+ }
436+
437+ limits := getInLimits (quote )
438+
439+ // populate the quote request with the sum of selected deposits and
440+ // prompt the user for acceptance.
441+ quoteReq .Amt , err = sumDeposits (
442+ depositOutpoints , summaryResp .FilteredDeposits ,
443+ )
444+ if err != nil {
445+ return err
446+ }
447+
448+ if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
449+ err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
450+ if err != nil {
451+ return err
452+ }
453+ }
454+
455+ if ctx .IsSet ("payment_timeout" ) {
456+ paymentTimeoutSeconds = uint32 (ctx .Duration ("payment_timeout" ).Seconds ())
457+ }
458+
459+ req := & looprpc.StaticAddressLoopInRequest {
460+ Outpoints : depositOutpoints ,
461+ MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
462+ LastHop : lastHop ,
463+ Label : ctx .String (labelFlag .Name ),
464+ Initiator : defaultInitiator ,
465+ RouteHints : hints ,
466+ Private : ctx .Bool ("private" ),
467+ PaymentTimeoutSeconds : paymentTimeoutSeconds ,
468+ }
469+
470+ resp , err := client .StaticAddressLoopIn (ctxb , req )
471+ if err != nil {
472+ return err
473+ }
474+
475+ fmt .Printf ("Static loop-in response from the server: %v\n " , resp )
476+
477+ return nil
478+ }
479+
480+ func containsDuplicates (outpoints []string ) bool {
481+ found := make (map [string ]struct {})
482+ for _ , outpoint := range outpoints {
483+ if _ , ok := found [outpoint ]; ok {
484+ return true
485+ }
486+ found [outpoint ] = struct {}{}
487+ }
488+
489+ return false
490+ }
491+
492+ func sumDeposits (outpoints []string , deposits []* looprpc.Deposit ) (int64 ,
493+ error ) {
494+
495+ var sum int64
496+ depositMap := make (map [string ]* looprpc.Deposit )
497+ for _ , deposit := range deposits {
498+ depositMap [deposit .Outpoint ] = deposit
499+ }
500+
501+ for _ , outpoint := range outpoints {
502+ if _ , ok := depositMap [outpoint ]; ! ok {
503+ return 0 , fmt .Errorf ("deposit %v not found" , outpoint )
504+ }
505+
506+ sum += depositMap [outpoint ].Value
507+ }
508+
509+ return sum , nil
510+ }
511+
512+ func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
513+ outpoints := make ([]string , 0 , len (deposits ))
514+ for _ , deposit := range deposits {
515+ outpoints = append (outpoints , deposit .Outpoint )
516+ }
517+
518+ return outpoints
519+ }
0 commit comments