diff --git a/cmd/loop/staticaddr.go b/cmd/loop/staticaddr.go index bf1f7fca..bc2a62da 100644 --- a/cmd/loop/staticaddr.go +++ b/cmd/loop/staticaddr.go @@ -2,8 +2,13 @@ package main import ( "context" + "encoding/hex" + "errors" "fmt" + "strconv" + "strings" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightninglabs/loop/looprpc" "github.com/urfave/cli" ) @@ -16,6 +21,7 @@ var staticAddressCommands = cli.Command{ Subcommands: []cli.Command{ newStaticAddressCommand, listUnspentCommand, + withdrawalCommand, }, } @@ -105,3 +111,111 @@ func listUnspent(ctx *cli.Context) error { return nil } + +var withdrawalCommand = cli.Command{ + Name: "withdraw", + ShortName: "w", + Usage: "Withdraw from static address deposits.", + Description: ` + Withdraws from all or selected static address deposits by sweeping them + back to our lnd wallet. + `, + Flags: []cli.Flag{ + cli.StringSliceFlag{ + Name: "utxo", + Usage: "specify utxos as outpoints(tx:idx) which will" + + "be withdrawn.", + }, + cli.BoolFlag{ + Name: "all", + Usage: "withdraws all static address deposits.", + }, + }, + Action: withdraw, +} + +func withdraw(ctx *cli.Context) error { + if ctx.NArg() > 0 { + return cli.ShowCommandHelp(ctx, "withdraw") + } + + client, cleanup, err := getClient(ctx) + if err != nil { + return err + } + defer cleanup() + + var ( + isAllSelected = ctx.IsSet("all") + isUtxoSelected = ctx.IsSet("utxo") + outpoints []*looprpc.OutPoint + ctxb = context.Background() + ) + + switch { + case isAllSelected == isUtxoSelected: + return errors.New("must select either all or some utxos") + + case isAllSelected: + case isUtxoSelected: + utxos := ctx.StringSlice("utxo") + outpoints, err = utxosToOutpoints(utxos) + if err != nil { + return err + } + + default: + return fmt.Errorf("unknown withdrawal request") + } + + resp, err := client.WithdrawDeposits(ctxb, &looprpc.WithdrawDepositsRequest{ + Outpoints: outpoints, + All: isAllSelected, + }) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func utxosToOutpoints(utxos []string) ([]*looprpc.OutPoint, error) { + outpoints := make([]*looprpc.OutPoint, 0, len(utxos)) + if len(utxos) == 0 { + return nil, fmt.Errorf("no utxos specified") + } + for _, utxo := range utxos { + outpoint, err := NewProtoOutPoint(utxo) + if err != nil { + return nil, err + } + outpoints = append(outpoints, outpoint) + } + + return outpoints, nil +} + +// NewProtoOutPoint parses an OutPoint into its corresponding lnrpc.OutPoint +// type. +func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) { + parts := strings.Split(op, ":") + if len(parts) != 2 { + return nil, errors.New("outpoint should be of the form " + + "txid:index") + } + txid := parts[0] + if hex.DecodedLen(len(txid)) != chainhash.HashSize { + return nil, fmt.Errorf("invalid hex-encoded txid %v", txid) + } + outputIndex, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, fmt.Errorf("invalid output index: %v", err) + } + + return &looprpc.OutPoint{ + TxidStr: txid, + OutputIndex: uint32(outputIndex), + }, nil +}