@@ -9,21 +9,47 @@ 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
1619var staticAddressCommands = cli.Command {
1720 Name : "static" ,
1821 ShortName : "s" ,
19- Usage : "manage static loop-in addresses" ,
20- Category : "StaticAddress" ,
22+ Usage : "perform on-chain to off-chain swaps using static addresses." ,
2123 Subcommands : []cli.Command {
2224 newStaticAddressCommand ,
2325 listUnspentCommand ,
2426 withdrawalCommand ,
2527 summaryCommand ,
2628 },
29+ Description : `
30+ Requests a loop-in swap based on static address deposits. After the
31+ creation of a static address funds can be send to it. Once the funds are
32+ confirmed on-chain they can be swapped instantaneously. If deposited
33+ funds are not needed they can we withdrawn back to the local lnd wallet.
34+ ` ,
35+ Flags : []cli.Flag {
36+ cli.StringSliceFlag {
37+ Name : "utxo" ,
38+ Usage : "specify the utxos of deposits as " +
39+ "outpoints(tx:idx) that should be looped in." ,
40+ },
41+ cli.BoolFlag {
42+ Name : "all" ,
43+ Usage : "loop in all static address deposits." ,
44+ },
45+ lastHopFlag ,
46+ labelFlag ,
47+ routeHintsFlag ,
48+ privateFlag ,
49+ forceFlag ,
50+ verboseFlag ,
51+ },
52+ Action : staticAddressLoopIn ,
2753}
2854
2955var newStaticAddressCommand = cli.Command {
@@ -194,10 +220,14 @@ var summaryCommand = cli.Command{
194220 cli.StringFlag {
195221 Name : "filter" ,
196222 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]." ,
223+ "the specified state. Leaving out the filter " +
224+ "returns all deposits.\n The state can be one " +
225+ "of the following: \n " +
226+ "deposited\n withdrawing\n withdrawn\n " +
227+ "looping_in\n looped_in\n " +
228+ "publish_expired_deposit\n " +
229+ "sweep_htlc_timeout\n htlc_timeout_swept\n " +
230+ "wait_for_expiry_sweep\n expired\n failed\n ." ,
201231 },
202232 },
203233 Action : summary ,
@@ -229,9 +259,21 @@ func summary(ctx *cli.Context) error {
229259 case "withdrawn" :
230260 filterState = looprpc .DepositState_WITHDRAWN
231261
262+ case "looping_in" :
263+ filterState = looprpc .DepositState_LOOPING_IN
264+
265+ case "looped_in" :
266+ filterState = looprpc .DepositState_LOOPED_IN
267+
232268 case "publish_expired_deposit" :
233269 filterState = looprpc .DepositState_PUBLISH_EXPIRED
234270
271+ case "sweep_htlc_timeout" :
272+ filterState = looprpc .DepositState_SWEEP_HTLC_TIMEOUT
273+
274+ case "htlc_timeout_swept" :
275+ filterState = looprpc .DepositState_HTLC_TIMEOUT_SWEPT
276+
235277 case "wait_for_expiry_sweep" :
236278 filterState = looprpc .DepositState_WAIT_FOR_EXPIRY_SWEEP
237279
@@ -297,3 +339,167 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
297339 OutputIndex : uint32 (outputIndex ),
298340 }, nil
299341}
342+
343+ func staticAddressLoopIn (ctx * cli.Context ) error {
344+ if ctx .NumFlags () == 0 && ctx .NArg () == 0 {
345+ return cli .ShowAppHelp (ctx )
346+ }
347+
348+ client , cleanup , err := getClient (ctx )
349+ if err != nil {
350+ return err
351+ }
352+ defer cleanup ()
353+
354+ var (
355+ ctxb = context .Background ()
356+ isAllSelected = ctx .IsSet ("all" )
357+ isUtxoSelected = ctx .IsSet ("utxo" )
358+ label = ctx .String ("static-loop-in" )
359+ hints []* swapserverrpc.RouteHint
360+ lastHop []byte
361+ )
362+
363+ // Validate our label early so that we can fail before getting a quote.
364+ if err := labels .Validate (label ); err != nil {
365+ return err
366+ }
367+
368+ // Private and route hints are mutually exclusive as setting private
369+ // means we retrieve our own route hints from the connected node.
370+ hints , err = validateRouteHints (ctx )
371+ if err != nil {
372+ return err
373+ }
374+
375+ if ctx .IsSet (lastHopFlag .Name ) {
376+ lastHopVertex , err := route .NewVertexFromStr (
377+ ctx .String (lastHopFlag .Name ),
378+ )
379+ if err != nil {
380+ return err
381+ }
382+
383+ lastHop = lastHopVertex [:]
384+ }
385+
386+ // Get the amount we need to quote for.
387+ summaryResp , err := client .GetStaticAddressSummary (
388+ ctxb , & looprpc.StaticAddressSummaryRequest {
389+ StateFilter : looprpc .DepositState_DEPOSITED ,
390+ },
391+ )
392+ if err != nil {
393+ return err
394+ }
395+
396+ var depositOutpoints []string
397+ switch {
398+ case isAllSelected == isUtxoSelected :
399+ return errors .New ("must select either all or some utxos" )
400+
401+ case isAllSelected :
402+ depositOutpoints = depositsToOutpoints (
403+ summaryResp .FilteredDeposits ,
404+ )
405+
406+ case isUtxoSelected :
407+ depositOutpoints = ctx .StringSlice ("utxo" )
408+
409+ default :
410+ return fmt .Errorf ("unknown quote request" )
411+ }
412+
413+ if containsDuplicates (depositOutpoints ) {
414+ return errors .New ("duplicate outpoints detected" )
415+ }
416+
417+ quoteReq := & looprpc.QuoteRequest {
418+ LoopInRouteHints : hints ,
419+ LoopInLastHop : lastHop ,
420+ Private : ctx .Bool (privateFlag .Name ),
421+ DepositOutpoints : depositOutpoints ,
422+ }
423+ quote , err := client .GetLoopInQuote (ctxb , quoteReq )
424+ if err != nil {
425+ return err
426+ }
427+
428+ limits := getInLimits (quote )
429+
430+ // populate the quote request with the sum of selected deposits and
431+ // prompt the user for acceptance.
432+ quoteReq .Amt , err = sumDeposits (
433+ depositOutpoints , summaryResp .FilteredDeposits ,
434+ )
435+ if err != nil {
436+ return err
437+ }
438+
439+ if ! (ctx .Bool ("force" ) || ctx .Bool ("f" )) {
440+ err = displayInDetails (quoteReq , quote , ctx .Bool ("verbose" ))
441+ if err != nil {
442+ return err
443+ }
444+ }
445+
446+ req := & looprpc.StaticAddressLoopInRequest {
447+ Outpoints : depositOutpoints ,
448+ MaxSwapFeeSatoshis : int64 (limits .maxSwapFee ),
449+ LastHop : lastHop ,
450+ Label : ctx .String (labelFlag .Name ),
451+ Initiator : defaultInitiator ,
452+ RouteHints : hints ,
453+ Private : ctx .Bool ("private" ),
454+ }
455+
456+ resp , err := client .StaticAddressLoopIn (ctxb , req )
457+ if err != nil {
458+ return err
459+ }
460+
461+ fmt .Printf ("Static loop-in response from the server: %v\n " , resp )
462+
463+ return nil
464+ }
465+
466+ func containsDuplicates (outpoints []string ) bool {
467+ found := make (map [string ]struct {})
468+ for _ , outpoint := range outpoints {
469+ if _ , ok := found [outpoint ]; ok {
470+ return true
471+ }
472+ found [outpoint ] = struct {}{}
473+ }
474+
475+ return false
476+ }
477+
478+ func sumDeposits (outpoints []string , deposits []* looprpc.Deposit ) (int64 ,
479+ error ) {
480+
481+ var sum int64
482+ depositMap := make (map [string ]* looprpc.Deposit )
483+ for _ , deposit := range deposits {
484+ depositMap [deposit .Outpoint ] = deposit
485+ }
486+
487+ for _ , outpoint := range outpoints {
488+ if _ , ok := depositMap [outpoint ]; ! ok {
489+ return 0 , fmt .Errorf ("deposit %v not found" , outpoint )
490+ }
491+
492+ sum += depositMap [outpoint ].Value
493+ }
494+
495+ return sum , nil
496+ }
497+
498+ func depositsToOutpoints (deposits []* looprpc.Deposit ) []string {
499+ outpoints := make ([]string , 0 , len (deposits ))
500+ for _ , deposit := range deposits {
501+ outpoints = append (outpoints , deposit .Outpoint )
502+ }
503+
504+ return outpoints
505+ }
0 commit comments