diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index 9e7a992a..2fe3c6e2 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -121,6 +121,9 @@ var loopOutCommand = &cli.Command{ verboseFlag, channelFlag, }, + Commands: []*cli.Command{ + sweepHtlcCommand, + }, Action: loopOut, } diff --git a/cmd/loop/sweephtlc.go b/cmd/loop/sweephtlc.go new file mode 100644 index 00000000..6f496bd1 --- /dev/null +++ b/cmd/loop/sweephtlc.go @@ -0,0 +1,119 @@ +package main + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/looprpc" + "github.com/urfave/cli/v3" +) + +// sweepHtlcCommand exposes HTLC success-path sweeping over loop CLI. +var sweepHtlcCommand = &cli.Command{ + Name: "sweephtlc", + Usage: "sweep an HTLC output using the preimage success path", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "outpoint", + Usage: "htlc outpoint to sweep (format: txid:vout)", + Required: true, + }, + &cli.StringFlag{ + Name: "htlcaddr", + Usage: "htlc address corresponding to the outpoint", + Required: true, + }, + &cli.UintFlag{ + Name: "feerate", + Usage: "fee rate to use in sat/vbyte", + Required: true, + }, + &cli.StringFlag{ + Name: "destaddr", + Usage: "optional destination address; defaults to a " + + "new wallet address", + }, + &cli.StringFlag{ + Name: "preimage", + Usage: "optional preimage hex to override stored " + + "swap preimage", + }, + &cli.BoolFlag{ + Name: "publish", + Usage: "publish the sweep transaction immediately", + Value: false, + }, + }, + Hidden: true, + Action: sweepHtlc, +} + +// sweepHtlc executes the SweepHtlc RPC and prints the sweep transaction hex. +func sweepHtlc(ctx context.Context, cmd *cli.Command) error { + // Loopd connecting client. + client, cleanup, err := getClient(cmd) + if err != nil { + return err + } + defer cleanup() + + // Find the preimage if the user passed it. + var preimage []byte + if cmd.IsSet("preimage") { + preimage, err = hex.DecodeString(cmd.String("preimage")) + if err != nil { + return fmt.Errorf("invalid preimage: %w", err) + } + } + + // Call SweepHtlc on loopd trying to sweep the HTLC. + resp, err := client.SweepHtlc(ctx, &looprpc.SweepHtlcRequest{ + Outpoint: cmd.String("outpoint"), + DestAddress: cmd.String("destaddr"), + HtlcAddress: cmd.String("htlcaddr"), + SatPerVbyte: uint32(cmd.Uint("feerate")), + Preimage: preimage, + Publish: cmd.Bool("publish"), + }) + if err != nil { + return err + } + + // Always display the raw sweep transaction. + fmt.Printf("sweep_tx_hex: %x\n", resp.SweepTx) + + // Report publish status in a user-friendly way based on response. + switch { + case resp.GetNotRequested() != nil: + fmt.Println("publish: not requested (pass --publish to " + + "broadcast)") + + case resp.GetPublished() != nil: + fmt.Println("publish: success") + + case resp.GetFailed() != nil: + errMsg := resp.GetFailed().GetError() + fmt.Printf("publish: failed: %s\n", errMsg) + + return fmt.Errorf("publish failed: %s", errMsg) + + default: + fmt.Println("publish: unknown status") + } + + // Print txid if the transaction is valid. + var tx wire.MsgTx + if err := tx.Deserialize(bytes.NewReader(resp.SweepTx)); err == nil { + fmt.Printf("sweep_txid: %s\n", tx.TxHash().String()) + } else { + fmt.Printf("sweep_txid: could not decode tx: %v\n", err) + } + + // Print the fee-rate. + fmt.Printf("fee_sats: %d\n", resp.FeeSats) + + return nil +} diff --git a/docs/loop.md b/docs/loop.md index ebe3a5c9..5d15fb17 100644 --- a/docs/loop.md +++ b/docs/loop.md @@ -53,6 +53,28 @@ The following flags are supported: | `--channel="…"` | the comma-separated list of short channel IDs of the channels to loop out | string | | `--help` (`-h`) | show help | bool | `false` | +### `out sweephtlc` subcommand + +sweep an HTLC output using the preimage success path. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] out sweephtlc [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|------------------|----------------------------------------------------------------|--------|:-------------:| +| `--outpoint="…"` | htlc outpoint to sweep (format: txid:vout) | string | +| `--htlcaddr="…"` | htlc address corresponding to the outpoint | string | +| `--feerate="…"` | fee rate to use in sat/vbyte | uint | `0` | +| `--destaddr="…"` | optional destination address; defaults to a new wallet address | string | +| `--preimage="…"` | optional preimage hex to override stored swap preimage | string | +| `--publish` | publish the sweep transaction immediately | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + ### `in` command perform an on-chain to off-chain swap (loop in).