From 866d2323cf318c378af0ce84afca598146545ae6 Mon Sep 17 00:00:00 2001 From: MPins Date: Tue, 16 Sep 2025 07:19:44 -0700 Subject: [PATCH 1/8] lnrpc: add sat/kw option For EstimateFeeResponse, SendManyRequest, SendCoinsRequest, CloseChannelRequest, BatchOpenChannelRequest, OpenChannelRequest, PendingSweeps, BumpFeeRequest the sat_per_kw field is added. This allows more fine granular control of transaction fees. --- lnrpc/lightning.pb.go | 195 +++++++++++++++++++------ lnrpc/lightning.proto | 66 +++++++-- lnrpc/lightning.swagger.json | 57 +++++++- lnrpc/walletrpc/walletkit.pb.go | 73 +++++++-- lnrpc/walletrpc/walletkit.proto | 32 +++- lnrpc/walletrpc/walletkit.swagger.json | 23 ++- 6 files changed, 352 insertions(+), 94 deletions(-) diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 59d01914159..f4aa22be09b 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -3665,7 +3665,9 @@ type EstimateFeeResponse struct { // The fee rate in satoshi/vbyte. SatPerVbyte uint64 `protobuf:"varint,3,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` // A list of selected inputs for the transaction the estimate is for. - Inputs []*OutPoint `protobuf:"bytes,4,rep,name=inputs,proto3" json:"inputs,omitempty"` + Inputs []*OutPoint `protobuf:"bytes,4,rep,name=inputs,proto3" json:"inputs,omitempty"` + // The fee rate in satoshi/kweight. + SatPerKw uint64 `protobuf:"varint,5,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3729,6 +3731,13 @@ func (x *EstimateFeeResponse) GetInputs() []*OutPoint { return nil } +func (x *EstimateFeeResponse) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + type SendManyRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // The map from addresses to amounts @@ -3738,6 +3747,8 @@ type SendManyRequest struct { TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. SatPerVbyte uint64 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` // Deprecated, use sat_per_vbyte. // A manual fee rate set in sat/vbyte that should be used when crafting the @@ -3745,6 +3756,9 @@ type SendManyRequest struct { // // Deprecated: Marked as deprecated in lightning.proto. SatPerByte int64 `protobuf:"varint,5,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` + // A manual fee rate set in sat/kweight that should be used when crafting the + // transaction. + SatPerKw uint64 `protobuf:"varint,10,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // An optional label for the transaction, limited to 500 characters. Label string `protobuf:"bytes,6,opt,name=label,proto3" json:"label,omitempty"` // The minimum number of confirmations each one of your outputs used for @@ -3802,6 +3816,7 @@ func (x *SendManyRequest) GetTargetConf() int32 { return 0 } +// Deprecated: Marked as deprecated in lightning.proto. func (x *SendManyRequest) GetSatPerVbyte() uint64 { if x != nil { return x.SatPerVbyte @@ -3817,6 +3832,13 @@ func (x *SendManyRequest) GetSatPerByte() int64 { return 0 } +func (x *SendManyRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *SendManyRequest) GetLabel() string { if x != nil { return x.Label @@ -3901,6 +3923,8 @@ type SendCoinsRequest struct { TargetConf int32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. SatPerVbyte uint64 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` // Deprecated, use sat_per_vbyte. // A manual fee rate set in sat/vbyte that should be used when crafting the @@ -3908,6 +3932,9 @@ type SendCoinsRequest struct { // // Deprecated: Marked as deprecated in lightning.proto. SatPerByte int64 `protobuf:"varint,5,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` + // A manual fee rate set in sat/kweight that should be used when crafting the + // transaction. + SatPerKw uint64 `protobuf:"varint,12,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // If set, the amount field should be unset. It indicates lnd will send all // wallet coins or all selected coins to the specified address. SendAll bool `protobuf:"varint,6,opt,name=send_all,json=sendAll,proto3" json:"send_all,omitempty"` @@ -3977,6 +4004,7 @@ func (x *SendCoinsRequest) GetTargetConf() int32 { return 0 } +// Deprecated: Marked as deprecated in lightning.proto. func (x *SendCoinsRequest) GetSatPerVbyte() uint64 { if x != nil { return x.SatPerVbyte @@ -3992,6 +4020,13 @@ func (x *SendCoinsRequest) GetSatPerByte() int64 { return 0 } +func (x *SendCoinsRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *SendCoinsRequest) GetSendAll() bool { if x != nil { return x.SendAll @@ -7140,18 +7175,31 @@ type CloseChannelRequest struct { // // Deprecated: Marked as deprecated in lightning.proto. SatPerByte int64 `protobuf:"varint,4,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. + SatPerVbyte uint64 `protobuf:"varint,6,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` + // A manual fee rate set in sat/kweight that should be used when crafting the + // transaction. + // + // NOTE: This field is only respected if we're the initiator of the channel. + SatPerKw uint64 `protobuf:"varint,9,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // An optional address to send funds to in the case of a cooperative close. // If the channel was opened with an upfront shutdown script and this field // is set, the request to close will fail because the channel must pay out // to the upfront shutdown addresss. DeliveryAddress string `protobuf:"bytes,5,opt,name=delivery_address,json=deliveryAddress,proto3" json:"delivery_address,omitempty"` - // A manual fee rate set in sat/vbyte that should be used when crafting the - // closure transaction. - SatPerVbyte uint64 `protobuf:"varint,6,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` // The maximum fee rate the closer is willing to pay. // // NOTE: This field is only respected if we're the initiator of the channel. + // + // Deprecated: Marked as deprecated in lightning.proto. MaxFeePerVbyte uint64 `protobuf:"varint,7,opt,name=max_fee_per_vbyte,json=maxFeePerVbyte,proto3" json:"max_fee_per_vbyte,omitempty"` + // The maximum fee rate the closer is willing to pay. + // + // NOTE: This field is only respected if we're the initiator of the channel. + MaxFeePerKw uint64 `protobuf:"varint,10,opt,name=max_fee_per_kw,json=maxFeePerKw,proto3" json:"max_fee_per_kw,omitempty"` // If true, then the rpc call will not block while it awaits a closing txid // to be broadcasted to the mempool. To obtain the closing tx one has to // listen to the stream for the particular updates. Moreover if a coop close @@ -7223,6 +7271,21 @@ func (x *CloseChannelRequest) GetSatPerByte() int64 { return 0 } +// Deprecated: Marked as deprecated in lightning.proto. +func (x *CloseChannelRequest) GetSatPerVbyte() uint64 { + if x != nil { + return x.SatPerVbyte + } + return 0 +} + +func (x *CloseChannelRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *CloseChannelRequest) GetDeliveryAddress() string { if x != nil { return x.DeliveryAddress @@ -7230,16 +7293,17 @@ func (x *CloseChannelRequest) GetDeliveryAddress() string { return "" } -func (x *CloseChannelRequest) GetSatPerVbyte() uint64 { +// Deprecated: Marked as deprecated in lightning.proto. +func (x *CloseChannelRequest) GetMaxFeePerVbyte() uint64 { if x != nil { - return x.SatPerVbyte + return x.MaxFeePerVbyte } return 0 } -func (x *CloseChannelRequest) GetMaxFeePerVbyte() uint64 { +func (x *CloseChannelRequest) GetMaxFeePerKw() uint64 { if x != nil { - return x.MaxFeePerVbyte + return x.MaxFeePerKw } return 0 } @@ -7541,7 +7605,12 @@ type BatchOpenChannelRequest struct { TargetConf int32 `protobuf:"varint,2,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` // A manual fee rate set in sat/vByte that should be used when crafting the // funding transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. SatPerVbyte int64 `protobuf:"varint,3,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` + // A manual fee rate set in sat/kweight that should be used when crafting the + // transaction. + SatPerKw uint64 `protobuf:"varint,8,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // The minimum number of confirmations each one of your outputs used for // the funding transaction must satisfy. MinConfs int32 `protobuf:"varint,4,opt,name=min_confs,json=minConfs,proto3" json:"min_confs,omitempty"` @@ -7600,6 +7669,7 @@ func (x *BatchOpenChannelRequest) GetTargetConf() int32 { return 0 } +// Deprecated: Marked as deprecated in lightning.proto. func (x *BatchOpenChannelRequest) GetSatPerVbyte() int64 { if x != nil { return x.SatPerVbyte @@ -7607,6 +7677,13 @@ func (x *BatchOpenChannelRequest) GetSatPerVbyte() int64 { return 0 } +func (x *BatchOpenChannelRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *BatchOpenChannelRequest) GetMinConfs() int32 { if x != nil { return x.MinConfs @@ -7929,7 +8006,18 @@ type OpenChannelRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // A manual fee rate set in sat/vbyte that should be used when crafting the // funding transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. SatPerVbyte uint64 `protobuf:"varint,1,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + // + // Deprecated: Marked as deprecated in lightning.proto. + SatPerByte int64 `protobuf:"varint,7,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` + // A manual fee rate set in sat/kweight that should be used when crafting the + // funding transaction. + SatPerKw uint64 `protobuf:"varint,29,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // The pubkey of the node to open a channel with. When using REST, this field // must be encoded as base64. NodePubkey []byte `protobuf:"bytes,2,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"` @@ -7946,12 +8034,6 @@ type OpenChannelRequest struct { // The target number of blocks that the funding transaction should be // confirmed by. TargetConf int32 `protobuf:"varint,6,opt,name=target_conf,json=targetConf,proto3" json:"target_conf,omitempty"` - // Deprecated, use sat_per_vbyte. - // A manual fee rate set in sat/vbyte that should be used when crafting the - // funding transaction. - // - // Deprecated: Marked as deprecated in lightning.proto. - SatPerByte int64 `protobuf:"varint,7,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` // Whether this channel should be private, not announced to the greater // network. Private bool `protobuf:"varint,8,opt,name=private,proto3" json:"private,omitempty"` @@ -8062,6 +8144,7 @@ func (*OpenChannelRequest) Descriptor() ([]byte, []int) { return file_lightning_proto_rawDescGZIP(), []int{79} } +// Deprecated: Marked as deprecated in lightning.proto. func (x *OpenChannelRequest) GetSatPerVbyte() uint64 { if x != nil { return x.SatPerVbyte @@ -8069,6 +8152,21 @@ func (x *OpenChannelRequest) GetSatPerVbyte() uint64 { return 0 } +// Deprecated: Marked as deprecated in lightning.proto. +func (x *OpenChannelRequest) GetSatPerByte() int64 { + if x != nil { + return x.SatPerByte + } + return 0 +} + +func (x *OpenChannelRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *OpenChannelRequest) GetNodePubkey() []byte { if x != nil { return x.NodePubkey @@ -8105,14 +8203,6 @@ func (x *OpenChannelRequest) GetTargetConf() int32 { return 0 } -// Deprecated: Marked as deprecated in lightning.proto. -func (x *OpenChannelRequest) GetSatPerByte() int64 { - if x != nil { - return x.SatPerByte - } - return 0 -} - func (x *OpenChannelRequest) GetPrivate() bool { if x != nil { return x.Private @@ -19035,19 +19125,24 @@ const file_lightning_proto_rawDesc = "" + "\x06inputs\x18\x06 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\x1a?\n" + "\x11AddrToAmountEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"\xb0\x01\n" + + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"\xce\x01\n" + "\x13EstimateFeeResponse\x12\x17\n" + "\afee_sat\x18\x01 \x01(\x03R\x06feeSat\x123\n" + "\x14feerate_sat_per_byte\x18\x02 \x01(\x03B\x02\x18\x01R\x11feerateSatPerByte\x12\"\n" + "\rsat_per_vbyte\x18\x03 \x01(\x04R\vsatPerVbyte\x12'\n" + - "\x06inputs\x18\x04 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\"\xc1\x03\n" + + "\x06inputs\x18\x04 \x03(\v2\x0f.lnrpc.OutPointR\x06inputs\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\x05 \x01(\x04R\bsatPerKw\"\xe3\x03\n" + "\x0fSendManyRequest\x12L\n" + "\fAddrToAmount\x18\x01 \x03(\v2(.lnrpc.SendManyRequest.AddrToAmountEntryR\fAddrToAmount\x12\x1f\n" + "\vtarget_conf\x18\x03 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x04 \x01(\x04R\vsatPerVbyte\x12$\n" + + "targetConf\x12&\n" + + "\rsat_per_vbyte\x18\x04 \x01(\x04B\x02\x18\x01R\vsatPerVbyte\x12$\n" + "\fsat_per_byte\x18\x05 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x14\n" + + "satPerByte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\n" + + " \x01(\x04R\bsatPerKw\x12\x14\n" + "\x05label\x18\x06 \x01(\tR\x05label\x12\x1b\n" + "\tmin_confs\x18\a \x01(\x05R\bminConfs\x12+\n" + "\x11spend_unconfirmed\x18\b \x01(\bR\x10spendUnconfirmed\x12T\n" + @@ -19056,15 +19151,17 @@ const file_lightning_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\x03R\x05value:\x028\x01\"&\n" + "\x10SendManyResponse\x12\x12\n" + - "\x04txid\x18\x01 \x01(\tR\x04txid\"\xa9\x03\n" + + "\x04txid\x18\x01 \x01(\tR\x04txid\"\xcb\x03\n" + "\x10SendCoinsRequest\x12\x12\n" + "\x04addr\x18\x01 \x01(\tR\x04addr\x12\x16\n" + "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1f\n" + "\vtarget_conf\x18\x03 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x04 \x01(\x04R\vsatPerVbyte\x12$\n" + + "targetConf\x12&\n" + + "\rsat_per_vbyte\x18\x04 \x01(\x04B\x02\x18\x01R\vsatPerVbyte\x12$\n" + "\fsat_per_byte\x18\x05 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x19\n" + + "satPerByte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\f \x01(\x04R\bsatPerKw\x12\x19\n" + "\bsend_all\x18\x06 \x01(\bR\asendAll\x12\x14\n" + "\x05label\x18\a \x01(\tR\x05label\x12\x1b\n" + "\tmin_confs\x18\b \x01(\x05R\bminConfs\x12+\n" + @@ -19336,17 +19433,21 @@ const file_lightning_proto_rawDesc = "" + "\asuccess\x18\x02 \x01(\bR\asuccess\x12@\n" + "\x12local_close_output\x18\x03 \x01(\v2\x12.lnrpc.CloseOutputR\x10localCloseOutput\x12B\n" + "\x13remote_close_output\x18\x04 \x01(\v2\x12.lnrpc.CloseOutputR\x11remoteCloseOutput\x12A\n" + - "\x12additional_outputs\x18\x05 \x03(\v2\x12.lnrpc.CloseOutputR\x11additionalOutputs\"\xbf\x02\n" + + "\x12additional_outputs\x18\x05 \x03(\v2\x12.lnrpc.CloseOutputR\x11additionalOutputs\"\x8a\x03\n" + "\x13CloseChannelRequest\x128\n" + "\rchannel_point\x18\x01 \x01(\v2\x13.lnrpc.ChannelPointR\fchannelPoint\x12\x14\n" + "\x05force\x18\x02 \x01(\bR\x05force\x12\x1f\n" + "\vtarget_conf\x18\x03 \x01(\x05R\n" + "targetConf\x12$\n" + "\fsat_per_byte\x18\x04 \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12)\n" + - "\x10delivery_address\x18\x05 \x01(\tR\x0fdeliveryAddress\x12\"\n" + - "\rsat_per_vbyte\x18\x06 \x01(\x04R\vsatPerVbyte\x12)\n" + - "\x11max_fee_per_vbyte\x18\a \x01(\x04R\x0emaxFeePerVbyte\x12\x17\n" + + "satPerByte\x12&\n" + + "\rsat_per_vbyte\x18\x06 \x01(\x04B\x02\x18\x01R\vsatPerVbyte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\t \x01(\x04R\bsatPerKw\x12)\n" + + "\x10delivery_address\x18\x05 \x01(\tR\x0fdeliveryAddress\x12-\n" + + "\x11max_fee_per_vbyte\x18\a \x01(\x04B\x02\x18\x01R\x0emaxFeePerVbyte\x12#\n" + + "\x0emax_fee_per_kw\x18\n" + + " \x01(\x04R\vmaxFeePerKw\x12\x17\n" + "\ano_wait\x18\b \x01(\bR\x06noWait\"\xd3\x01\n" + "\x11CloseStatusUpdate\x12;\n" + "\rclose_pending\x18\x01 \x01(\v2\x14.lnrpc.PendingUpdateH\x00R\fclosePending\x12:\n" + @@ -19364,12 +19465,14 @@ const file_lightning_proto_rawDesc = "" + "\x13ReadyForPsbtFunding\x12'\n" + "\x0ffunding_address\x18\x01 \x01(\tR\x0efundingAddress\x12%\n" + "\x0efunding_amount\x18\x02 \x01(\x03R\rfundingAmount\x12\x12\n" + - "\x04psbt\x18\x03 \x01(\fR\x04psbt\"\xc9\x02\n" + + "\x04psbt\x18\x03 \x01(\fR\x04psbt\"\xeb\x02\n" + "\x17BatchOpenChannelRequest\x123\n" + "\bchannels\x18\x01 \x03(\v2\x17.lnrpc.BatchOpenChannelR\bchannels\x12\x1f\n" + "\vtarget_conf\x18\x02 \x01(\x05R\n" + - "targetConf\x12\"\n" + - "\rsat_per_vbyte\x18\x03 \x01(\x03R\vsatPerVbyte\x12\x1b\n" + + "targetConf\x12&\n" + + "\rsat_per_vbyte\x18\x03 \x01(\x03B\x02\x18\x01R\vsatPerVbyte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\b \x01(\x04R\bsatPerKw\x12\x1b\n" + "\tmin_confs\x18\x04 \x01(\x05R\bminConfs\x12+\n" + "\x11spend_unconfirmed\x18\x05 \x01(\bR\x10spendUnconfirmed\x12\x14\n" + "\x05label\x18\x06 \x01(\tR\x05label\x12T\n" + @@ -19401,18 +19504,20 @@ const file_lightning_proto_rawDesc = "" + "\x17remote_chan_reserve_sat\x18\x13 \x01(\x04R\x14remoteChanReserveSat\x12\x12\n" + "\x04memo\x18\x14 \x01(\tR\x04memo\"[\n" + "\x18BatchOpenChannelResponse\x12?\n" + - "\x10pending_channels\x18\x01 \x03(\v2\x14.lnrpc.PendingUpdateR\x0fpendingChannels\"\xcb\b\n" + - "\x12OpenChannelRequest\x12\"\n" + - "\rsat_per_vbyte\x18\x01 \x01(\x04R\vsatPerVbyte\x12\x1f\n" + + "\x10pending_channels\x18\x01 \x03(\v2\x14.lnrpc.PendingUpdateR\x0fpendingChannels\"\xed\b\n" + + "\x12OpenChannelRequest\x12&\n" + + "\rsat_per_vbyte\x18\x01 \x01(\x04B\x02\x18\x01R\vsatPerVbyte\x12$\n" + + "\fsat_per_byte\x18\a \x01(\x03B\x02\x18\x01R\n" + + "satPerByte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\x1d \x01(\x04R\bsatPerKw\x12\x1f\n" + "\vnode_pubkey\x18\x02 \x01(\fR\n" + "nodePubkey\x120\n" + "\x12node_pubkey_string\x18\x03 \x01(\tB\x02\x18\x01R\x10nodePubkeyString\x120\n" + "\x14local_funding_amount\x18\x04 \x01(\x03R\x12localFundingAmount\x12\x19\n" + "\bpush_sat\x18\x05 \x01(\x03R\apushSat\x12\x1f\n" + "\vtarget_conf\x18\x06 \x01(\x05R\n" + - "targetConf\x12$\n" + - "\fsat_per_byte\x18\a \x01(\x03B\x02\x18\x01R\n" + - "satPerByte\x12\x18\n" + + "targetConf\x12\x18\n" + "\aprivate\x18\b \x01(\bR\aprivate\x12\"\n" + "\rmin_htlc_msat\x18\t \x01(\x03R\vminHtlcMsat\x12(\n" + "\x10remote_csv_delay\x18\n" + diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index b60f45e0950..7be1149816f 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -1251,6 +1251,9 @@ message EstimateFeeResponse { // A list of selected inputs for the transaction the estimate is for. repeated OutPoint inputs = 4; + + // The fee rate in satoshi/kweight. + uint64 sat_per_kw = 5; } message SendManyRequest { @@ -1263,13 +1266,19 @@ message SendManyRequest { // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. - uint64 sat_per_vbyte = 4; + uint64 sat_per_vbyte = 4 [deprecated = true]; // Deprecated, use sat_per_vbyte. // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. int64 sat_per_byte = 5 [deprecated = true]; + /* + A manual fee rate set in sat/kweight that should be used when crafting the + transaction. + */ + uint64 sat_per_kw = 10; + // An optional label for the transaction, limited to 500 characters. string label = 6; @@ -1301,13 +1310,19 @@ message SendCoinsRequest { // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. - uint64 sat_per_vbyte = 4; + uint64 sat_per_vbyte = 4 [deprecated = true]; // Deprecated, use sat_per_vbyte. // A manual fee rate set in sat/vbyte that should be used when crafting the // transaction. int64 sat_per_byte = 5 [deprecated = true]; + /* + A manual fee rate set in sat/kweight that should be used when crafting the + transaction. + */ + uint64 sat_per_kw = 12; + /* If set, the amount field should be unset. It indicates lnd will send all wallet coins or all selected coins to the specified address. @@ -2248,6 +2263,18 @@ message CloseChannelRequest { // closure transaction. int64 sat_per_byte = 4 [deprecated = true]; + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + uint64 sat_per_vbyte = 6 [deprecated = true]; + + /* + A manual fee rate set in sat/kweight that should be used when crafting the + transaction. + + NOTE: This field is only respected if we're the initiator of the channel. + */ + uint64 sat_per_kw = 9; + /* An optional address to send funds to in the case of a cooperative close. If the channel was opened with an upfront shutdown script and this field @@ -2256,14 +2283,15 @@ message CloseChannelRequest { */ string delivery_address = 5; - // A manual fee rate set in sat/vbyte that should be used when crafting the - // closure transaction. - uint64 sat_per_vbyte = 6; + // The maximum fee rate the closer is willing to pay. + // + // NOTE: This field is only respected if we're the initiator of the channel. + uint64 max_fee_per_vbyte = 7 [deprecated = true]; // The maximum fee rate the closer is willing to pay. // // NOTE: This field is only respected if we're the initiator of the channel. - uint64 max_fee_per_vbyte = 7; + uint64 max_fee_per_kw = 10; // If true, then the rpc call will not block while it awaits a closing txid // to be broadcasted to the mempool. To obtain the closing tx one has to @@ -2329,7 +2357,13 @@ message BatchOpenChannelRequest { // A manual fee rate set in sat/vByte that should be used when crafting the // funding transaction. - int64 sat_per_vbyte = 3; + int64 sat_per_vbyte = 3 [deprecated = true]; + + /* + A manual fee rate set in sat/kweight that should be used when crafting the + transaction. + */ + uint64 sat_per_kw = 8; // The minimum number of confirmations each one of your outputs used for // the funding transaction must satisfy. @@ -2473,7 +2507,18 @@ message BatchOpenChannelResponse { message OpenChannelRequest { // A manual fee rate set in sat/vbyte that should be used when crafting the // funding transaction. - uint64 sat_per_vbyte = 1; + uint64 sat_per_vbyte = 1 [deprecated = true]; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + int64 sat_per_byte = 7 [deprecated = true]; + + /* + A manual fee rate set in sat/kweight that should be used when crafting the + funding transaction. + */ + uint64 sat_per_kw = 29; /* The pubkey of the node to open a channel with. When using REST, this field @@ -2498,11 +2543,6 @@ message OpenChannelRequest { // confirmed by. int32 target_conf = 6; - // Deprecated, use sat_per_vbyte. - // A manual fee rate set in sat/vbyte that should be used when crafting the - // funding transaction. - int64 sat_per_byte = 7 [deprecated = true]; - // Whether this channel should be private, not announced to the greater // network. bool private = 8; diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 0818132ede3..e0af7488e0e 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -853,6 +853,22 @@ "type": "string", "format": "int64" }, + { + "name": "sat_per_vbyte", + "description": "A manual fee rate set in sat/vbyte that should be used when crafting the\nclosure transaction.", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + }, + { + "name": "sat_per_kw", + "description": "A manual fee rate set in sat/kweight that should be used when crafting the\ntransaction.\n\nNOTE: This field is only respected if we're the initiator of the channel.", + "in": "query", + "required": false, + "type": "string", + "format": "uint64" + }, { "name": "delivery_address", "description": "An optional address to send funds to in the case of a cooperative close.\nIf the channel was opened with an upfront shutdown script and this field\nis set, the request to close will fail because the channel must pay out\nto the upfront shutdown addresss.", @@ -861,15 +877,15 @@ "type": "string" }, { - "name": "sat_per_vbyte", - "description": "A manual fee rate set in sat/vbyte that should be used when crafting the\nclosure transaction.", + "name": "max_fee_per_vbyte", + "description": "The maximum fee rate the closer is willing to pay.\n\nNOTE: This field is only respected if we're the initiator of the channel.", "in": "query", "required": false, "type": "string", "format": "uint64" }, { - "name": "max_fee_per_vbyte", + "name": "max_fee_per_kw", "description": "The maximum fee rate the closer is willing to pay.\n\nNOTE: This field is only respected if we're the initiator of the channel.", "in": "query", "required": false, @@ -3768,6 +3784,11 @@ "format": "int64", "description": "A manual fee rate set in sat/vByte that should be used when crafting the\nfunding transaction." }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "A manual fee rate set in sat/kweight that should be used when crafting the\ntransaction." + }, "min_confs": { "type": "integer", "format": "int32", @@ -5085,6 +5106,11 @@ "$ref": "#/definitions/lnrpcOutPoint" }, "description": "A list of selected inputs for the transaction the estimate is for." + }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "The fee rate in satoshi/kweight." } } }, @@ -6652,6 +6678,16 @@ "format": "uint64", "description": "A manual fee rate set in sat/vbyte that should be used when crafting the\nfunding transaction." }, + "sat_per_byte": { + "type": "string", + "format": "int64", + "description": "Deprecated, use sat_per_vbyte.\nA manual fee rate set in sat/vbyte that should be used when crafting the\nfunding transaction." + }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "A manual fee rate set in sat/kweight that should be used when crafting the\nfunding transaction." + }, "node_pubkey": { "type": "string", "format": "byte", @@ -6676,11 +6712,6 @@ "format": "int32", "description": "The target number of blocks that the funding transaction should be\nconfirmed by." }, - "sat_per_byte": { - "type": "string", - "format": "int64", - "description": "Deprecated, use sat_per_vbyte.\nA manual fee rate set in sat/vbyte that should be used when crafting the\nfunding transaction." - }, "private": { "type": "boolean", "description": "Whether this channel should be private, not announced to the greater\nnetwork." @@ -7636,6 +7667,11 @@ "format": "int64", "description": "Deprecated, use sat_per_vbyte.\nA manual fee rate set in sat/vbyte that should be used when crafting the\ntransaction." }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "A manual fee rate set in sat/kweight that should be used when crafting the\ntransaction." + }, "send_all": { "type": "boolean", "description": "If set, the amount field should be unset. It indicates lnd will send all\nwallet coins or all selected coins to the specified address." @@ -7731,6 +7767,11 @@ "format": "int64", "description": "Deprecated, use sat_per_vbyte.\nA manual fee rate set in sat/vbyte that should be used when crafting the\ntransaction." }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "A manual fee rate set in sat/kweight that should be used when crafting the\ntransaction." + }, "label": { "type": "string", "description": "An optional label for the transaction, limited to 500 characters." diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index ef8e84ee182..6d5e9b1997b 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -2758,6 +2758,16 @@ type PendingSweep struct { // // Deprecated: Marked as deprecated in walletrpc/walletkit.proto. SatPerByte uint32 `protobuf:"varint,4,opt,name=sat_per_byte,json=satPerByte,proto3" json:"sat_per_byte,omitempty"` + // The current fee rate we'll use to sweep the output, expressed in sat/vbyte. + // The fee rate is only determined once a sweeping transaction for the output + // is created, so it's possible for this to be 0 before this. + // + // Deprecated: Marked as deprecated in walletrpc/walletkit.proto. + SatPerVbyte uint64 `protobuf:"varint,10,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` + // The fee rate we'll use to sweep the output, expressed in sat/kweight. The + // fee rate is only determined once a sweeping transaction for the output is + // created, so it's possible for this to be 0 before this. + SatPerKw uint64 `protobuf:"varint,17,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` // The number of broadcast attempts we've made to sweep the output. BroadcastAttempts uint32 `protobuf:"varint,5,opt,name=broadcast_attempts,json=broadcastAttempts,proto3" json:"broadcast_attempts,omitempty"` // Deprecated. @@ -2783,13 +2793,13 @@ type PendingSweep struct { // // Deprecated: Marked as deprecated in walletrpc/walletkit.proto. RequestedSatPerByte uint32 `protobuf:"varint,9,opt,name=requested_sat_per_byte,json=requestedSatPerByte,proto3" json:"requested_sat_per_byte,omitempty"` - // The current fee rate we'll use to sweep the output, expressed in sat/vbyte. - // The fee rate is only determined once a sweeping transaction for the output - // is created, so it's possible for this to be 0 before this. - SatPerVbyte uint64 `protobuf:"varint,10,opt,name=sat_per_vbyte,json=satPerVbyte,proto3" json:"sat_per_vbyte,omitempty"` // The requested starting fee rate, expressed in sat/vbyte, for this // output. When not requested, this field will be 0. + // + // Deprecated: Marked as deprecated in walletrpc/walletkit.proto. RequestedSatPerVbyte uint64 `protobuf:"varint,11,opt,name=requested_sat_per_vbyte,json=requestedSatPerVbyte,proto3" json:"requested_sat_per_vbyte,omitempty"` + // The requested fee rate, expressed in sat/kweight, for this output. + RequestedSatPerKw uint64 `protobuf:"varint,16,opt,name=requested_sat_per_kw,json=requestedSatPerKw,proto3" json:"requested_sat_per_kw,omitempty"` // Whether this input will be swept immediately. Immediate bool `protobuf:"varint,12,opt,name=immediate,proto3" json:"immediate,omitempty"` // The budget for this sweep, expressed in satoshis. This is the maximum amount @@ -2863,6 +2873,21 @@ func (x *PendingSweep) GetSatPerByte() uint32 { return 0 } +// Deprecated: Marked as deprecated in walletrpc/walletkit.proto. +func (x *PendingSweep) GetSatPerVbyte() uint64 { + if x != nil { + return x.SatPerVbyte + } + return 0 +} + +func (x *PendingSweep) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + func (x *PendingSweep) GetBroadcastAttempts() uint32 { if x != nil { return x.BroadcastAttempts @@ -2902,16 +2927,17 @@ func (x *PendingSweep) GetRequestedSatPerByte() uint32 { return 0 } -func (x *PendingSweep) GetSatPerVbyte() uint64 { +// Deprecated: Marked as deprecated in walletrpc/walletkit.proto. +func (x *PendingSweep) GetRequestedSatPerVbyte() uint64 { if x != nil { - return x.SatPerVbyte + return x.RequestedSatPerVbyte } return 0 } -func (x *PendingSweep) GetRequestedSatPerVbyte() uint64 { +func (x *PendingSweep) GetRequestedSatPerKw() uint64 { if x != nil { - return x.RequestedSatPerVbyte + return x.RequestedSatPerKw } return 0 } @@ -3064,6 +3090,9 @@ type BumpFeeRequest struct { // fee function that the sweeper will use to bump the fee rate. When the // deadline is reached, ALL the budget will be spent as fees. DeadlineDelta uint32 `protobuf:"varint,8,opt,name=deadline_delta,json=deadlineDelta,proto3" json:"deadline_delta,omitempty"` + // The fee rate, expressed in sat/kweight, that should be used to spend the + // input with. + SatPerKw uint64 `protobuf:"varint,9,opt,name=sat_per_kw,json=satPerKw,proto3" json:"sat_per_kw,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3156,6 +3185,13 @@ func (x *BumpFeeRequest) GetDeadlineDelta() uint32 { return 0 } +func (x *BumpFeeRequest) GetSatPerKw() uint64 { + if x != nil { + return x.SatPerKw + } + return 0 +} + type BumpFeeResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // The status of the bump fee operation. @@ -4664,29 +4700,32 @@ const file_walletrpc_walletkit_proto_rawDesc = "" + "\x13EstimateFeeResponse\x12\x1c\n" + "\n" + "sat_per_kw\x18\x01 \x01(\x03R\bsatPerKw\x125\n" + - "\x18min_relay_fee_sat_per_kw\x18\x02 \x01(\x03R\x13minRelayFeeSatPerKw\"\x90\x05\n" + + "\x18min_relay_fee_sat_per_kw\x18\x02 \x01(\x03R\x13minRelayFeeSatPerKw\"\xe7\x05\n" + "\fPendingSweep\x12+\n" + "\boutpoint\x18\x01 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x129\n" + "\fwitness_type\x18\x02 \x01(\x0e2\x16.walletrpc.WitnessTypeR\vwitnessType\x12\x1d\n" + "\n" + "amount_sat\x18\x03 \x01(\rR\tamountSat\x12$\n" + "\fsat_per_byte\x18\x04 \x01(\rB\x02\x18\x01R\n" + - "satPerByte\x12-\n" + + "satPerByte\x12&\n" + + "\rsat_per_vbyte\x18\n" + + " \x01(\x04B\x02\x18\x01R\vsatPerVbyte\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\x11 \x01(\x04R\bsatPerKw\x12-\n" + "\x12broadcast_attempts\x18\x05 \x01(\rR\x11broadcastAttempts\x126\n" + "\x15next_broadcast_height\x18\x06 \x01(\rB\x02\x18\x01R\x13nextBroadcastHeight\x12\x18\n" + "\x05force\x18\a \x01(\bB\x02\x18\x01R\x05force\x126\n" + "\x15requested_conf_target\x18\b \x01(\rB\x02\x18\x01R\x13requestedConfTarget\x127\n" + - "\x16requested_sat_per_byte\x18\t \x01(\rB\x02\x18\x01R\x13requestedSatPerByte\x12\"\n" + - "\rsat_per_vbyte\x18\n" + - " \x01(\x04R\vsatPerVbyte\x125\n" + - "\x17requested_sat_per_vbyte\x18\v \x01(\x04R\x14requestedSatPerVbyte\x12\x1c\n" + + "\x16requested_sat_per_byte\x18\t \x01(\rB\x02\x18\x01R\x13requestedSatPerByte\x129\n" + + "\x17requested_sat_per_vbyte\x18\v \x01(\x04B\x02\x18\x01R\x14requestedSatPerVbyte\x12/\n" + + "\x14requested_sat_per_kw\x18\x10 \x01(\x04R\x11requestedSatPerKw\x12\x1c\n" + "\timmediate\x18\f \x01(\bR\timmediate\x12\x16\n" + "\x06budget\x18\r \x01(\x04R\x06budget\x12'\n" + "\x0fdeadline_height\x18\x0e \x01(\rR\x0edeadlineHeight\x12'\n" + "\x0fmaturity_height\x18\x0f \x01(\rR\x0ematurityHeight\"\x16\n" + "\x14PendingSweepsRequest\"W\n" + "\x15PendingSweepsResponse\x12>\n" + - "\x0epending_sweeps\x18\x01 \x03(\v2\x17.walletrpc.PendingSweepR\rpendingSweeps\"\x9f\x02\n" + + "\x0epending_sweeps\x18\x01 \x03(\v2\x17.walletrpc.PendingSweepR\rpendingSweeps\"\xbd\x02\n" + "\x0eBumpFeeRequest\x12+\n" + "\boutpoint\x18\x01 \x01(\v2\x0f.lnrpc.OutPointR\boutpoint\x12\x1f\n" + "\vtarget_conf\x18\x02 \x01(\rR\n" + @@ -4697,7 +4736,9 @@ const file_walletrpc_walletkit_proto_rawDesc = "" + "\rsat_per_vbyte\x18\x05 \x01(\x04R\vsatPerVbyte\x12\x1c\n" + "\timmediate\x18\x06 \x01(\bR\timmediate\x12\x16\n" + "\x06budget\x18\a \x01(\x04R\x06budget\x12%\n" + - "\x0edeadline_delta\x18\b \x01(\rR\rdeadlineDelta\")\n" + + "\x0edeadline_delta\x18\b \x01(\rR\rdeadlineDelta\x12\x1c\n" + + "\n" + + "sat_per_kw\x18\t \x01(\x04R\bsatPerKw\")\n" + "\x0fBumpFeeResponse\x12\x16\n" + "\x06status\x18\x01 \x01(\tR\x06status\"\xf7\x01\n" + "\x18BumpForceCloseFeeRequest\x122\n" + diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 81176d90910..d78d85fd9ba 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -1168,6 +1168,20 @@ message PendingSweep { */ uint32 sat_per_byte = 4 [deprecated = true]; + /* + The current fee rate we'll use to sweep the output, expressed in sat/vbyte. + The fee rate is only determined once a sweeping transaction for the output + is created, so it's possible for this to be 0 before this. + */ + uint64 sat_per_vbyte = 10 [deprecated = true]; + + /* + The fee rate we'll use to sweep the output, expressed in sat/kweight. The + fee rate is only determined once a sweeping transaction for the output is + created, so it's possible for this to be 0 before this. + */ + uint64 sat_per_kw = 17; + // The number of broadcast attempts we've made to sweep the output. uint32 broadcast_attempts = 5; @@ -1196,16 +1210,14 @@ message PendingSweep { // The requested fee rate, expressed in sat/vbyte, for this output. uint32 requested_sat_per_byte = 9 [deprecated = true]; - /* - The current fee rate we'll use to sweep the output, expressed in sat/vbyte. - The fee rate is only determined once a sweeping transaction for the output - is created, so it's possible for this to be 0 before this. - */ - uint64 sat_per_vbyte = 10; - // The requested starting fee rate, expressed in sat/vbyte, for this // output. When not requested, this field will be 0. - uint64 requested_sat_per_vbyte = 11; + uint64 requested_sat_per_vbyte = 11 [deprecated = true]; + + /* + The requested fee rate, expressed in sat/kweight, for this output. + */ + uint64 requested_sat_per_kw = 16; /* Whether this input will be swept immediately. @@ -1291,6 +1303,10 @@ message BumpFeeRequest { // fee function that the sweeper will use to bump the fee rate. When the // deadline is reached, ALL the budget will be spent as fees. uint32 deadline_delta = 8; + + // The fee rate, expressed in sat/kweight, that should be used to spend the + // input with. + uint64 sat_per_kw = 9; } message BumpFeeResponse { diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index e5a0946c103..da4be1e7a93 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -1453,6 +1453,11 @@ "type": "integer", "format": "int64", "description": "Optional. The deadline delta in number of blocks that the output\nshould be spent within. This translates internally to the width of the\nfee function that the sweeper will use to bump the fee rate. When the\ndeadline is reached, ALL the budget will be spent as fees." + }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "The fee rate, expressed in sat/kweight, that should be used to spend the\ninput with." } } }, @@ -1946,6 +1951,16 @@ "format": "int64", "description": "Deprecated, use sat_per_vbyte.\nThe fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee\nrate is only determined once a sweeping transaction for the output is\ncreated, so it's possible for this to be 0 before this." }, + "sat_per_vbyte": { + "type": "string", + "format": "uint64", + "description": "The current fee rate we'll use to sweep the output, expressed in sat/vbyte.\nThe fee rate is only determined once a sweeping transaction for the output\nis created, so it's possible for this to be 0 before this." + }, + "sat_per_kw": { + "type": "string", + "format": "uint64", + "description": "The fee rate we'll use to sweep the output, expressed in sat/kweight. The\nfee rate is only determined once a sweeping transaction for the output is\ncreated, so it's possible for this to be 0 before this." + }, "broadcast_attempts": { "type": "integer", "format": "int64", @@ -1970,15 +1985,15 @@ "format": "int64", "description": "Deprecated, use requested_sat_per_vbyte.\nThe requested fee rate, expressed in sat/vbyte, for this output." }, - "sat_per_vbyte": { + "requested_sat_per_vbyte": { "type": "string", "format": "uint64", - "description": "The current fee rate we'll use to sweep the output, expressed in sat/vbyte.\nThe fee rate is only determined once a sweeping transaction for the output\nis created, so it's possible for this to be 0 before this." + "description": "The requested starting fee rate, expressed in sat/vbyte, for this\noutput. When not requested, this field will be 0." }, - "requested_sat_per_vbyte": { + "requested_sat_per_kw": { "type": "string", "format": "uint64", - "description": "The requested starting fee rate, expressed in sat/vbyte, for this\noutput. When not requested, this field will be 0." + "description": "The requested fee rate, expressed in sat/kweight, for this output." }, "immediate": { "type": "boolean", From 100930f3ca1dcde8f63b62f4589944977cc59049 Mon Sep 17 00:00:00 2001 From: MPins Date: Tue, 16 Sep 2025 10:49:30 -0700 Subject: [PATCH 2/8] rpcserver+walletrpc+lnrpc: add support for sat/kw option Fee calculation for sendcoins, sendmany, openchannel, closechannel, PendingSweeps, BumpFee has now the option sat_per_kw. In addition a safety check is added to prevent very high fees. For the estimatefee cmd the fee in sat_per_kw is added to the response. --- lnrpc/rpc_utils.go | 39 +++++++++++++++----- lnrpc/walletrpc/walletkit_server.go | 31 +++++++++++----- rpcserver.go | 57 ++++++++++++++++------------- 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/lnrpc/rpc_utils.go b/lnrpc/rpc_utils.go index 15e3f2f7622..ce66a03dc8b 100644 --- a/lnrpc/rpc_utils.go +++ b/lnrpc/rpc_utils.go @@ -231,23 +231,38 @@ func GetChannelOutPoint(chanPoint *ChannelPoint) (*OutPoint, error) { // request to calculate the fee rate. It provides compatibility for the // deprecated field, satPerByte. Once the field is safe to be removed, the // check can then be deleted. -func CalculateFeeRate(satPerByte, satPerVByte uint64, targetConf uint32, - estimator chainfee.Estimator) (chainfee.SatPerKWeight, error) { +func CalculateFeeRate(satPerByte int64, satPerVByte uint64, targetConf uint32, + satPerKWeight uint64, estimator chainfee.Estimator) ( + chainfee.SatPerKWeight, error) { var feeRate chainfee.SatPerKWeight - // We only allow using either the deprecated field or the new field. - if satPerByte != 0 && satPerVByte != 0 { - return feeRate, fmt.Errorf("either SatPerByte or " + - "SatPerVByte should be set, but not both") - } + // Only one fee field may be set: satPerKWeight, satPerByte, or + // satPerVByte. Set the fee rate based on which field is provided. + var satPerKw chainfee.SatPerKWeight + fieldsSet := 0 + + if satPerVByte != 0 { + satPerKw = chainfee.SatPerKVByte( + satPerVByte * 1000, + ).FeePerKWeight() - // Default to satPerVByte, and overwrite it if satPerByte is set. - satPerKw := chainfee.SatPerKVByte(satPerVByte * 1000).FeePerKWeight() + fieldsSet++ + } if satPerByte != 0 { satPerKw = chainfee.SatPerKVByte( satPerByte * 1000, ).FeePerKWeight() + + fieldsSet++ + } + if satPerKWeight != 0 { + satPerKw = chainfee.SatPerKWeight(satPerKWeight) + fieldsSet++ + } + + if fieldsSet > 1 { + return feeRate, fmt.Errorf("only one fee field may be set") } // Based on the passed fee related parameters, we'll determine an @@ -262,5 +277,11 @@ func CalculateFeeRate(satPerByte, satPerVByte uint64, targetConf uint32, return feeRate, err } + // A security measure to prevent very high fees, especially + // when the fee is specified via satPerKWeight. + if feeRate > sweep.DefaultMaxFeeRate.FeePerKWeight() { + feeRate = sweep.DefaultMaxFeeRate.FeePerKWeight() + } + return feeRate, nil } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index bece6001d6a..bd0a47eb4d3 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -898,6 +898,7 @@ func (w *WalletKit) PendingSweeps(ctx context.Context, op := lnrpc.MarshalOutPoint(&inp.OutPoint) amountSat := uint32(inp.Amount) satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte()) + satPerKWeight := uint64(inp.LastFeeRate) broadcastAttempts := uint32(inp.BroadcastAttempts) // Get the requested starting fee rate, if set. @@ -907,16 +908,25 @@ func (w *WalletKit) PendingSweeps(ctx context.Context, return uint64(feeRate.FeePerVByte()) }) + startingFeeRateKw := fn.MapOptionZ( + inp.Params.StartingFeeRate, + func(feeRate chainfee.SatPerKWeight) uint64 { + return uint64(feeRate) + }, + ) + ps := &PendingSweep{ Outpoint: op, WitnessType: witnessType, AmountSat: amountSat, SatPerVbyte: satPerVbyte, + SatPerKw: satPerKWeight, BroadcastAttempts: broadcastAttempts, Immediate: inp.Params.Immediate, Budget: uint64(inp.Params.Budget), DeadlineHeight: inp.DeadlineHeight, RequestedSatPerVbyte: startingFeeRate, + RequestedSatPerKw: startingFeeRateKw, MaturityHeight: inp.MaturityHeight, } rpcPendingSweeps = append(rpcPendingSweeps, ps) @@ -968,23 +978,24 @@ func validateBumpFeeRequest(in *BumpFeeRequest, estimator chainfee.Estimator) ( // Get the specified fee rate if set. satPerKwOpt := fn.None[chainfee.SatPerKWeight]() - // We only allow using either the deprecated field or the new field. + // We only allow using either the SatPerVbyte or SatPerKweight, + // but not both. switch { - case in.SatPerByte != 0 && in.SatPerVbyte != 0: - return satPerKwOpt, false, fmt.Errorf("either SatPerByte or " + - "SatPerVbyte should be set, but not both") - - case in.SatPerByte != 0: - satPerKw := chainfee.SatPerVByte( - in.SatPerByte, - ).FeePerKWeight() - satPerKwOpt = fn.Some(satPerKw) + case in.SatPerKw != 0 && in.SatPerVbyte != 0: + return satPerKwOpt, false, fmt.Errorf("either SatPerVbyte or " + + "SatPerKweight should be set, but not both") case in.SatPerVbyte != 0: satPerKw := chainfee.SatPerVByte( in.SatPerVbyte, ).FeePerKWeight() satPerKwOpt = fn.Some(satPerKw) + + case in.SatPerKw != 0: + satPerKw := chainfee.SatPerKWeight( + in.SatPerKw, + ) + satPerKwOpt = fn.Some(satPerKw) } // We make sure either the conf target or the exact fee rate is diff --git a/rpcserver.go b/rpcserver.go index e62de95fb1d..5697c438449 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1334,6 +1334,7 @@ func (r *rpcServer) EstimateFee(ctx context.Context, resp := &lnrpc.EstimateFeeResponse{ FeeSat: totalFee, SatPerVbyte: uint64(feePerKw.FeePerVByte()), + SatPerKw: uint64(feePerKw), // Deprecated field. FeerateSatPerByte: int64(feePerKw.FeePerVByte()), @@ -1348,13 +1349,13 @@ func (r *rpcServer) EstimateFee(ctx context.Context, // maybeUseDefaultConf makes sure that when the user doesn't set either the fee // rate or conf target, the default conf target is used. -func maybeUseDefaultConf(satPerByte int64, satPerVByte uint64, - targetConf uint32) uint32 { +func maybeUseDefaultConf(satPerByte int64, satPerVbyte uint64, + satPerKweight uint64, targetConf uint32) uint32 { // If the fee rate is set, there's no need to use the default conf // target. In this case, we just return the targetConf from the // request. - if satPerByte != 0 || satPerVByte != 0 { + if satPerByte != 0 || satPerVbyte != 0 || satPerKweight != 0 { return targetConf } @@ -1365,8 +1366,8 @@ func maybeUseDefaultConf(satPerByte int64, satPerVByte uint64, // If the fee rate is not set, yet the conf target is zero, the default // 6 will be returned. - rpcsLog.Warnf("Expected either 'sat_per_vbyte' or 'conf_target' to " + - "be set, using default conf of 6 instead") + rpcsLog.Warnf("Expected either 'sat_per_vbyte', 'sat_per_kw' or " + + "'conf_target' to be set, using default conf of 6 instead") return defaultNumBlocksEstimate } @@ -1379,13 +1380,14 @@ func (r *rpcServer) SendCoins(ctx context.Context, // Keep the old behavior prior to 0.18.0 - when the user doesn't set // fee rate or conf target, the default conf target of 6 is used. targetConf := maybeUseDefaultConf( - in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf), + in.SatPerByte, in.SatPerVbyte, in.SatPerKw, + uint32(in.TargetConf), ) // Calculate an appropriate fee rate for this transaction. feePerKw, err := lnrpc.CalculateFeeRate( - uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck - targetConf, r.server.cc.FeeEstimator, + in.SatPerByte, in.SatPerVbyte, targetConf, in.SatPerKw, + r.server.cc.FeeEstimator, ) if err != nil { return nil, err @@ -1623,13 +1625,14 @@ func (r *rpcServer) SendMany(ctx context.Context, // Keep the old behavior prior to 0.18.0 - when the user doesn't set // fee rate or conf target, the default conf target of 6 is used. targetConf := maybeUseDefaultConf( - in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf), + in.SatPerByte, in.SatPerVbyte, in.SatPerKw, + uint32(in.TargetConf), ) // Calculate an appropriate fee rate for this transaction. feePerKw, err := lnrpc.CalculateFeeRate( - uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck - targetConf, r.server.cc.FeeEstimator, + in.SatPerByte, in.SatPerVbyte, targetConf, in.SatPerKw, + r.server.cc.FeeEstimator, ) if err != nil { return nil, err @@ -2300,13 +2303,14 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, // NOTE: We also need to do the fee rate calculation for the psbt // funding flow because the `batchfund` depends on it. targetConf := maybeUseDefaultConf( - in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf), + in.SatPerByte, in.SatPerVbyte, in.SatPerKw, + uint32(in.TargetConf), ) // Calculate an appropriate fee rate for this transaction. feeRate, err := lnrpc.CalculateFeeRate( - uint64(in.SatPerByte), in.SatPerVbyte, - targetConf, r.server.cc.FeeEstimator, + in.SatPerByte, in.SatPerVbyte, targetConf, in.SatPerKw, + r.server.cc.FeeEstimator, ) if err != nil { return nil, err @@ -2766,8 +2770,8 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // If force closing a channel, the fee set in the commitment transaction // is used. - if in.Force && (in.SatPerByte != 0 || in.SatPerVbyte != 0 || // nolint:staticcheck - in.TargetConf != 0) { + if in.Force && (in.SatPerByte != 0 || in.SatPerVbyte != 0 || + in.TargetConf != 0 || in.SatPerKw != 0) { return fmt.Errorf("force closing a channel uses a pre-defined fee") } @@ -2943,15 +2947,16 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, // doesn't set fee rate or conf target, the default conf target // of 6 is used. targetConf := maybeUseDefaultConf( - in.SatPerByte, in.SatPerVbyte, uint32(in.TargetConf), + in.SatPerByte, in.SatPerVbyte, in.SatPerKw, + uint32(in.TargetConf), ) // Based on the passed fee related parameters, we'll determine // an appropriate fee rate for the cooperative closure // transaction. feeRate, err := lnrpc.CalculateFeeRate( - uint64(in.SatPerByte), in.SatPerVbyte, // nolint:staticcheck - targetConf, r.server.cc.FeeEstimator, + in.SatPerByte, in.SatPerVbyte, targetConf, in.SatPerKw, + r.server.cc.FeeEstimator, ) if err != nil { return err @@ -3002,9 +3007,14 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, } } - maxFee := chainfee.SatPerKVByte( - in.MaxFeePerVbyte * 1000, - ).FeePerKWeight() + // We default to MaxFeePerKw and only use MaxFeePerVbyte if it + // was set. + maxFee := chainfee.SatPerKWeight(in.MaxFeePerKw) + if in.MaxFeePerVbyte != 0 { + maxFee = chainfee.SatPerKVByte( + in.MaxFeePerVbyte * 1000, + ).FeePerKWeight() + } // In case the max fee was specified, we check if it's less than // the initial fee rate and abort if it is. @@ -3030,9 +3040,6 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, updateChan = closeUpdates.UpdateChan errChan = closeUpdates.ErrChan } else { - maxFee := chainfee.SatPerKVByte( - in.MaxFeePerVbyte * 1000, - ).FeePerKWeight() updateChan, errChan = r.server.htlcSwitch.CloseLink( updateStream.Context(), chanPoint, contractcourt.CloseRegular, feeRate, maxFee, From 371be40d768d9c52f3ba7f5009b66ad1d77f74fd Mon Sep 17 00:00:00 2001 From: MPins Date: Wed, 1 Oct 2025 10:10:01 -0700 Subject: [PATCH 3/8] commands: add sat/kw option in the lncli commands The sat_per_vbyte input option now accepts fractional values and is internally converted into sat_per_kw. The max_fee_per_vbyte input option now accepts fractional values and is internally converted into max_fee_per_kw for fee calculation. Add the sat_per_kw option for the lnrpc cmds sendcoins, sendmany, openchannel, batchopenchannel, closechannel and closeallchannels. Add the sat_per_kw for estimatefee in the response. Add sat_per_kw option for walletrpc cmds "wallet bumpfee" and in the response of "wallet pending sweeps". Add max_fee_per_kw for closechannel command. --- cmd/commands/cmd_open_channel.go | 26 ++++++++++---- cmd/commands/commands.go | 58 ++++++++++++++++++++++++-------- cmd/commands/main.go | 19 +++++++++++ cmd/commands/walletrpc_active.go | 10 ++++-- cmd/commands/walletrpc_types.go | 4 +++ 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/cmd/commands/cmd_open_channel.go b/cmd/commands/cmd_open_channel.go index 7adf48a3445..d9aa2d32133 100644 --- a/cmd/commands/cmd_open_channel.go +++ b/cmd/commands/cmd_open_channel.go @@ -172,7 +172,7 @@ var openChannelCommand = cli.Command{ Usage: "Deprecated, use sat_per_vbyte instead.", Hidden: true, }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + @@ -306,17 +306,22 @@ func openChannel(ctx *cli.Context) error { // Check that only the field sat_per_vbyte or the deprecated field // sat_per_byte is used. - feeRateFlag, err := checkNotBothSet( + _, err = checkNotBothSet( ctx, "sat_per_vbyte", "sat_per_byte", ) if err != nil { return err } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) req := &lnrpc.OpenChannelRequest{ TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), MinHtlcMsat: ctx.Int64("min_htlc_msat"), RemoteCsvDelay: uint32(ctx.Uint64("remote_csv_delay")), MinConfs: minConfs, @@ -329,6 +334,7 @@ func openChannel(ctx *cli.Context) error { RemoteChanReserveSat: ctx.Uint64("remote_reserve_sats"), FundMax: ctx.Bool("fundmax"), Memo: ctx.String("memo"), + SatPerKw: satPerKw, } switch { @@ -802,7 +808,7 @@ var batchOpenChannelCommand = cli.Command{ "transaction *should* confirm in, will be " + "used for fee estimation", }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vByte that should be used when crafting " + @@ -855,14 +861,20 @@ func batchOpenChannel(ctx *cli.Context) error { return err } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) req := &lnrpc.BatchOpenChannelRequest{ TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")), MinConfs: minConfs, SpendUnconfirmed: minConfs == 0, Label: ctx.String("label"), CoinSelectionStrategy: coinSelectionStrategy, + SatPerKw: satPerKw, } // Let's try and parse the JSON part of the CLI now. Fortunately we can @@ -1062,7 +1074,9 @@ func checkPsbtFlags(req *lnrpc.OpenChannelRequest) error { return fmt.Errorf("specifying minimum confirmations for PSBT " + "funding is not supported") } - if req.TargetConf != 0 || req.SatPerByte != 0 || req.SatPerVbyte != 0 { // nolint:staticcheck + if req.TargetConf != 0 || req.SatPerByte != 0 || req.SatPerVbyte != 0 || + req.SatPerKw != 0 { + return fmt.Errorf("setting fee estimation parameters not " + "supported for PSBT funding") } diff --git a/cmd/commands/commands.go b/cmd/commands/commands.go index 0c389a610b0..dcb4ce695fc 100644 --- a/cmd/commands/commands.go +++ b/cmd/commands/commands.go @@ -502,7 +502,7 @@ var sendCoinsCommand = cli.Command{ Usage: "Deprecated, use sat_per_vbyte instead.", Hidden: true, }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + @@ -658,6 +658,12 @@ func sendCoins(ctx *cli.Context) error { } } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + // Ask for confirmation if we're on an actual terminal and the output is // not being redirected to another command. This prevents existing shell // scripts from breaking. @@ -675,13 +681,13 @@ func sendCoins(ctx *cli.Context) error { Addr: addr, Amount: amt, TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), SendAll: ctx.Bool("sweepall"), Label: ctx.String(txLabelFlag.Name), MinConfs: minConfs, SpendUnconfirmed: minConfs == 0, CoinSelectionStrategy: coinSelectionStrategy, Outpoints: outpoints, + SatPerKw: satPerKw, } txid, err := client.SendCoins(ctxc, req) if err != nil { @@ -841,7 +847,7 @@ var sendManyCommand = cli.Command{ Usage: "Deprecated, use sat_per_vbyte instead.", Hidden: true, }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + @@ -893,15 +899,21 @@ func sendMany(ctx *cli.Context) error { client, cleanUp := getClient(ctx) defer cleanUp() + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{ AddrToAmount: amountToAddr, TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), Label: ctx.String(txLabelFlag.Name), MinConfs: minConfs, SpendUnconfirmed: minConfs == 0, CoinSelectionStrategy: coinSelectionStrategy, + SatPerKw: satPerKw, }) if err != nil { return err @@ -1092,7 +1104,7 @@ var closeChannelCommand = cli.Command{ Usage: "Deprecated, use sat_per_vbyte instead.", Hidden: true, }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + @@ -1106,7 +1118,7 @@ var closeChannelCommand = cli.Command{ "be used if an upfront shutdown address is not " + "already set", }, - cli.Uint64Flag{ + cli.Float64Flag{ Name: "max_fee_rate", Usage: "(optional) maximum fee rate in sat/vbyte " + "accepted during the negotiation (default is " + @@ -1131,7 +1143,7 @@ func closeChannel(ctx *cli.Context) error { // Check that only the field sat_per_vbyte or the deprecated field // sat_per_byte is used. - feeRateFlag, err := checkNotBothSet( + _, err := checkNotBothSet( ctx, "sat_per_vbyte", "sat_per_byte", ) if err != nil { @@ -1143,14 +1155,26 @@ func closeChannel(ctx *cli.Context) error { return err } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + + // Parse fee rate from --max_fee_rate and convert to sat/kw. + maxFeePerKw, err := parseFeeRate(ctx, "max_fee_rate") + if err != nil { + return err + } + // TODO(roasbeef): implement time deadline within server req := &lnrpc.CloseChannelRequest{ ChannelPoint: channelPoint, Force: ctx.Bool("force"), TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), + SatPerKw: satPerKw, DeliveryAddress: ctx.String("delivery_addr"), - MaxFeePerVbyte: ctx.Uint64("max_fee_rate"), + MaxFeePerKw: maxFeePerKw, // This makes sure that a coop close will also be executed if // active HTLCs are present on the channel. NoWait: true, @@ -1296,7 +1320,7 @@ var closeAllChannelsCommand = cli.Command{ Usage: "Deprecated, use sat_per_vbyte instead.", Hidden: true, }, - cli.Int64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + @@ -1318,13 +1342,19 @@ func closeAllChannels(ctx *cli.Context) error { // Check that only the field sat_per_vbyte or the deprecated field // sat_per_byte is used. - feeRateFlag, err := checkNotBothSet( + _, err := checkNotBothSet( ctx, "sat_per_vbyte", "sat_per_byte", ) if err != nil { return err } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + prompt := "Do you really want to close ALL channels? (yes/no): " if !ctx.Bool("skip_confirmation") && !promptForConfirmation(prompt) { return errors.New("action aborted by user") @@ -1456,9 +1486,9 @@ func closeAllChannels(ctx *cli.Context) error { }, OutputIndex: uint32(index), }, - Force: !channel.GetActive(), - TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), + Force: !channel.GetActive(), + TargetConf: int32(ctx.Int64("conf_target")), + SatPerKw: satPerKw, } txidChan := make(chan string, 1) diff --git a/cmd/commands/main.go b/cmd/commands/main.go index dc9b993de57..26e3edce6b4 100644 --- a/cmd/commands/main.go +++ b/cmd/commands/main.go @@ -14,6 +14,7 @@ import ( "strings" "syscall" + "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/lightningnetwork/lnd" @@ -328,6 +329,24 @@ func extractPathArgs(ctx *cli.Context) (string, string, error) { return tlsCertPath, macPath, nil } +// parseFeeRate converts a fee from sat/vB to sat/kw using float64 math. +// We round up to avoid underpaying due to floating point truncation. +func parseFeeRate(ctx *cli.Context, flagName string) (uint64, error) { + if !ctx.IsSet(flagName) { + return 0, nil + } + + satPerVb := ctx.Float64(flagName) + if satPerVb <= 0 { + return 0, fmt.Errorf("invalid --%s", flagName) + } + + scaleFactor := float64(blockchain.WitnessScaleFactor) + satPerKw := uint64((satPerVb*1000 + scaleFactor - 1) / scaleFactor) + + return satPerKw, nil +} + // checkNotBothSet accepts two flag names, a and b, and checks that only flag a // or flag b can be set, but not both. It returns the name of the flag or an // error. diff --git a/cmd/commands/walletrpc_active.go b/cmd/commands/walletrpc_active.go index dcb91a71ff5..cb71fece3d6 100644 --- a/cmd/commands/walletrpc_active.go +++ b/cmd/commands/walletrpc_active.go @@ -285,7 +285,7 @@ var bumpFeeCommand = cli.Command{ Usage: "Deprecated, use immediate instead.", Hidden: true, }, - cli.Uint64Flag{ + cli.Float64Flag{ Name: "sat_per_vbyte", Usage: ` The starting fee rate, expressed in sat/vbyte, that will be used to @@ -354,13 +354,19 @@ func bumpFee(ctx *cli.Context) error { immediate = true } + // Parse fee rate from --sat_per_vbyte and convert to sat/kw. + satPerKw, err := parseFeeRate(ctx, "sat_per_vbyte") + if err != nil { + return err + } + resp, err := client.BumpFee(ctxc, &walletrpc.BumpFeeRequest{ Outpoint: protoOutPoint, TargetConf: uint32(ctx.Uint64("conf_target")), Immediate: immediate, Budget: ctx.Uint64("budget"), - SatPerVbyte: ctx.Uint64("sat_per_vbyte"), DeadlineDelta: uint32(ctx.Uint64("deadline_delta")), + SatPerKw: satPerKw, }) if err != nil { return err diff --git a/cmd/commands/walletrpc_types.go b/cmd/commands/walletrpc_types.go index f3a025c393f..e3dd3305c57 100644 --- a/cmd/commands/walletrpc_types.go +++ b/cmd/commands/walletrpc_types.go @@ -16,8 +16,10 @@ type PendingSweep struct { WitnessType string `json:"witness_type"` AmountSat uint32 `json:"amount_sat"` SatPerVByte uint32 `json:"sat_per_vbyte"` + SatPerKw uint64 `json:"sat_per_kw"` BroadcastAttempts uint32 `json:"broadcast_attempts"` RequestedSatPerVByte uint32 `json:"requested_sat_per_vbyte"` + RequestedSatPerKw uint64 `json:"requested_sat_per_kw"` Immediate bool `json:"immediate"` Budget uint64 `json:"budget"` DeadlineHeight uint32 `json:"deadline_height"` @@ -38,8 +40,10 @@ func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSwee WitnessType: pendingSweep.WitnessType.String(), AmountSat: pendingSweep.AmountSat, SatPerVByte: uint32(pendingSweep.SatPerVbyte), + SatPerKw: pendingSweep.SatPerKw, BroadcastAttempts: pendingSweep.BroadcastAttempts, RequestedSatPerVByte: uint32(pendingSweep.RequestedSatPerVbyte), + RequestedSatPerKw: pendingSweep.RequestedSatPerKw, Immediate: pendingSweep.Immediate, Budget: pendingSweep.Budget, DeadlineHeight: pendingSweep.DeadlineHeight, From 37e7e176a5a85cc62fadd57724706a65cd6ace0d Mon Sep 17 00:00:00 2001 From: ziggie Date: Thu, 23 Feb 2023 11:47:24 +0100 Subject: [PATCH 4/8] funding: add the option sat_per_kw for batch channel openings. --- funding/batch.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/funding/batch.go b/funding/batch.go index d95941e8476..a09f5713b11 100644 --- a/funding/batch.go +++ b/funding/batch.go @@ -233,6 +233,7 @@ func (b *Batcher) BatchFund(ctx context.Context, //nolint:ll fundingReq, err := b.cfg.RequestParser(&lnrpc.OpenChannelRequest{ SatPerVbyte: uint64(req.SatPerVbyte), + SatPerKw: req.SatPerKw, TargetConf: req.TargetConf, MinConfs: req.MinConfs, SpendUnconfirmed: req.SpendUnconfirmed, @@ -331,14 +332,14 @@ func (b *Batcher) BatchFund(ctx context.Context, // settings from the first request as all of them should be equal // anyway. firstReq := b.channels[0].fundingReq - feeRateSatPerVByte := firstReq.FundingFeePerKw.FeePerVByte() + feeRateSatPerKw := firstReq.FundingFeePerKw changeType := walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR fundPsbtReq := &walletrpc.FundPsbtRequest{ Template: &walletrpc.FundPsbtRequest_Raw{ Raw: txTemplate, }, - Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{ - SatPerVbyte: uint64(feeRateSatPerVByte), + Fees: &walletrpc.FundPsbtRequest_SatPerKw{ + SatPerKw: uint64(feeRateSatPerKw), }, MinConfs: firstReq.MinConfs, SpendUnconfirmed: firstReq.MinConfs == 0, From 3dec5113299b214a6b40d75cacb68143c6231a4d Mon Sep 17 00:00:00 2001 From: MPins Date: Sun, 31 Aug 2025 10:20:41 -0700 Subject: [PATCH 5/8] itest+lntest: add tests for sat/kw option Add new itests for openchannel, sendcoins and sendmany. --- itest/list_on_test.go | 12 +++ itest/lnd_onchain_test.go | 135 +++++++++++++++++++++++++++++++++ itest/lnd_open_channel_test.go | 63 +++++++++++++++ lntest/harness.go | 32 +++++++- lntest/rpc/lnd.go | 24 ++++++ 5 files changed, 265 insertions(+), 1 deletion(-) diff --git a/itest/list_on_test.go b/itest/list_on_test.go index c8e4244e7e3..c2a8ec65245 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -651,6 +651,18 @@ var allTestCases = []*lntest.TestCase{ Name: "route blinding dummy hops", TestFunc: testBlindedRouteDummyHops, }, + { + Name: "openchannel feerate", + TestFunc: testOpenChannelFeerate, + }, + { + Name: "sendcoins feerate", + TestFunc: testSendCoinsFeerate, + }, + { + Name: "sendmany feerate", + TestFunc: testSendManyFeerate, + }, { Name: "removetx", TestFunc: testRemoveTx, diff --git a/itest/lnd_onchain_test.go b/itest/lnd_onchain_test.go index d5d0a19f28e..1db93d4b63b 100644 --- a/itest/lnd_onchain_test.go +++ b/itest/lnd_onchain_test.go @@ -898,3 +898,138 @@ func testListSweeps(ht *lntest.HarnessTest) { } ht.AssertNumSweeps(alice, req4, 0) } + +// testSendCoinsFeerate tests that we can create transaction with a specified +// feerate in either sats_per_vbyte and sats_per_kweight. +func testSendCoinsFeerate(ht *lntest.HarnessTest) { + alice := ht.NewNode("Alice", nil) + // Send Alice enough coins to be able to call SendCoins. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + minerAddr := alice.RPC.NewAddress( + &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_TAPROOT_PUBKEY, + }, + ).Address + + sweepReq := &lnrpc.SendCoinsRequest{ + Addr: minerAddr, + SatPerVbyte: 2, + Amount: 10001, + } + _ = alice.RPC.SendCoins(sweepReq) + + // We'll mine a block which should include the sweep transaction we + // generated above. + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] + sweepTx := block.Transactions[1].TxHash() + + // Calculate the feerate of the funding transaction in sat_per_vbyte. + rawTx := ht.Miner().GetRawTransaction(sweepTx) + feerate := ht.CalculateFeeRateSatPerVByte(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.Equal(ht, int64(sweepReq.SatPerVbyte), feerate) + + // Send coins with a defined feerate in sat_per_kw. + sweepReq = &lnrpc.SendCoinsRequest{ + Addr: minerAddr, + SatPerKw: 5000, + Amount: 10002, + } + _ = alice.RPC.SendCoins(sweepReq) + + // We'll mine a block which should include the sweep transaction we + // generated above. + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] + sweepTx = block.Transactions[1].TxHash() + + // Calculate the feerate of the funding transaction in sat_per_kw. + rawTx = ht.Miner().GetRawTransaction(sweepTx) + feerate = ht.CalculateFeeRateSatPerKWeight(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.InEpsilon(ht, int64(sweepReq.SatPerKw), feerate, 0.01) + + // Try sending coins with both feerate options sat_per_kw and + // sat_per_vbyte specified. + sweepReq = &lnrpc.SendCoinsRequest{ + Addr: minerAddr, + SatPerKw: 500, + SatPerVbyte: 2, + SendAll: true, + } + + // This should fail because only one feerate option makes sense. + err := alice.RPC.SendCoinsAssertErr(sweepReq) + require.Error(ht, err, "either SatPerKW or SatPerVByte should "+ + "be set, but not both") +} + +// testSendManyFeerate tests that we can create transaction with many outputs +// with a specified feerate in either sats_per_vbyte and sats_per_kweight. +func testSendManyFeerate(ht *lntest.HarnessTest) { + alice := ht.NewNode("Alice", nil) + // Send Alice enough coins to be able to call SendCoins. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + addr1 := ht.Miner().NewMinerAddress().String() + addr2 := ht.Miner().NewMinerAddress().String() + + addrAmtMap := map[string]int64{ + addr1: 50000, + addr2: 50000, + } + + // We create the transaction specifying the feerate in sats_per_vbyte. + sendManyReq := &lnrpc.SendManyRequest{ + AddrToAmount: addrAmtMap, + SatPerVbyte: 2, + } + _ = alice.RPC.SendMany(sendManyReq) + + // We'll mine a block which should include the sweep transaction we + // generated above. + block := ht.MineBlocksAndAssertNumTxes(1, 1)[0] + sweepTx := block.Transactions[1].TxHash() + + // Calculate the feerate of the funding transaction in sat_per_vbyte. + rawTx := ht.Miner().GetRawTransaction(sweepTx) + feerate := ht.CalculateFeeRateSatPerVByte(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.InEpsilon(ht, int64(sendManyReq.SatPerVbyte), feerate, 0.01) + + // We create the transaction specifying the feerate in + // sats_per_kweight. + sendManyReq = &lnrpc.SendManyRequest{ + AddrToAmount: addrAmtMap, + SatPerKw: 2000, + } + _ = alice.RPC.SendMany(sendManyReq) + + // We'll mine a block which should include the sweep transaction we + // generated above. + block = ht.MineBlocksAndAssertNumTxes(1, 1)[0] + sweepTx = block.Transactions[1].TxHash() + + // Calculate the feerate of the funding transaction in sats_per_kweight. + rawTx = ht.Miner().GetRawTransaction(sweepTx) + feerate = ht.CalculateFeeRateSatPerKWeight(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.InEpsilon(ht, int64(sendManyReq.SatPerKw), feerate, 0.01) + + // Try sending coins with both feerate options sat_per_kw and + // sat_per_vbyte specified. + sendManyReq = &lnrpc.SendManyRequest{ + AddrToAmount: addrAmtMap, + SatPerKw: 2000, + SatPerVbyte: 3, + } + + // This should fail because only one feerate option makes sense. + alice.RPC.SendManyAssertErr(sendManyReq) +} diff --git a/itest/lnd_open_channel_test.go b/itest/lnd_open_channel_test.go index 15b0bd70d8f..5c3272324e5 100644 --- a/itest/lnd_open_channel_test.go +++ b/itest/lnd_open_channel_test.go @@ -1497,3 +1497,66 @@ func testChannelUpdateNotifications(ht *lntest.HarnessTest) { assertUpdates(bobSub, 2) expectNoMoreUpdates(bobSub) } + +// testOpenChannelFeerate tests that we can open a channel with a specified +// feerate either in sat_per_vbyte or in sat_per_kweight. +func testOpenChannelFeerate(ht *lntest.HarnessTest) { + alice, bob := ht.NewNode("Alice", nil), ht.NewNode("Bob", nil) + + // Ensure Alice is connected to Bob. + ht.EnsureConnected(alice, bob) + + // Send Alice enough coins to be able to call OpenChannel. + ht.FundCoins(btcutil.SatoshiPerBitcoin, alice) + + chanAmt := funding.MaxBtcFundingAmount / 3 + + // First we test opening a channel by specifying the feerate in + // sat_per_kweight + params := lntest.OpenChannelParams{ + Amt: chanAmt, + SatPerKWeight: 500, + } + + // Create a channel Alice->Bob. + chanPoint := ht.OpenChannel(alice, bob, params) + defer ht.CloseChannel(alice, chanPoint) + + // Calculate the feerate of the funding transaction. + fundingTxID := ht.GetChanPointFundingTxid(chanPoint) + rawTx := ht.Miner().GetRawTransaction(fundingTxID) + feerate := ht.CalculateFeeRateSatPerKWeight(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.InEpsilon(ht, int64(params.SatPerKWeight), feerate, 0.01) + + // Now we open another channel with the feerate sat_per_vbyte + params = lntest.OpenChannelParams{ + Amt: chanAmt, + SatPerVByte: 100, + } + + // Create a channel Alice->Bob. + chanPoint = ht.OpenChannel(alice, bob, params) + defer ht.CloseChannel(alice, chanPoint) + + // Calculate the feerate of the funding transaction. + fundingTxID = ht.GetChanPointFundingTxid(chanPoint) + rawTx = ht.Miner().GetRawTransaction(fundingTxID) + feerate = ht.CalculateFeeRateSatPerVByte(rawTx.MsgTx()) + + // Allow some deviation because weight estimates during tx generation + // are estimates (ECDSA signature size estimate). + require.InEpsilon(ht, int64(params.SatPerVByte), feerate, 0.01) + + // Test when specififying both feerate options we should fail. + params = lntest.OpenChannelParams{ + Amt: chanAmt, + SatPerVByte: 2, + SatPerKWeight: 500, + } + + ht.OpenChannelAssertErr(alice, bob, params, fmt.Errorf("only one fee "+ + "field may be set")) +} diff --git a/lntest/harness.go b/lntest/harness.go index 08c62a984b1..59e104be430 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -938,6 +938,10 @@ type OpenChannelParams struct { // should be confirmed in. ConfTarget fn.Option[int32] + // SatPerKWeight is the amount of satoshis to spend in chain fees per + // KWeight of the transaction. + SatPerKWeight btcutil.Amount + // CommitmentType is the commitment type that should be used for the // channel to be opened. CommitmentType lnrpc.CommitmentType @@ -1016,7 +1020,7 @@ func (h *HarnessTest) prepareOpenChannel(srcNode, destNode *node.HarnessNode, confTarget := p.ConfTarget.UnwrapOr(6) // If there's fee rate set, unset the conf target. - if p.SatPerVByte != 0 { + if p.SatPerVByte != 0 || p.SatPerKWeight != 0 { confTarget = 0 } @@ -1033,6 +1037,7 @@ func (h *HarnessTest) prepareOpenChannel(srcNode, destNode *node.HarnessNode, RemoteMaxHtlcs: uint32(p.RemoteMaxHtlcs), FundingShim: p.FundingShim, SatPerVbyte: uint64(p.SatPerVByte), + SatPerKw: uint64(p.SatPerKWeight), CommitmentType: p.CommitmentType, ZeroConf: p.ZeroConf, ScidAlias: p.ScidAlias, @@ -2087,6 +2092,31 @@ func (h *HarnessTest) CalculateTxesFeeRate(txns []*wire.MsgTx) int64 { return feeRate } +// CalculateFeeRateSatPerVByte calculates the weight of a transaction in +// sat_per_vbyte. +func (h *HarnessTest) CalculateFeeRateSatPerVByte(tx *wire.MsgTx) int64 { + weight := h.CalculateTxWeight(tx) + // always rounding up because there are no decimal vbytes. + virtualSize := int64(weight+3) / 4 + txFee := int64(h.CalculateTxFee(tx)) + + return txFee / virtualSize +} + +// CalculateFeeRateSatPerKWeight calculates the weight of a transaction in +// sat_per_kweight. +func (h *HarnessTest) CalculateFeeRateSatPerKWeight(tx *wire.MsgTx) int64 { + weight := int64(h.CalculateTxWeight(tx)) + txFee := int64(h.CalculateTxFee(tx)) + + return txFee * 1000 / weight +} + +type SweptOutput struct { + OutPoint wire.OutPoint + SweepTx *wire.MsgTx +} + // AssertSweepFound looks up a sweep in a nodes list of broadcast sweeps and // asserts it's found. // diff --git a/lntest/rpc/lnd.go b/lntest/rpc/lnd.go index 946b37b8817..4e652676d36 100644 --- a/lntest/rpc/lnd.go +++ b/lntest/rpc/lnd.go @@ -459,6 +459,30 @@ func (h *HarnessRPC) SendCoinsAssertErr(req *lnrpc.SendCoinsRequest) error { return err } +// SendMany sends a given amount of money to the specified address from the +// passed node. +func (h *HarnessRPC) SendMany( + req *lnrpc.SendManyRequest) *lnrpc.SendManyResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.SendMany(ctxt, req) + h.NoError(err, "SendMany") + + return resp +} + +// SendManyAssertErr sends a given amount of money to the specified address +// from the passed node and asserts an error has returned. +func (h *HarnessRPC) SendManyAssertErr(req *lnrpc.SendManyRequest) { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.LN.SendMany(ctxt, req) + require.Error(h, err, "node %s didn't not return an error", h.Name) +} + // GetTransactions makes a RPC call to GetTransactions and asserts. func (h *HarnessRPC) GetTransactions( req *lnrpc.GetTransactionsRequest) *lnrpc.TransactionDetails { From 795ab4a4aed4664757449f802f5f063e6bea6f7e Mon Sep 17 00:00:00 2001 From: MPins Date: Sun, 31 Aug 2025 10:22:48 -0700 Subject: [PATCH 6/8] itest: Fix the test "rbf coop close" Fix the test "rbf coop close" that was broken by a safety check added to prevent very high fees. --- itest/lnd_coop_close_rbf_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/itest/lnd_coop_close_rbf_test.go b/itest/lnd_coop_close_rbf_test.go index f1acf0288f1..4b7229d19b2 100644 --- a/itest/lnd_coop_close_rbf_test.go +++ b/itest/lnd_coop_close_rbf_test.go @@ -100,8 +100,9 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, _, err = st.ReceiveCloseChannelUpdate(bobCloseStream) require.NoError(st, err) - // We'll now attempt a fee update that we can't actually pay for. This - // will actually show up as an error to the remote party. + // We'll now attempt a fee update greater than DefaultMaxFeeRate. It + // will be capped at DefaultMaxFeeRate, but we still can't afford it. + // This will result in an error shown to the remote party. aliceRejectedFeeRate = 100_000 _, _ = st.CloseChannelAssertPending( alice, chanPoint, false, @@ -182,8 +183,8 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) { lnrpc.CommitmentType_SIMPLE_TAPROOT params := lntest.OpenChannelParams{ - Amt: btcutil.Amount(1000000), - PushAmt: btcutil.Amount(1000000 / 2), + Amt: btcutil.Amount(100000), + PushAmt: btcutil.Amount(100000 / 2), CommitmentType: chanType.commitType, Private: isTaproot, } From ce6ce525c7a240b59b991408ccc7a13cf0823056 Mon Sep 17 00:00:00 2001 From: MPins Date: Tue, 16 Sep 2025 07:21:56 -0700 Subject: [PATCH 7/8] multi: change rbf coop close from sat/vb to sat/kw Change RBF cooperative close from sat/vB to sat/kw, along with the minimal changes needed for existing tests. --- itest/lnd_coop_close_rbf_test.go | 70 ++++++++++----------- lnrpc/lightning.pb.go | 14 ++++- lnrpc/lightning.proto | 1 + lnrpc/lightning.swagger.json | 4 ++ lntest/harness.go | 12 ++-- lnwallet/chancloser/rbf_coop_states.go | 14 ++--- lnwallet/chancloser/rbf_coop_test.go | 28 ++++----- lnwallet/chancloser/rbf_coop_transitions.go | 16 +++-- peer/brontide.go | 14 ++--- peer/musig_nonce_order_test.go | 2 +- rpcserver.go | 4 +- 11 files changed, 99 insertions(+), 80 deletions(-) diff --git a/itest/lnd_coop_close_rbf_test.go b/itest/lnd_coop_close_rbf_test.go index 4b7229d19b2..43a00289bae 100644 --- a/itest/lnd_coop_close_rbf_test.go +++ b/itest/lnd_coop_close_rbf_test.go @@ -21,19 +21,20 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, chanPoint *lnrpc.ChannelPoint, isTaproot bool) { // To start, we'll have Alice try to close the channel, with a fee rate - // of 5 sat/byte. - aliceFeeRate := chainfee.SatPerVByte(5) + // of 1250 sat/kw . + aliceFeeRate := chainfee.SatPerKWeight(1250) aliceCloseStream, aliceCloseUpdate := st.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceFeeRate), lntest.WithLocalTxNotify(), ) - // Confirm that this new update was at 5 sat/vb. + // Confirm that this new update was at 1250 sat/kw. alicePendingUpdate := aliceCloseUpdate.GetClosePending() require.NotNil(st, aliceCloseUpdate) require.Equal( - st, int64(aliceFeeRate), alicePendingUpdate.FeePerVbyte, + st, int64(aliceFeeRate), + int64(alicePendingUpdate.FeePerKw), ) require.True(st, alicePendingUpdate.LocalCloseTx) @@ -45,45 +46,41 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, lntest.WithLocalTxNotify(), ) - // Confirm that this new update was at 10 sat/vb. + // Confirm that this new update was at 2500 sat/kw. bobPendingUpdate := bobCloseUpdate.GetClosePending() require.NotNil(st, bobCloseUpdate) - require.Equal(st, bobPendingUpdate.FeePerVbyte, int64(bobFeeRate)) + require.Equal( + st, int64(bobPendingUpdate.FeePerKw), + int64(bobFeeRate), + ) require.True(st, bobPendingUpdate.LocalCloseTx) var err error // Alice should've also received a similar update that Bob has - // increased the closing fee rate to 10 sat/vb with his settled funds. + // increased the closing fee rate to 2500 sat/kw with his settled + // funds. aliceCloseUpdate, err = st.ReceiveCloseChannelUpdate(aliceCloseStream) require.NoError(st, err) alicePendingUpdate = aliceCloseUpdate.GetClosePending() require.NotNil(st, aliceCloseUpdate) - // For taproot channels, due to different witness sizes, - // the fee per vbyte might be slightly different due to + // The fee per kweight might be slightly different due to // rounding when converting between absolute fee and fee - // per vbyte. - if isTaproot { - // Allow for a small difference in fee - // calculation for taproot. - require.InDelta( - st, int64(bobFeeRate), - alicePendingUpdate.FeePerVbyte, 1, - ) - } else { - require.Equal( - st, alicePendingUpdate.FeePerVbyte, - int64(bobFeeRate), - ) - } + // per kweight. + require.InDelta( + st, alicePendingUpdate.FeePerKw, + int64(bobFeeRate), + 15, + ) + require.False(st, alicePendingUpdate.LocalCloseTx) // We'll now attempt to make a fee update that increases Alice's fee - // rate by 6 sat/vb, which should be rejected as it is too small of an - // increase for the RBF rules. The RPC API however will return the new - // fee. We'll skip the mempool check here as it won't make it in. - aliceRejectedFeeRate := aliceFeeRate + 1 + // rate by 1500 sat/kw, which should be rejected as it is too small of + // an increase for the RBF rules. The RPC API however will return the + // new fee. We'll skip the mempool check here as it won't make it in. + aliceRejectedFeeRate := aliceFeeRate + 250 _, aliceCloseUpdate = st.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(aliceRejectedFeeRate), @@ -92,7 +89,7 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, alicePendingUpdate = aliceCloseUpdate.GetClosePending() require.NotNil(st, aliceCloseUpdate) require.Equal( - st, alicePendingUpdate.FeePerVbyte, + st, int64(alicePendingUpdate.FeePerKw), int64(aliceRejectedFeeRate), ) require.True(st, alicePendingUpdate.LocalCloseTx) @@ -116,7 +113,7 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, st.DisconnectNodes(alice, bob) st.ConnectNodes(alice, bob) - // Next, we'll have Alice double that fee rate again to 20 sat/vb. + // Next, we'll have Alice double that fee rate again to 5000 sat/kw. aliceFeeRate = bobFeeRate * 2 aliceCloseStream, aliceCloseUpdate = st.CloseChannelAssertPending( alice, chanPoint, false, @@ -127,7 +124,8 @@ func runRbfCoopCloseTest(st *lntest.HarnessTest, alicePendingUpdate = aliceCloseUpdate.GetClosePending() require.NotNil(st, aliceCloseUpdate) require.Equal( - st, alicePendingUpdate.FeePerVbyte, int64(aliceFeeRate), + st, int64(alicePendingUpdate.FeePerKw), + int64(aliceFeeRate), ) require.True(st, alicePendingUpdate.LocalCloseTx) @@ -183,8 +181,8 @@ func testCoopCloseRbf(ht *lntest.HarnessTest) { lnrpc.CommitmentType_SIMPLE_TAPROOT params := lntest.OpenChannelParams{ - Amt: btcutil.Amount(100000), - PushAmt: btcutil.Amount(100000 / 2), + Amt: btcutil.Amount(100_000), + PushAmt: btcutil.Amount(100_000 / 2), CommitmentType: chanType.commitType, Private: isTaproot, } @@ -260,8 +258,8 @@ func testCoopCloseRBFWithReorg(ht *lntest.HarnessTest) { alice, bob := nodes[0], nodes[1] chanPoint := chanPoints[0] - // Initiate cooperative close with initial fee rate of 5 sat/vb. - initialFeeRate := chainfee.SatPerVByte(5) + // Initiate cooperative close with initial fee rate of 1250 sat/kw. + initialFeeRate := chainfee.SatPerKWeight(1250) _, aliceCloseUpdate := ht.CloseChannelAssertPending( alice, chanPoint, false, lntest.WithCoopCloseFeeRate(initialFeeRate), @@ -272,7 +270,7 @@ func testCoopCloseRBFWithReorg(ht *lntest.HarnessTest) { alicePendingUpdate := aliceCloseUpdate.GetClosePending() require.NotNil(ht, aliceCloseUpdate) require.Equal( - ht, int64(initialFeeRate), alicePendingUpdate.FeePerVbyte, + ht, int64(initialFeeRate), int64(alicePendingUpdate.FeePerKw), ) // Capture the initial close transaction from the mempool. @@ -281,7 +279,7 @@ func testCoopCloseRBFWithReorg(ht *lntest.HarnessTest) { initialCloseTx := ht.AssertTxInMempool(*initialCloseTxid) // Create first RBF replacement before any mining. - firstRbfFeeRate := chainfee.SatPerVByte(10) + firstRbfFeeRate := chainfee.SatPerKWeight(2500) _, firstRbfUpdate := ht.CloseChannelAssertPending( bob, chanPoint, false, lntest.WithCoopCloseFeeRate(firstRbfFeeRate), diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index f4aa22be09b..cc8b6fc5573 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -7419,6 +7419,7 @@ type PendingUpdate struct { OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` FeePerVbyte int64 `protobuf:"varint,3,opt,name=fee_per_vbyte,json=feePerVbyte,proto3" json:"fee_per_vbyte,omitempty"` LocalCloseTx bool `protobuf:"varint,4,opt,name=local_close_tx,json=localCloseTx,proto3" json:"local_close_tx,omitempty"` + FeePerKw uint64 `protobuf:"varint,5,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7481,6 +7482,13 @@ func (x *PendingUpdate) GetLocalCloseTx() bool { return false } +func (x *PendingUpdate) GetFeePerKw() uint64 { + if x != nil { + return x.FeePerKw + } + return 0 +} + type InstantUpdate struct { state protoimpl.MessageState `protogen:"open.v1"` // The number of pending HTLCs that are currently active on the channel. @@ -19454,12 +19462,14 @@ const file_lightning_proto_rawDesc = "" + "\n" + "chan_close\x18\x03 \x01(\v2\x19.lnrpc.ChannelCloseUpdateH\x00R\tchanClose\x12;\n" + "\rclose_instant\x18\x04 \x01(\v2\x14.lnrpc.InstantUpdateH\x00R\fcloseInstantB\b\n" + - "\x06update\"\x90\x01\n" + + "\x06update\"\xae\x01\n" + "\rPendingUpdate\x12\x12\n" + "\x04txid\x18\x01 \x01(\fR\x04txid\x12!\n" + "\foutput_index\x18\x02 \x01(\rR\voutputIndex\x12\"\n" + "\rfee_per_vbyte\x18\x03 \x01(\x03R\vfeePerVbyte\x12$\n" + - "\x0elocal_close_tx\x18\x04 \x01(\bR\flocalCloseTx\";\n" + + "\x0elocal_close_tx\x18\x04 \x01(\bR\flocalCloseTx\x12\x1c\n" + + "\n" + + "fee_per_kw\x18\x05 \x01(\x04R\bfeePerKw\";\n" + "\rInstantUpdate\x12*\n" + "\x11num_pending_htlcs\x18\x01 \x01(\x05R\x0fnumPendingHtlcs\"y\n" + "\x13ReadyForPsbtFunding\x12'\n" + diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 7be1149816f..4cf9659c24a 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -2316,6 +2316,7 @@ message PendingUpdate { uint32 output_index = 2; int64 fee_per_vbyte = 3; bool local_close_tx = 4; + uint64 fee_per_kw = 5; } message InstantUpdate { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index e0af7488e0e..3d1d3ef14b1 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -7242,6 +7242,10 @@ }, "local_close_tx": { "type": "boolean" + }, + "fee_per_kw": { + "type": "string", + "format": "uint64" } } }, diff --git a/lntest/harness.go b/lntest/harness.go index 59e104be430..30071fb420e 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -1232,7 +1232,7 @@ func (h *HarnessTest) OpenChannelAssertErr(srcNode, destNode *node.HarnessNode, // closeChannelOpts holds the options for closing a channel. type closeChannelOpts struct { - feeRate fn.Option[chainfee.SatPerVByte] + feeRate fn.Option[chainfee.SatPerKWeight] // localTxOnly is a boolean indicating if we should only attempt to // consume close pending notifications for the local transaction. @@ -1253,7 +1253,7 @@ type CloseChanOpt func(*closeChannelOpts) // WithCoopCloseFeeRate is a functional option to set the fee rate for a coop // close attempt. -func WithCoopCloseFeeRate(rate chainfee.SatPerVByte) CloseChanOpt { +func WithCoopCloseFeeRate(rate chainfee.SatPerKWeight) CloseChanOpt { return func(o *closeChannelOpts) { o.feeRate = fn.Some(rate) } @@ -1311,8 +1311,8 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, NoWait: true, } - closeOpts.feeRate.WhenSome(func(feeRate chainfee.SatPerVByte) { - closeReq.SatPerVbyte = uint64(feeRate) + closeOpts.feeRate.WhenSome(func(feeRate chainfee.SatPerKWeight) { + closeReq.SatPerKw = uint64(feeRate) }) var ( @@ -1353,9 +1353,9 @@ func (h *HarnessTest) CloseChannelAssertPending(hn *node.HarnessNode, continue } - notifyRate := pendingClose.ClosePending.FeePerVbyte + notifyRate := pendingClose.ClosePending.FeePerKw if closeOpts.localTxOnly && - notifyRate != int64(closeReq.SatPerVbyte) { + notifyRate != closeReq.SatPerKw { continue } diff --git a/lnwallet/chancloser/rbf_coop_states.go b/lnwallet/chancloser/rbf_coop_states.go index 61f7718b5a6..7009b8b4b84 100644 --- a/lnwallet/chancloser/rbf_coop_states.go +++ b/lnwallet/chancloser/rbf_coop_states.go @@ -105,7 +105,7 @@ type SendShutdown struct { // IdealFeeRate is the ideal fee rate we'd like to use for the closing // attempt. - IdealFeeRate chainfee.SatPerVByte + IdealFeeRate chainfee.SatPerKWeight // CloseeNonce is the nonce we'll send in the shutdown message. The // remote party will use this when they create their closing transaction @@ -197,7 +197,7 @@ func (c *ChannelFlushed) protocolSealed() {} // - toState: LocalOfferSent type SendOfferEvent struct { // TargetFeeRate is the fee rate we'll use for the closing transaction. - TargetFeeRate chainfee.SatPerVByte + TargetFeeRate chainfee.SatPerKWeight } // protocolSealed indicates that this struct is a ProtocolEvent instance. @@ -319,7 +319,7 @@ type Environment struct { // DefaultFeeRate is the fee we'll use for the closing transaction if // the user didn't specify an ideal fee rate. This may happen if the // remote party is the one that initiates the co-op close. - DefaultFeeRate chainfee.SatPerVByte + DefaultFeeRate chainfee.SatPerKWeight // ThawHeight is the height at which the channel will be thawed. If // this is None, then co-op close can occur at any moment. @@ -484,7 +484,7 @@ type ShutdownPending struct { // IdealFeeRate is the ideal fee rate we'd like to use for the closing // attempt. - IdealFeeRate fn.Option[chainfee.SatPerVByte] + IdealFeeRate fn.Option[chainfee.SatPerKWeight] // EarlyRemoteOffer is the offer we received from the remote party // before we received their shutdown message. We'll stash it to process @@ -533,7 +533,7 @@ type ChannelFlushing struct { // IdealFeeRate is the ideal fee rate we'd like to use for the closing // transaction. Once the channel has been flushed, we'll use this as // our target fee rate. - IdealFeeRate fn.Option[chainfee.SatPerVByte] + IdealFeeRate fn.Option[chainfee.SatPerKWeight] // NonceState tracks the nonces exchanged during shutdown for taproot // channels. @@ -788,7 +788,7 @@ type LocalOfferSent struct { ProposedFee btcutil.Amount // ProposedFeeRate is the fee rate we proposed to the remote party. - ProposedFeeRate chainfee.SatPerVByte + ProposedFeeRate chainfee.SatPerKWeight // LocalSig is the signature we sent to the remote party. LocalSig lnwire.Sig @@ -843,7 +843,7 @@ type ClosePending struct { *CloseChannelTerms // FeeRate is the fee rate of the closing transaction. - FeeRate chainfee.SatPerVByte + FeeRate chainfee.SatPerKWeight // Party indicates which party is at this state. This is used to // implement the state transition properly, based on ShouldRouteTo. diff --git a/lnwallet/chancloser/rbf_coop_test.go b/lnwallet/chancloser/rbf_coop_test.go index e8bbcc3f40c..e467fe08df7 100644 --- a/lnwallet/chancloser/rbf_coop_test.go +++ b/lnwallet/chancloser/rbf_coop_test.go @@ -988,7 +988,7 @@ func newRbfCloserTestHarness(t *testing.T, ChanPoint: chanPoint, ChanID: chanID, Scid: scid, - DefaultFeeRate: defaultFeeRate.FeePerVByte(), + DefaultFeeRate: defaultFeeRate, ThawHeight: cfg.thawHeight, RemoteUpfrontShutdown: cfg.remoteUpfrontAddr, LocalUpfrontShutdown: cfg.localUpfrontAddr, @@ -1069,7 +1069,7 @@ func testInitiatorShutdownRecvOkNonTap(t *testing.T, ctx context.Context, t.Run("non_taproot", func(t *testing.T) { firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1128,7 +1128,7 @@ func testInitiatorShutdownRecvOkTaproot(t *testing.T, ctx context.Context, t.Run("taproot", func(t *testing.T) { firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1377,7 +1377,7 @@ func TestRbfChannelActiveTransitions(t *testing.T) { localAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x01}, 20)) remoteAddr := lnwire.DeliveryAddress(bytes.Repeat([]byte{0x02}, 20)) - feeRate := chainfee.SatPerVByte(1000) + feeRate := chainfee.SatPerKWeight(250000) // Test that if a spend event is received, the FSM transitions to the // CloseFin terminal state. @@ -1580,7 +1580,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { t.Run("initiator_shutdown_recv_taproot_no_nonce_fail", func(t *testing.T) { //nolint:ll firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1632,7 +1632,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { t.Run("responder_complete", func(t *testing.T) { firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1663,7 +1663,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { t.Run("early_remote_offer_shutdown_complete", func(t *testing.T) { firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1710,7 +1710,7 @@ func TestRbfShutdownPendingTransitions(t *testing.T) { t.Run("early_remote_offer_shutdown_received", func(t *testing.T) { firstState := *startingState firstState.IdealFeeRate = fn.Some( - chainfee.FeePerKwFloor.FeePerVByte(), + chainfee.FeePerKwFloor, ) firstState.ShutdownScripts = ShutdownScripts{ LocalDeliveryScript: localAddr, @@ -1970,7 +1970,7 @@ func testSendOfferRbfIterationLoopNonTap(t *testing.T, noDustExpect, false, ) - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10 + rbfFeeBump := chainfee.FeePerKwFloor * 10 localOffer := &SendOfferEvent{ TargetFeeRate: rbfFeeBump, } @@ -2033,7 +2033,7 @@ func testSendOfferRbfIterationLoopTaproot(t *testing.T, lnwire.Musig2Nonce{7, 8, 9}, ) - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() * 10 + rbfFeeBump := chainfee.FeePerKwFloor * 10 localOffer := &SendOfferEvent{ TargetFeeRate: rbfFeeBump, } @@ -2331,7 +2331,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { } sendOfferEvent := &SendOfferEvent{ - TargetFeeRate: chainfee.FeePerKwFloor.FeePerVByte(), + TargetFeeRate: chainfee.FeePerKwFloor, } balanceAfterClose := localBalance.ToSatoshis() - absoluteFee @@ -2519,7 +2519,7 @@ func TestRbfCloseClosingNegotiationLocal(t *testing.T) { // the amount we have in the channel. closeHarness.expectFeeEstimate(btcutil.SatoshiPerBitcoin, 1) - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() + rbfFeeBump := chainfee.FeePerKwFloor localOffer := &SendOfferEvent{ TargetFeeRate: rbfFeeBump, } @@ -2960,7 +2960,7 @@ func TestRbfCloseErr(t *testing.T) { }) defer closeHarness.stopAndAssert() - rbfFeeBump := chainfee.FeePerKwFloor.FeePerVByte() + rbfFeeBump := chainfee.FeePerKwFloor localOffer := &SendOfferEvent{ TargetFeeRate: rbfFeeBump, } @@ -3293,7 +3293,7 @@ func TestLocalOfferSentUsesStoredSig(t *testing.T) { localOfferSent := &LocalOfferSent{ CloseChannelTerms: closeTerms, ProposedFee: btcutil.Amount(1000), - ProposedFeeRate: chainfee.FeePerKwFloor.FeePerVByte(), + ProposedFeeRate: chainfee.FeePerKwFloor, LocalSig: localSchnorrSig, LocalMusigSig: fn.Some(lnwallet.MusigPartialSig{}), } diff --git a/lnwallet/chancloser/rbf_coop_transitions.go b/lnwallet/chancloser/rbf_coop_transitions.go index eb8b1d67968..a4d741fd4e0 100644 --- a/lnwallet/chancloser/rbf_coop_transitions.go +++ b/lnwallet/chancloser/rbf_coop_transitions.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" @@ -607,7 +608,7 @@ func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment, localTxOut, remoteTxOut := closeTerms.DeriveCloseTxOuts() absoluteFee := env.FeeEstimator.EstimateFee( env.ChanType, localTxOut, remoteTxOut, - idealFeeRate.FeePerKWeight(), + idealFeeRate, ) chancloserLog.Infof("ChannelPoint(%v): using ideal_fee=%v, "+ @@ -1119,7 +1120,7 @@ func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, localTxOut, remoteTxOut := l.DeriveCloseTxOuts() absoluteFee := env.FeeEstimator.EstimateFee( env.ChanType, localTxOut, remoteTxOut, - msg.TargetFeeRate.FeePerKWeight(), + msg.TargetFeeRate, ) // If we can't actually pay for fees here, then we'll just do a @@ -2115,11 +2116,16 @@ func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment, // We'll also compute the final fee rate that the remote party // paid based off the absolute fee and the size of the closing // transaction. - vSize := mempool.GetTxVirtualSize(btcutil.NewTx(closeTx)) - feeRate := chainfee.SatPerVByte( - int64(msg.SigMsg.FeeSatoshis) / vSize, + weight := blockchain.GetTransactionWeight( + btcutil.NewTx(closeTx), ) + // Round to the next integer. + fee := int64(msg.SigMsg.FeeSatoshis) + rate := ((fee * 1000) + weight - 1) / weight + + feeRate := chainfee.SatPerKWeight(rate) + // Now that we've extracted the signature, we'll transition to // the next state where we'll sign+broadcast the sig. return &CloseStateTransition{ diff --git a/peer/brontide.go b/peer/brontide.go index 5ab4e8fa6bf..07da068e12b 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -157,10 +157,10 @@ type PendingUpdate struct { // transaction. OutputIndex uint32 - // FeePerVByte is an optional field, that is set only when the new RBF + // FeePerKw is an optional field, that is set only when the new RBF // coop close flow is used. This indicates the new closing fee rate on // the closing transaction. - FeePerVbyte fn.Option[chainfee.SatPerVByte] + FeePerKw fn.Option[chainfee.SatPerKWeight] // IsLocalCloseTx is an optional field that indicates if this update is // sent for our local close txn, or the close txn of the remote party. @@ -3835,7 +3835,7 @@ func (p *Brontide) observeRbfCloseUpdates(chanCloser *chancloser.RbfChanCloser, var ( lastTxids lntypes.Dual[chainhash.Hash] - lastFeeRates lntypes.Dual[chainfee.SatPerVByte] + lastFeeRates lntypes.Dual[chainfee.SatPerKWeight] ) maybeNotifyTxBroadcast := func(state chancloser.AsymmetricPeerState, @@ -3894,8 +3894,8 @@ func (p *Brontide) observeRbfCloseUpdates(chanCloser *chancloser.RbfChanCloser, if closeReq != nil && closingTxid != lastTxid { select { case closeReq.Updates <- &PendingUpdate{ - Txid: closingTxid[:], - FeePerVbyte: fn.Some(closePending.FeeRate), + Txid: closingTxid[:], + FeePerKw: fn.Some(closePending.FeeRate), IsLocalCloseTx: fn.Some( party == lntypes.Local, ), @@ -4163,7 +4163,7 @@ func (p *Brontide) initRbfChanCloser( ChanID: chanID, Scid: scid, ChanType: channel.ChanType(), - DefaultFeeRate: defaultFeePerKw.FeePerVByte(), + DefaultFeeRate: defaultFeePerKw, ThawHeight: fn.Some(thawHeight), RemoteUpfrontShutdown: ChooseAddr( channel.RemoteUpfrontShutdownScript(), @@ -4432,7 +4432,7 @@ func (p *Brontide) startRbfChanCloser(shutdown shutdownInit, } ctx, _ := p.cg.Create(context.Background()) - feeRate := defaultFeePerKw.FeePerVByte() + feeRate := defaultFeePerKw // Depending on the state of the state machine, we'll either // kick things off by sending shutdown, or attempt to send a new diff --git a/peer/musig_nonce_order_test.go b/peer/musig_nonce_order_test.go index 13918649c68..be877324c62 100644 --- a/peer/musig_nonce_order_test.go +++ b/peer/musig_nonce_order_test.go @@ -127,7 +127,7 @@ func TestRemoteCloseStartTaprootIntegration(t *testing.T) { aliceChan.ChannelPoint(), ), ChanType: chanType, - DefaultFeeRate: chainfee.SatPerVByte(10), + DefaultFeeRate: chainfee.SatPerKWeight(10 * 250), FeeEstimator: feeEstimator, ChanObserver: chanObserver, CloseSigner: closeSigner, diff --git a/rpcserver.go b/rpcserver.go index 5697c438449..dbf1cdd3470 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3214,8 +3214,8 @@ func createRPCCloseUpdate( u.IsLocalCloseTx.WhenSome(func(isLocal bool) { upd.LocalCloseTx = isLocal }) - u.FeePerVbyte.WhenSome(func(feeRate chainfee.SatPerVByte) { - upd.FeePerVbyte = int64(feeRate) + u.FeePerKw.WhenSome(func(feeRate chainfee.SatPerKWeight) { + upd.FeePerKw = uint64(feeRate) }) return &lnrpc.CloseStatusUpdate{ From 76d72cfc3077be8139119ffd02792a7cdfb2a5d5 Mon Sep 17 00:00:00 2001 From: MPins Date: Mon, 20 Oct 2025 13:06:18 -0700 Subject: [PATCH 8/8] docs: add release-notes --- docs/release-notes/release-notes-0.21.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index b8a37789fb8..123ad09a32d 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -168,6 +168,11 @@ specify a list of inputs to use as transaction inputs via the new `inputs` field in `EstimateFeeRequest`. +* [Add sat_per_kw option for more fine granular control of transaction + fees](https://github.com/lightningnetwork/lnd/pull/10067). This option is added for the sendcoins, sendmany, openchannel, batchopenchannel, + closechannel, closeallchannels and wallet bumpfee commands. Also add + max_fee_per_kw for closechannel command. + ## lncli Additions * The `estimatefee` command now supports the `--utxos` flag to specify explicit @@ -177,6 +182,11 @@ PSBT](https://github.com/lightningnetwork/lnd/pull/10659). +* The [--sat_per_vbyte](https://github.com/lightningnetwork/lnd/pull/10067) + option now supports fractional values (e.g. 1.05). + This option is added for the sendcoins, sendmany, openchannel, + batchopenchannel, closechannel, closeallchannels and wallet bumpfee commands. The max_fee_rate argument for closechannel also supports fractional values. + # Improvements ## Functional Updates @@ -322,6 +332,10 @@ | [`lnrpc.SendMany`](https://lightning.engineering/api-docs/api/lnd/lightning/send-many/) | [`lnrpc.SendManyRequest`](https://lightning.engineering/api-docs/api/lnd/lightning/send-many/#lnrpcsendmanyrequest) | sat_per_byte | [`walletrpc.BumpFee`](https://lightning.engineering/api-docs/api/lnd/wallet-kit/bump-fee/) | [`walletrpc.BumpFeeRequest`](walletrpc.BumpFeeRequest) | sat_per_byte +### ⚠️ **Warning:** The deprecated fee rate option --sat_per_byte will be removed in release version **0.22** + + The following RPCs will be impacted: sendcoins, sendmany, openchannel, closechannel, closeallchannels and wallet bumpfee. + # Technical and Architectural Updates ## BOLT Spec Updates