@@ -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 {
@@ -169,10 +203,11 @@ func withdraw(ctx *cli.Context) error {
169203		return  fmt .Errorf ("unknown withdrawal request" )
170204	}
171205
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+ 		})
176211	if  err  !=  nil  {
177212		return  err 
178213	}
@@ -194,10 +229,14 @@ var summaryCommand = cli.Command{
194229		cli.StringFlag {
195230			Name : "filter" ,
196231			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 ." ,
201240		},
202241	},
203242	Action : summary ,
@@ -229,9 +268,21 @@ func summary(ctx *cli.Context) error {
229268	case  "withdrawn" :
230269		filterState  =  looprpc .DepositState_WITHDRAWN 
231270
271+ 	case  "looping_in" :
272+ 		filterState  =  looprpc .DepositState_LOOPING_IN 
273+ 
274+ 	case  "looped_in" :
275+ 		filterState  =  looprpc .DepositState_LOOPED_IN 
276+ 
232277	case  "publish_expired_deposit" :
233278		filterState  =  looprpc .DepositState_PUBLISH_EXPIRED 
234279
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+ 
235286	case  "wait_for_expiry_sweep" :
236287		filterState  =  looprpc .DepositState_WAIT_FOR_EXPIRY_SWEEP 
237288
@@ -294,3 +345,173 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
294345		OutputIndex : uint32 (outputIndex ),
295346	}, nil 
296347}
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