@@ -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