cmd/loop: expose both taproot channel types

LND v0.21 added the production TAPROOT commitment type while
SIMPLE_TAPROOT remains available as the legacy enum.

The static open-channel CLI previously used "taproot" for
SIMPLE_TAPROOT. Keep both choices available by renaming that legacy
spelling to "simple-taproot" and mapping "taproot" to TAPROOT. This
makes the CLI spelling match the channel type it requests while still
leaving an explicit path for users that need SIMPLE_TAPROOT.
This commit is contained in:
Boris Nagaev 2026-06-20 18:33:48 -05:00
parent 5d8a5019cf
commit 682d45405c
No known key found for this signature in database
8 changed files with 275 additions and 10 deletions

View file

@ -13,13 +13,11 @@ import (
)
const (
defaultUtxoMinConf = 1
)
var (
defaultUtxoMinConf = 1
channelTypeTweakless = "tweakless"
channelTypeAnchors = "anchors"
channelTypeSimpleTaproot = "taproot"
channelTypeSimpleTaproot = "simple-taproot"
channelTypeTaproot = "taproot"
)
var openChannelCommand = &cli.Command{
@ -137,9 +135,9 @@ var openChannelCommand = &cli.Command{
&cli.StringFlag{
Name: "channel_type",
Usage: fmt.Sprintf("(optional) the type of channel to "+
"propose to the remote peer (%q, %q, %q)",
"propose to the remote peer (%q, %q, %q, %q)",
channelTypeTweakless, channelTypeAnchors,
channelTypeSimpleTaproot),
channelTypeSimpleTaproot, channelTypeTaproot),
},
&cli.BoolFlag{
Name: "zero_conf",
@ -322,6 +320,7 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
switch channelType {
case "":
break
case channelTypeTweakless:
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
@ -330,6 +329,10 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
case channelTypeSimpleTaproot:
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelTypeTaproot:
req.CommitmentType = lnrpc.CommitmentType_TAPROOT
default:
return fmt.Errorf("unsupported channel type %v", channelType)
}

View file

@ -67,7 +67,7 @@
" --max_local_csv uint (optional) the maximum number of blocks that we will allow the remote peer to require we wait before accessing our funds in the case of a unilateral close. (default: 0)\n",
" --close_address string (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will *not* be able to cooperatively close to a different address.\n",
" --remote_max_value_in_flight_msat uint (optional) the maximum value in msat that can be pending within the channel at any given time (default: 0)\n",
" --channel_type string (optional) the type of channel to propose to the remote peer (\"tweakless\", \"anchors\", \"taproot\")\n",
" --channel_type string (optional) the type of channel to propose to the remote peer (\"tweakless\", \"anchors\", \"simple-taproot\", \"taproot\")\n",
" --zero_conf (optional) whether a zero-conf channel open should be attempted. (default: false)\n",
" --scid_alias (optional) whether a scid-alias channel type should be negotiated. (default: false)\n",
" --remote_reserve_sats uint (optional) the minimum number of satoshis we require the remote node to keep as a direct payment. If not specified, a default of 1% of the channel capacity will be used. (default: 0)\n",

View file

@ -0,0 +1,104 @@
{
"metadata": {
"args": [
"/home/user/bin/loop",
"--rpcserver=localhost:11010",
"--loopdir=/redacted/loop",
"--tlscertpath=/redacted/loop/regtest/tls.cert",
"--macaroonpath=/redacted/loop/regtest/loop.macaroon",
"static",
"openchannel",
"--node_key",
"03465f68fd39358667678f8353a31e0d99475e3fd2fb4e58daf7dbfabe04c011f4",
"--fundmax",
"--utxo",
"89f6fd2ee96445c6e48278e7eaaca7de8e342904f20609ce72c0d27afb443e2d:1",
"--channel_type",
"taproot",
"--private",
"--network",
"regtest"
],
"env": {},
"version": "0.33.2-beta commit=v0.33.2-beta-bump-lnd-21-a-7-g21019e684ed06e2267382f473dfeaafb81a33310 commit_hash=21019e684ed06e2267382f473dfeaafb81a33310",
"duration": 295879956,
"clock_start_unix": 1782002722
},
"events": [
{
"time_ms": 2,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "request",
"message_type": "looprpc.StaticOpenChannelRequest",
"payload": {
"open_channel_request": {
"sat_per_vbyte": "0",
"node_pubkey": "A0ZfaP05NYZnZ4+DU6MeDZlHXj/S+05Y2vfb+r4EwBH0",
"node_pubkey_string": "",
"local_funding_amount": "0",
"push_sat": "0",
"target_conf": 0,
"sat_per_byte": "0",
"private": true,
"min_htlc_msat": "0",
"remote_csv_delay": 0,
"min_confs": 1,
"spend_unconfirmed": false,
"close_address": "",
"funding_shim": null,
"remote_max_value_in_flight_msat": "0",
"remote_max_htlcs": 0,
"max_local_csv": 0,
"commitment_type": "TAPROOT",
"zero_conf": false,
"scid_alias": false,
"base_fee": "0",
"fee_rate": "0",
"use_base_fee": false,
"use_fee_rate": false,
"remote_chan_reserve_sat": "0",
"fund_max": true,
"memo": "",
"outpoints": [
{
"txid_bytes": "",
"txid_str": "89f6fd2ee96445c6e48278e7eaaca7de8e342904f20609ce72c0d27afb443e2d",
"output_index": 1
}
]
}
}
}
},
{
"time_ms": 295,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "response",
"message_type": "looprpc.StaticOpenChannelResponse",
"payload": {
"channel_open_outpoint": "302a41b53946494306e415bdf4724e88b2cf2b37ad2f36dc075db00cc6499e9f:0"
}
}
},
{
"time_ms": 295,
"kind": "stdout",
"data": {
"lines": [
"{\n",
" \"channel_open_outpoint\": \"302a41b53946494306e415bdf4724e88b2cf2b37ad2f36dc075db00cc6499e9f:0\"\n",
"}\n"
]
}
},
{
"time_ms": 295,
"kind": "exit",
"data": {}
}
]
}

View file

@ -0,0 +1,106 @@
{
"metadata": {
"args": [
"/home/user/bin/loop",
"--rpcserver=localhost:11010",
"--loopdir=/redacted/loop",
"--tlscertpath=/redacted/loop/regtest/tls.cert",
"--macaroonpath=/redacted/loop/regtest/loop.macaroon",
"static",
"openchannel",
"--node_key",
"026eca9330cc3e589505c4a240ea1f7f551e5765b1aae9040c6667141da657eae6",
"--fundmax",
"--utxo",
"dc942b3252ded50abc30ef4e8614d392ab0f091acab75fdd09e18a07f6ac36e6:0",
"--channel_type",
"taproot",
"--private",
"--network",
"regtest"
],
"env": {},
"version": "0.33.3-beta commit= commit_hash=",
"run_error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7",
"duration": 39639744,
"clock_start_unix": 1782158339
},
"events": [
{
"time_ms": 7,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "request",
"message_type": "looprpc.StaticOpenChannelRequest",
"payload": {
"open_channel_request": {
"sat_per_vbyte": "0",
"node_pubkey": "Am7KkzDMPliVBcSiQOoff1UeV2WxqukEDGZnFB2mV+rm",
"node_pubkey_string": "",
"local_funding_amount": "0",
"push_sat": "0",
"target_conf": 0,
"sat_per_byte": "0",
"private": true,
"min_htlc_msat": "0",
"remote_csv_delay": 0,
"min_confs": 1,
"spend_unconfirmed": false,
"close_address": "",
"funding_shim": null,
"remote_max_value_in_flight_msat": "0",
"remote_max_htlcs": 0,
"max_local_csv": 0,
"commitment_type": "TAPROOT",
"zero_conf": false,
"scid_alias": false,
"base_fee": "0",
"fee_rate": "0",
"use_base_fee": false,
"use_fee_rate": false,
"remote_chan_reserve_sat": "0",
"fund_max": true,
"memo": "",
"outpoints": [
{
"txid_bytes": "",
"txid_str": "dc942b3252ded50abc30ef4e8614d392ab0f091acab75fdd09e18a07f6ac36e6",
"output_index": 0
}
]
}
}
}
},
{
"time_ms": 39,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "error",
"error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7",
"status": {
"code": 2,
"message": "channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7"
}
}
},
{
"time_ms": 39,
"kind": "stderr",
"data": {
"lines": [
"[loop] rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7\n"
]
}
},
{
"time_ms": 39,
"kind": "exit",
"data": {
"run_error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7"
}
}
]
}

View file

@ -589,7 +589,7 @@ Open a channel to an existing peer.
\fB--base_fee_msat\fP="": the base fee in milli-satoshis that will be charged for each forwarded HTLC, regardless of payment size (default: 0)
.PP
\fB--channel_type\fP="": (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "taproot")
\fB--channel_type\fP="": (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "simple-taproot", "taproot")
.PP
\fB--close_address\fP="": (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will \fInot\fP be able to cooperatively close to a different address.

View file

@ -715,7 +715,7 @@ The following flags are supported:
| `--max_local_csv="…"` | (optional) the maximum number of blocks that we will allow the remote peer to require we wait before accessing our funds in the case of a unilateral close | uint | `0` |
| `--close_address="…"` | (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will *not* be able to cooperatively close to a different address | string |
| `--remote_max_value_in_flight_msat="…"` | (optional) the maximum value in msat that can be pending within the channel at any given time | uint | `0` |
| `--channel_type="…"` | (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "taproot") | string |
| `--channel_type="…"` | (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "simple-taproot", "taproot") | string |
| `--zero_conf` | (optional) whether a zero-conf channel open should be attempted | bool | `false` |
| `--scid_alias` | (optional) whether a scid-alias channel type should be negotiated | bool | `false` |
| `--remote_reserve_sats="…"` | (optional) the minimum number of satoshis we require the remote node to keep as a direct payment. If not specified, a default of 1% of the channel capacity will be used | uint | `0` |

View file

@ -368,6 +368,7 @@ func (m *Manager) OpenChannel(ctx context.Context,
if err == nil {
return chanOutpoint, nil
}
err = maybeWrapTaprootUnsupportedError(reqClone, err)
log.Infof("error opening channel: %v", err)
@ -782,6 +783,30 @@ func resolveCommitmentType(commitmentType lnrpc.CommitmentType) (
}
}
// maybeWrapTaprootUnsupportedError turns lnd's generic unknown channel type
// error into a user-actionable message for Loop's production taproot channel
// type.
func maybeWrapTaprootUnsupportedError(req *lnrpc.OpenChannelRequest,
err error) error {
if err == nil || req.CommitmentType != lnrpc.CommitmentType_TAPROOT {
return err
}
errMsg := strings.ToLower(err.Error())
switch {
case strings.Contains(errMsg, "unhandled request channel type"),
strings.Contains(errMsg, "unknown channel type"),
strings.Contains(errMsg, "unsupported channel type"):
return fmt.Errorf("channel_type=taproot is not supported "+
"by the connected lnd; update LND to v0.21.0-beta "+
"or later to use this channel type: %w", err)
}
return err
}
// checkPsbtFlags make sure a request to open a channel doesn't set any
// parameters that are incompatible with the PSBT funding flow.
func checkPsbtFlags(req *lnrpc.OpenChannelRequest) error {

View file

@ -601,6 +601,33 @@ func TestResolveCommitmentType(t *testing.T) {
}
}
// TestMaybeWrapTaprootUnsupportedError verifies that generic old-lnd channel
// type rejections become actionable for Loop users selecting production
// taproot channels.
func TestMaybeWrapTaprootUnsupportedError(t *testing.T) {
t.Parallel()
baseErr := errors.New("got error from server: rpc error: " +
"code = Unknown desc = unhandled request channel type 7")
req := &lnrpc.OpenChannelRequest{
CommitmentType: lnrpc.CommitmentType_TAPROOT,
}
err := maybeWrapTaprootUnsupportedError(req, baseErr)
require.ErrorContains(
t, err, "channel_type=taproot is not supported",
)
require.ErrorContains(
t, err, "update LND to v0.21.0-beta or later",
)
require.ErrorIs(t, err, baseErr)
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
err = maybeWrapTaprootUnsupportedError(req, baseErr)
require.ErrorIs(t, err, baseErr)
require.NotContains(t, err.Error(), "update LND")
}
// ---------------------------------------------------------------------------
// Mock types for PSBT channel open flow tests.
// ---------------------------------------------------------------------------