From f55c0565d02cff63760b055970d36eca82e2beb1 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 11 Jun 2026 13:35:22 -0700 Subject: [PATCH 1/4] lnwallet+walletrpc: add SubmitPackage for v3 CPFP package relay Add SubmitPackage to the lnwallet.WalletController interface and a new WalletKit.SubmitPackage RPC, so a client of lnd can relay a package of related transactions (parents first, child last) through lnd's own chain connection. This lets a zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child without the caller needing a separate connection to the chain backend. BtcWallet.SubmitPackage forwards to the chain backend's submitpackage for bitcoind/btcd, and broadcasts each transaction individually for neutrino (no mempool; relies on the peer's 1p1c package relay). The WalletKit handler maps the proto request/response to the btcjson result and is gated by the onchain:write macaroon permission. Mock controllers and the no-chain backend gain trivial implementations. --- chainreg/no_chain_backend.go | 9 + go.mod | 2 +- go.sum | 4 +- lnmock/chain.go | 13 + lnrpc/walletrpc/walletkit.pb.go | 638 +++++++++++++++-------- lnrpc/walletrpc/walletkit.pb.gw.go | 85 +++ lnrpc/walletrpc/walletkit.pb.json.go | 25 + lnrpc/walletrpc/walletkit.proto | 56 ++ lnrpc/walletrpc/walletkit.swagger.json | 91 ++++ lnrpc/walletrpc/walletkit.yaml | 3 + lnrpc/walletrpc/walletkit_grpc.pb.go | 58 +++ lnrpc/walletrpc/walletkit_server.go | 85 +++ lntest/mock/walletcontroller.go | 13 + lnwallet/btcwallet/btcwallet.go | 86 +++ lnwallet/btcwallet/submitpackage_test.go | 38 ++ lnwallet/interface.go | 14 + lnwallet/mock.go | 14 + 17 files changed, 1019 insertions(+), 215 deletions(-) create mode 100644 lnwallet/btcwallet/submitpackage_test.go diff --git a/chainreg/no_chain_backend.go b/chainreg/no_chain_backend.go index 02dfed5f9b5..5e555537461 100644 --- a/chainreg/no_chain_backend.go +++ b/chainreg/no_chain_backend.go @@ -221,6 +221,15 @@ func (n *NoChainSource) TestMempoolAccept([]*wire.MsgTx, return nil, nil } +// SubmitPackage is a stub implementation of the chain.Interface method for +// NoChainSource; there is no chain backend, so it always returns +// errNotImplemented. +func (n *NoChainSource) SubmitPackage([]*wire.MsgTx, + *float64) (*btcjson.SubmitPackageResult, error) { + + return nil, errNotImplemented +} + func (n *NoChainSource) MapRPCErr(err error) error { return err } diff --git a/go.mod b/go.mod index d17815d11bd..5eceb3a341c 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/btcsuite/btcd/wire/v2 v2.0.0 github.com/btcsuite/btclog v1.0.0 github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b - github.com/btcsuite/btcwallet v0.17.0 + github.com/btcsuite/btcwallet v0.18.0 github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0 github.com/btcsuite/btcwallet/wallet/txrules v1.3.0 github.com/btcsuite/btcwallet/walletdb v1.6.0 diff --git a/go.sum b/go.sum index 835874dfe26..45f40ca8e53 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= -github.com/btcsuite/btcwallet v0.17.0 h1:uDHH/4BLWMz3nEGmfVEPepcidKWq4CQ+ZfWY/w71PKk= -github.com/btcsuite/btcwallet v0.17.0/go.mod h1:1ZMc1EEskov+AKKv4kCMZqN8BwVh9rpXwEyxbeWy2A4= +github.com/btcsuite/btcwallet v0.18.0 h1:VSRClNLT7NX0wmJEGALz3jOZRRjWPpUdp7VI1Akie1o= +github.com/btcsuite/btcwallet v0.18.0/go.mod h1:1ZMc1EEskov+AKKv4kCMZqN8BwVh9rpXwEyxbeWy2A4= github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0 h1:oIkGj32YK1CvWaJGlVwZA1f+y/KVHkfrd2PoST0ZpQs= github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0/go.mod h1:sGrBjcqQ8UPexuRajFs72+o544CJn3Pavv/5H0VAWVk= github.com/btcsuite/btcwallet/wallet/txrules v1.3.0 h1:D5aGMwWIxdqek3xEJs4eOdMoh6iga2EI2xSlaXCdnNo= diff --git a/lnmock/chain.go b/lnmock/chain.go index cdc007d62ac..84fda0030e6 100644 --- a/lnmock/chain.go +++ b/lnmock/chain.go @@ -172,6 +172,19 @@ func (m *MockChain) TestMempoolAccept(txns []*wire.MsgTx, maxFeeRate float64) ( return args.Get(0).([]*btcjson.TestMempoolAcceptResult), args.Error(1) } +// SubmitPackage is a mock implementation of the chain.Interface method. +func (m *MockChain) SubmitPackage(txns []*wire.MsgTx, + maxFeeRate *float64) (*btcjson.SubmitPackageResult, error) { + + args := m.Called(txns, maxFeeRate) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*btcjson.SubmitPackageResult), args.Error(1) +} + func (m *MockChain) MapRPCErr(err error) error { args := m.Called(err) diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 347a38fd5d6..40e9506e3f3 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -2461,6 +2461,192 @@ func (x *PublishResponse) GetPublishError() string { return "" } +type SubmitPackageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The raw serialized transactions forming the package, topologically sorted + // with unconfirmed parents first and the child last. + RawTxs [][]byte `protobuf:"bytes,1,rep,name=raw_txs,json=rawTxs,proto3" json:"raw_txs,omitempty"` + // Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the + // submitpackage maxfeerate). When unset the node's default is used; an + // explicit 0 means no limit, which is required for a CPFP child whose + // standalone feerate is high. + SatPerVbyte *uint64 `protobuf:"varint,2,opt,name=sat_per_vbyte,json=satPerVbyte,proto3,oneof" json:"sat_per_vbyte,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPackageRequest) Reset() { + *x = SubmitPackageRequest{} + mi := &file_walletrpc_walletkit_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPackageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPackageRequest) ProtoMessage() {} + +func (x *SubmitPackageRequest) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPackageRequest.ProtoReflect.Descriptor instead. +func (*SubmitPackageRequest) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} +} + +func (x *SubmitPackageRequest) GetRawTxs() [][]byte { + if x != nil { + return x.RawTxs + } + return nil +} + +func (x *SubmitPackageRequest) GetSatPerVbyte() uint64 { + if x != nil && x.SatPerVbyte != nil { + return *x.SatPerVbyte + } + return 0 +} + +type SubmitPackageTxResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The transaction id (txid) in hex. + Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` + // If non-empty, the reason this transaction was rejected. + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + // If non-empty, the wtxid (in hex) of a transaction with the same txid but a + // different witness that was already in the mempool; the submitted + // transaction was ignored as a duplicate (witness replacement). + OtherWtxid string `protobuf:"bytes,3,opt,name=other_wtxid,json=otherWtxid,proto3" json:"other_wtxid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPackageTxResult) Reset() { + *x = SubmitPackageTxResult{} + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPackageTxResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPackageTxResult) ProtoMessage() {} + +func (x *SubmitPackageTxResult) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPackageTxResult.ProtoReflect.Descriptor instead. +func (*SubmitPackageTxResult) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} +} + +func (x *SubmitPackageTxResult) GetTxid() string { + if x != nil { + return x.Txid + } + return "" +} + +func (x *SubmitPackageTxResult) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *SubmitPackageTxResult) GetOtherWtxid() string { + if x != nil { + return x.OtherWtxid + } + return "" +} + +type SubmitPackageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // A summary message; "success" when the whole package was accepted. + PackageMsg string `protobuf:"bytes,1,opt,name=package_msg,json=packageMsg,proto3" json:"package_msg,omitempty"` + // Per-transaction results keyed by wtxid (hex). + TxResults map[string]*SubmitPackageTxResult `protobuf:"bytes,2,rep,name=tx_results,json=txResults,proto3" json:"tx_results,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // The txids of transactions evicted via package RBF. + ReplacedTransactions []string `protobuf:"bytes,3,rep,name=replaced_transactions,json=replacedTransactions,proto3" json:"replaced_transactions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPackageResponse) Reset() { + *x = SubmitPackageResponse{} + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPackageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPackageResponse) ProtoMessage() {} + +func (x *SubmitPackageResponse) ProtoReflect() protoreflect.Message { + mi := &file_walletrpc_walletkit_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPackageResponse.ProtoReflect.Descriptor instead. +func (*SubmitPackageResponse) Descriptor() ([]byte, []int) { + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} +} + +func (x *SubmitPackageResponse) GetPackageMsg() string { + if x != nil { + return x.PackageMsg + } + return "" +} + +func (x *SubmitPackageResponse) GetTxResults() map[string]*SubmitPackageTxResult { + if x != nil { + return x.TxResults + } + return nil +} + +func (x *SubmitPackageResponse) GetReplacedTransactions() []string { + if x != nil { + return x.ReplacedTransactions + } + return nil +} + type RemoveTransactionResponse struct { state protoimpl.MessageState `protogen:"open.v1"` // The status of the remove transaction operation. @@ -2471,7 +2657,7 @@ type RemoveTransactionResponse struct { func (x *RemoveTransactionResponse) Reset() { *x = RemoveTransactionResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[34] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2483,7 +2669,7 @@ func (x *RemoveTransactionResponse) String() string { func (*RemoveTransactionResponse) ProtoMessage() {} func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[34] + mi := &file_walletrpc_walletkit_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2496,7 +2682,7 @@ func (x *RemoveTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveTransactionResponse.ProtoReflect.Descriptor instead. func (*RemoveTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{34} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} } func (x *RemoveTransactionResponse) GetStatus() string { @@ -2528,7 +2714,7 @@ type SendOutputsRequest struct { func (x *SendOutputsRequest) Reset() { *x = SendOutputsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2540,7 +2726,7 @@ func (x *SendOutputsRequest) String() string { func (*SendOutputsRequest) ProtoMessage() {} func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[35] + mi := &file_walletrpc_walletkit_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2553,7 +2739,7 @@ func (x *SendOutputsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsRequest.ProtoReflect.Descriptor instead. func (*SendOutputsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{35} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} } func (x *SendOutputsRequest) GetSatPerKw() int64 { @@ -2608,7 +2794,7 @@ type SendOutputsResponse struct { func (x *SendOutputsResponse) Reset() { *x = SendOutputsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2620,7 +2806,7 @@ func (x *SendOutputsResponse) String() string { func (*SendOutputsResponse) ProtoMessage() {} func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[36] + mi := &file_walletrpc_walletkit_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2633,7 +2819,7 @@ func (x *SendOutputsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOutputsResponse.ProtoReflect.Descriptor instead. func (*SendOutputsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{36} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} } func (x *SendOutputsResponse) GetRawTx() []byte { @@ -2653,7 +2839,7 @@ type EstimateFeeRequest struct { func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2665,7 +2851,7 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[37] + mi := &file_walletrpc_walletkit_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2678,7 +2864,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{37} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} } func (x *EstimateFeeRequest) GetConfTarget() int32 { @@ -2701,7 +2887,7 @@ type EstimateFeeResponse struct { func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2713,7 +2899,7 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[38] + mi := &file_walletrpc_walletkit_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2726,7 +2912,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{38} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} } func (x *EstimateFeeResponse) GetSatPerKw() int64 { @@ -2806,7 +2992,7 @@ type PendingSweep struct { func (x *PendingSweep) Reset() { *x = PendingSweep{} - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2818,7 +3004,7 @@ func (x *PendingSweep) String() string { func (*PendingSweep) ProtoMessage() {} func (x *PendingSweep) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[39] + mi := &file_walletrpc_walletkit_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2831,7 +3017,7 @@ func (x *PendingSweep) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweep.ProtoReflect.Descriptor instead. func (*PendingSweep) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{39} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} } func (x *PendingSweep) GetOutpoint() *lnrpc.OutPoint { @@ -2952,7 +3138,7 @@ type PendingSweepsRequest struct { func (x *PendingSweepsRequest) Reset() { *x = PendingSweepsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[40] + mi := &file_walletrpc_walletkit_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2964,7 +3150,7 @@ func (x *PendingSweepsRequest) String() string { func (*PendingSweepsRequest) ProtoMessage() {} func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[40] + mi := &file_walletrpc_walletkit_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2977,7 +3163,7 @@ func (x *PendingSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsRequest.ProtoReflect.Descriptor instead. func (*PendingSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{40} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} } type PendingSweepsResponse struct { @@ -2990,7 +3176,7 @@ type PendingSweepsResponse struct { func (x *PendingSweepsResponse) Reset() { *x = PendingSweepsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[41] + mi := &file_walletrpc_walletkit_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3002,7 +3188,7 @@ func (x *PendingSweepsResponse) String() string { func (*PendingSweepsResponse) ProtoMessage() {} func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[41] + mi := &file_walletrpc_walletkit_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3015,7 +3201,7 @@ func (x *PendingSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingSweepsResponse.ProtoReflect.Descriptor instead. func (*PendingSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{41} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} } func (x *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { @@ -3070,7 +3256,7 @@ type BumpFeeRequest struct { func (x *BumpFeeRequest) Reset() { *x = BumpFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[42] + mi := &file_walletrpc_walletkit_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3082,7 +3268,7 @@ func (x *BumpFeeRequest) String() string { func (*BumpFeeRequest) ProtoMessage() {} func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[42] + mi := &file_walletrpc_walletkit_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3095,7 +3281,7 @@ func (x *BumpFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeRequest.ProtoReflect.Descriptor instead. func (*BumpFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{42} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} } func (x *BumpFeeRequest) GetOutpoint() *lnrpc.OutPoint { @@ -3166,7 +3352,7 @@ type BumpFeeResponse struct { func (x *BumpFeeResponse) Reset() { *x = BumpFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[43] + mi := &file_walletrpc_walletkit_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3178,7 +3364,7 @@ func (x *BumpFeeResponse) String() string { func (*BumpFeeResponse) ProtoMessage() {} func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[43] + mi := &file_walletrpc_walletkit_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3191,7 +3377,7 @@ func (x *BumpFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpFeeResponse.ProtoReflect.Descriptor instead. func (*BumpFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{43} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} } func (x *BumpFeeResponse) GetStatus() string { @@ -3235,7 +3421,7 @@ type BumpForceCloseFeeRequest struct { func (x *BumpForceCloseFeeRequest) Reset() { *x = BumpForceCloseFeeRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[44] + mi := &file_walletrpc_walletkit_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3247,7 +3433,7 @@ func (x *BumpForceCloseFeeRequest) String() string { func (*BumpForceCloseFeeRequest) ProtoMessage() {} func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[44] + mi := &file_walletrpc_walletkit_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3260,7 +3446,7 @@ func (x *BumpForceCloseFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeRequest.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{44} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} } func (x *BumpForceCloseFeeRequest) GetChanPoint() *lnrpc.ChannelPoint { @@ -3315,7 +3501,7 @@ type BumpForceCloseFeeResponse struct { func (x *BumpForceCloseFeeResponse) Reset() { *x = BumpForceCloseFeeResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[45] + mi := &file_walletrpc_walletkit_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3327,7 +3513,7 @@ func (x *BumpForceCloseFeeResponse) String() string { func (*BumpForceCloseFeeResponse) ProtoMessage() {} func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[45] + mi := &file_walletrpc_walletkit_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3340,7 +3526,7 @@ func (x *BumpForceCloseFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BumpForceCloseFeeResponse.ProtoReflect.Descriptor instead. func (*BumpForceCloseFeeResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{45} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} } func (x *BumpForceCloseFeeResponse) GetStatus() string { @@ -3366,7 +3552,7 @@ type ListSweepsRequest struct { func (x *ListSweepsRequest) Reset() { *x = ListSweepsRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[46] + mi := &file_walletrpc_walletkit_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3378,7 +3564,7 @@ func (x *ListSweepsRequest) String() string { func (*ListSweepsRequest) ProtoMessage() {} func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[46] + mi := &file_walletrpc_walletkit_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3391,7 +3577,7 @@ func (x *ListSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsRequest.ProtoReflect.Descriptor instead. func (*ListSweepsRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{46} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} } func (x *ListSweepsRequest) GetVerbose() bool { @@ -3421,7 +3607,7 @@ type ListSweepsResponse struct { func (x *ListSweepsResponse) Reset() { *x = ListSweepsResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[47] + mi := &file_walletrpc_walletkit_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3433,7 +3619,7 @@ func (x *ListSweepsResponse) String() string { func (*ListSweepsResponse) ProtoMessage() {} func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[47] + mi := &file_walletrpc_walletkit_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3446,7 +3632,7 @@ func (x *ListSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSweepsResponse.ProtoReflect.Descriptor instead. func (*ListSweepsResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} } func (x *ListSweepsResponse) GetSweeps() isListSweepsResponse_Sweeps { @@ -3505,7 +3691,7 @@ type LabelTransactionRequest struct { func (x *LabelTransactionRequest) Reset() { *x = LabelTransactionRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[48] + mi := &file_walletrpc_walletkit_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3517,7 +3703,7 @@ func (x *LabelTransactionRequest) String() string { func (*LabelTransactionRequest) ProtoMessage() {} func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[48] + mi := &file_walletrpc_walletkit_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3530,7 +3716,7 @@ func (x *LabelTransactionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionRequest.ProtoReflect.Descriptor instead. func (*LabelTransactionRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{48} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} } func (x *LabelTransactionRequest) GetTxid() []byte { @@ -3564,7 +3750,7 @@ type LabelTransactionResponse struct { func (x *LabelTransactionResponse) Reset() { *x = LabelTransactionResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[49] + mi := &file_walletrpc_walletkit_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3576,7 +3762,7 @@ func (x *LabelTransactionResponse) String() string { func (*LabelTransactionResponse) ProtoMessage() {} func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[49] + mi := &file_walletrpc_walletkit_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3589,7 +3775,7 @@ func (x *LabelTransactionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LabelTransactionResponse.ProtoReflect.Descriptor instead. func (*LabelTransactionResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{49} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} } func (x *LabelTransactionResponse) GetStatus() string { @@ -3644,7 +3830,7 @@ type FundPsbtRequest struct { func (x *FundPsbtRequest) Reset() { *x = FundPsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[50] + mi := &file_walletrpc_walletkit_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3656,7 +3842,7 @@ func (x *FundPsbtRequest) String() string { func (*FundPsbtRequest) ProtoMessage() {} func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[50] + mi := &file_walletrpc_walletkit_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3669,7 +3855,7 @@ func (x *FundPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtRequest.ProtoReflect.Descriptor instead. func (*FundPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} } func (x *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template { @@ -3886,7 +4072,7 @@ type FundPsbtResponse struct { func (x *FundPsbtResponse) Reset() { *x = FundPsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[51] + mi := &file_walletrpc_walletkit_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3898,7 +4084,7 @@ func (x *FundPsbtResponse) String() string { func (*FundPsbtResponse) ProtoMessage() {} func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[51] + mi := &file_walletrpc_walletkit_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3911,7 +4097,7 @@ func (x *FundPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FundPsbtResponse.ProtoReflect.Descriptor instead. func (*FundPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{51} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} } func (x *FundPsbtResponse) GetFundedPsbt() []byte { @@ -3953,7 +4139,7 @@ type TxTemplate struct { func (x *TxTemplate) Reset() { *x = TxTemplate{} - mi := &file_walletrpc_walletkit_proto_msgTypes[52] + mi := &file_walletrpc_walletkit_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3965,7 +4151,7 @@ func (x *TxTemplate) String() string { func (*TxTemplate) ProtoMessage() {} func (x *TxTemplate) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[52] + mi := &file_walletrpc_walletkit_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3978,7 +4164,7 @@ func (x *TxTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use TxTemplate.ProtoReflect.Descriptor instead. func (*TxTemplate) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{52} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} } func (x *TxTemplate) GetInputs() []*lnrpc.OutPoint { @@ -4018,7 +4204,7 @@ type PsbtCoinSelect struct { func (x *PsbtCoinSelect) Reset() { *x = PsbtCoinSelect{} - mi := &file_walletrpc_walletkit_proto_msgTypes[53] + mi := &file_walletrpc_walletkit_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4030,7 +4216,7 @@ func (x *PsbtCoinSelect) String() string { func (*PsbtCoinSelect) ProtoMessage() {} func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[53] + mi := &file_walletrpc_walletkit_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4043,7 +4229,7 @@ func (x *PsbtCoinSelect) ProtoReflect() protoreflect.Message { // Deprecated: Use PsbtCoinSelect.ProtoReflect.Descriptor instead. func (*PsbtCoinSelect) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{53} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} } func (x *PsbtCoinSelect) GetPsbt() []byte { @@ -4119,7 +4305,7 @@ type UtxoLease struct { func (x *UtxoLease) Reset() { *x = UtxoLease{} - mi := &file_walletrpc_walletkit_proto_msgTypes[54] + mi := &file_walletrpc_walletkit_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4131,7 +4317,7 @@ func (x *UtxoLease) String() string { func (*UtxoLease) ProtoMessage() {} func (x *UtxoLease) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[54] + mi := &file_walletrpc_walletkit_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4144,7 +4330,7 @@ func (x *UtxoLease) ProtoReflect() protoreflect.Message { // Deprecated: Use UtxoLease.ProtoReflect.Descriptor instead. func (*UtxoLease) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{54} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} } func (x *UtxoLease) GetId() []byte { @@ -4193,7 +4379,7 @@ type SignPsbtRequest struct { func (x *SignPsbtRequest) Reset() { *x = SignPsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[55] + mi := &file_walletrpc_walletkit_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4205,7 +4391,7 @@ func (x *SignPsbtRequest) String() string { func (*SignPsbtRequest) ProtoMessage() {} func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[55] + mi := &file_walletrpc_walletkit_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4218,7 +4404,7 @@ func (x *SignPsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtRequest.ProtoReflect.Descriptor instead. func (*SignPsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{55} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} } func (x *SignPsbtRequest) GetFundedPsbt() []byte { @@ -4240,7 +4426,7 @@ type SignPsbtResponse struct { func (x *SignPsbtResponse) Reset() { *x = SignPsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[56] + mi := &file_walletrpc_walletkit_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4252,7 +4438,7 @@ func (x *SignPsbtResponse) String() string { func (*SignPsbtResponse) ProtoMessage() {} func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[56] + mi := &file_walletrpc_walletkit_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4265,7 +4451,7 @@ func (x *SignPsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SignPsbtResponse.ProtoReflect.Descriptor instead. func (*SignPsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{56} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} } func (x *SignPsbtResponse) GetSignedPsbt() []byte { @@ -4297,7 +4483,7 @@ type FinalizePsbtRequest struct { func (x *FinalizePsbtRequest) Reset() { *x = FinalizePsbtRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[57] + mi := &file_walletrpc_walletkit_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4309,7 +4495,7 @@ func (x *FinalizePsbtRequest) String() string { func (*FinalizePsbtRequest) ProtoMessage() {} func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[57] + mi := &file_walletrpc_walletkit_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4322,7 +4508,7 @@ func (x *FinalizePsbtRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtRequest.ProtoReflect.Descriptor instead. func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{57} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} } func (x *FinalizePsbtRequest) GetFundedPsbt() []byte { @@ -4351,7 +4537,7 @@ type FinalizePsbtResponse struct { func (x *FinalizePsbtResponse) Reset() { *x = FinalizePsbtResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[58] + mi := &file_walletrpc_walletkit_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4363,7 +4549,7 @@ func (x *FinalizePsbtResponse) String() string { func (*FinalizePsbtResponse) ProtoMessage() {} func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[58] + mi := &file_walletrpc_walletkit_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4376,7 +4562,7 @@ func (x *FinalizePsbtResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePsbtResponse.ProtoReflect.Descriptor instead. func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{58} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{61} } func (x *FinalizePsbtResponse) GetSignedPsbt() []byte { @@ -4401,7 +4587,7 @@ type ListLeasesRequest struct { func (x *ListLeasesRequest) Reset() { *x = ListLeasesRequest{} - mi := &file_walletrpc_walletkit_proto_msgTypes[59] + mi := &file_walletrpc_walletkit_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4413,7 +4599,7 @@ func (x *ListLeasesRequest) String() string { func (*ListLeasesRequest) ProtoMessage() {} func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[59] + mi := &file_walletrpc_walletkit_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4426,7 +4612,7 @@ func (x *ListLeasesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesRequest.ProtoReflect.Descriptor instead. func (*ListLeasesRequest) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{59} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{62} } type ListLeasesResponse struct { @@ -4439,7 +4625,7 @@ type ListLeasesResponse struct { func (x *ListLeasesResponse) Reset() { *x = ListLeasesResponse{} - mi := &file_walletrpc_walletkit_proto_msgTypes[60] + mi := &file_walletrpc_walletkit_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4451,7 +4637,7 @@ func (x *ListLeasesResponse) String() string { func (*ListLeasesResponse) ProtoMessage() {} func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[60] + mi := &file_walletrpc_walletkit_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4464,7 +4650,7 @@ func (x *ListLeasesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLeasesResponse.ProtoReflect.Descriptor instead. func (*ListLeasesResponse) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{60} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{63} } func (x *ListLeasesResponse) GetLockedUtxos() []*UtxoLease { @@ -4486,7 +4672,7 @@ type ListSweepsResponse_TransactionIDs struct { func (x *ListSweepsResponse_TransactionIDs) Reset() { *x = ListSweepsResponse_TransactionIDs{} - mi := &file_walletrpc_walletkit_proto_msgTypes[61] + mi := &file_walletrpc_walletkit_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4498,7 +4684,7 @@ func (x *ListSweepsResponse_TransactionIDs) String() string { func (*ListSweepsResponse_TransactionIDs) ProtoMessage() {} func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message { - mi := &file_walletrpc_walletkit_proto_msgTypes[61] + mi := &file_walletrpc_walletkit_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4511,7 +4697,7 @@ func (x *ListSweepsResponse_TransactionIDs) ProtoReflect() protoreflect.Message // Deprecated: Use ListSweepsResponse_TransactionIDs.ProtoReflect.Descriptor instead. func (*ListSweepsResponse_TransactionIDs) Descriptor() ([]byte, []int) { - return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{47, 0} + return file_walletrpc_walletkit_proto_rawDescGZIP(), []int{50, 0} } func (x *ListSweepsResponse_TransactionIDs) GetTransactionIds() []string { @@ -4645,7 +4831,25 @@ const file_walletrpc_walletkit_proto_rawDesc = "" + "\x06tx_hex\x18\x01 \x01(\fR\x05txHex\x12\x14\n" + "\x05label\x18\x02 \x01(\tR\x05label\"6\n" + "\x0fPublishResponse\x12#\n" + - "\rpublish_error\x18\x01 \x01(\tR\fpublishError\"3\n" + + "\rpublish_error\x18\x01 \x01(\tR\fpublishError\"j\n" + + "\x14SubmitPackageRequest\x12\x17\n" + + "\araw_txs\x18\x01 \x03(\fR\x06rawTxs\x12'\n" + + "\rsat_per_vbyte\x18\x02 \x01(\x04H\x00R\vsatPerVbyte\x88\x01\x01B\x10\n" + + "\x0e_sat_per_vbyte\"b\n" + + "\x15SubmitPackageTxResult\x12\x12\n" + + "\x04txid\x18\x01 \x01(\tR\x04txid\x12\x14\n" + + "\x05error\x18\x02 \x01(\tR\x05error\x12\x1f\n" + + "\vother_wtxid\x18\x03 \x01(\tR\n" + + "otherWtxid\"\x9d\x02\n" + + "\x15SubmitPackageResponse\x12\x1f\n" + + "\vpackage_msg\x18\x01 \x01(\tR\n" + + "packageMsg\x12N\n" + + "\n" + + "tx_results\x18\x02 \x03(\v2/.walletrpc.SubmitPackageResponse.TxResultsEntryR\ttxResults\x123\n" + + "\x15replaced_transactions\x18\x03 \x03(\tR\x14replacedTransactions\x1a^\n" + + "\x0eTxResultsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x126\n" + + "\x05value\x18\x02 \x01(\v2 .walletrpc.SubmitPackageTxResultR\x05value:\x028\x01\"3\n" + "\x19RemoveTransactionResponse\x12\x16\n" + "\x06status\x18\x01 \x01(\tR\x06status\"\x92\x02\n" + "\x12SendOutputsRequest\x12\x1c\n" + @@ -4846,7 +5050,7 @@ const file_walletrpc_walletkit_proto_rawDesc = "" + "\x1fTAPROOT_COMMITMENT_REVOKE_FINAL\x10**V\n" + "\x11ChangeAddressType\x12#\n" + "\x1fCHANGE_ADDRESS_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n" + - "\x18CHANGE_ADDRESS_TYPE_P2TR\x10\x012\xd6\x11\n" + + "\x18CHANGE_ADDRESS_TYPE_P2TR\x10\x012\xaa\x12\n" + "\tWalletKit\x12L\n" + "\vListUnspent\x12\x1d.walletrpc.ListUnspentRequest\x1a\x1e.walletrpc.ListUnspentResponse\x12L\n" + "\vLeaseOutput\x12\x1d.walletrpc.LeaseOutputRequest\x1a\x1e.walletrpc.LeaseOutputResponse\x12R\n" + @@ -4865,7 +5069,8 @@ const file_walletrpc_walletkit_proto_rawDesc = "" + "\rImportAccount\x12\x1f.walletrpc.ImportAccountRequest\x1a .walletrpc.ImportAccountResponse\x12X\n" + "\x0fImportPublicKey\x12!.walletrpc.ImportPublicKeyRequest\x1a\".walletrpc.ImportPublicKeyResponse\x12X\n" + "\x0fImportTapscript\x12!.walletrpc.ImportTapscriptRequest\x1a\".walletrpc.ImportTapscriptResponse\x12H\n" + - "\x12PublishTransaction\x12\x16.walletrpc.Transaction\x1a\x1a.walletrpc.PublishResponse\x12[\n" + + "\x12PublishTransaction\x12\x16.walletrpc.Transaction\x1a\x1a.walletrpc.PublishResponse\x12R\n" + + "\rSubmitPackage\x12\x1f.walletrpc.SubmitPackageRequest\x1a .walletrpc.SubmitPackageResponse\x12[\n" + "\x11RemoveTransaction\x12 .walletrpc.GetTransactionRequest\x1a$.walletrpc.RemoveTransactionResponse\x12L\n" + "\vSendOutputs\x12\x1d.walletrpc.SendOutputsRequest\x1a\x1e.walletrpc.SendOutputsResponse\x12L\n" + "\vEstimateFee\x12\x1d.walletrpc.EstimateFeeRequest\x1a\x1e.walletrpc.EstimateFeeResponse\x12R\n" + @@ -4892,7 +5097,7 @@ func file_walletrpc_walletkit_proto_rawDescGZIP() []byte { } var file_walletrpc_walletkit_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 63) +var file_walletrpc_walletkit_proto_msgTypes = make([]protoimpl.MessageInfo, 67) var file_walletrpc_walletkit_proto_goTypes = []any{ (AddressType)(0), // 0: walletrpc.AddressType (WitnessType)(0), // 1: walletrpc.WitnessType @@ -4931,49 +5136,53 @@ var file_walletrpc_walletkit_proto_goTypes = []any{ (*ImportTapscriptResponse)(nil), // 34: walletrpc.ImportTapscriptResponse (*Transaction)(nil), // 35: walletrpc.Transaction (*PublishResponse)(nil), // 36: walletrpc.PublishResponse - (*RemoveTransactionResponse)(nil), // 37: walletrpc.RemoveTransactionResponse - (*SendOutputsRequest)(nil), // 38: walletrpc.SendOutputsRequest - (*SendOutputsResponse)(nil), // 39: walletrpc.SendOutputsResponse - (*EstimateFeeRequest)(nil), // 40: walletrpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 41: walletrpc.EstimateFeeResponse - (*PendingSweep)(nil), // 42: walletrpc.PendingSweep - (*PendingSweepsRequest)(nil), // 43: walletrpc.PendingSweepsRequest - (*PendingSweepsResponse)(nil), // 44: walletrpc.PendingSweepsResponse - (*BumpFeeRequest)(nil), // 45: walletrpc.BumpFeeRequest - (*BumpFeeResponse)(nil), // 46: walletrpc.BumpFeeResponse - (*BumpForceCloseFeeRequest)(nil), // 47: walletrpc.BumpForceCloseFeeRequest - (*BumpForceCloseFeeResponse)(nil), // 48: walletrpc.BumpForceCloseFeeResponse - (*ListSweepsRequest)(nil), // 49: walletrpc.ListSweepsRequest - (*ListSweepsResponse)(nil), // 50: walletrpc.ListSweepsResponse - (*LabelTransactionRequest)(nil), // 51: walletrpc.LabelTransactionRequest - (*LabelTransactionResponse)(nil), // 52: walletrpc.LabelTransactionResponse - (*FundPsbtRequest)(nil), // 53: walletrpc.FundPsbtRequest - (*FundPsbtResponse)(nil), // 54: walletrpc.FundPsbtResponse - (*TxTemplate)(nil), // 55: walletrpc.TxTemplate - (*PsbtCoinSelect)(nil), // 56: walletrpc.PsbtCoinSelect - (*UtxoLease)(nil), // 57: walletrpc.UtxoLease - (*SignPsbtRequest)(nil), // 58: walletrpc.SignPsbtRequest - (*SignPsbtResponse)(nil), // 59: walletrpc.SignPsbtResponse - (*FinalizePsbtRequest)(nil), // 60: walletrpc.FinalizePsbtRequest - (*FinalizePsbtResponse)(nil), // 61: walletrpc.FinalizePsbtResponse - (*ListLeasesRequest)(nil), // 62: walletrpc.ListLeasesRequest - (*ListLeasesResponse)(nil), // 63: walletrpc.ListLeasesResponse - (*ListSweepsResponse_TransactionIDs)(nil), // 64: walletrpc.ListSweepsResponse.TransactionIDs - nil, // 65: walletrpc.TxTemplate.OutputsEntry - (*lnrpc.Utxo)(nil), // 66: lnrpc.Utxo - (*lnrpc.OutPoint)(nil), // 67: lnrpc.OutPoint - (*signrpc.TxOut)(nil), // 68: signrpc.TxOut - (lnrpc.CoinSelectionStrategy)(0), // 69: lnrpc.CoinSelectionStrategy - (*lnrpc.ChannelPoint)(nil), // 70: lnrpc.ChannelPoint - (*lnrpc.TransactionDetails)(nil), // 71: lnrpc.TransactionDetails - (*signrpc.KeyLocator)(nil), // 72: signrpc.KeyLocator - (*signrpc.KeyDescriptor)(nil), // 73: signrpc.KeyDescriptor - (*lnrpc.Transaction)(nil), // 74: lnrpc.Transaction + (*SubmitPackageRequest)(nil), // 37: walletrpc.SubmitPackageRequest + (*SubmitPackageTxResult)(nil), // 38: walletrpc.SubmitPackageTxResult + (*SubmitPackageResponse)(nil), // 39: walletrpc.SubmitPackageResponse + (*RemoveTransactionResponse)(nil), // 40: walletrpc.RemoveTransactionResponse + (*SendOutputsRequest)(nil), // 41: walletrpc.SendOutputsRequest + (*SendOutputsResponse)(nil), // 42: walletrpc.SendOutputsResponse + (*EstimateFeeRequest)(nil), // 43: walletrpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 44: walletrpc.EstimateFeeResponse + (*PendingSweep)(nil), // 45: walletrpc.PendingSweep + (*PendingSweepsRequest)(nil), // 46: walletrpc.PendingSweepsRequest + (*PendingSweepsResponse)(nil), // 47: walletrpc.PendingSweepsResponse + (*BumpFeeRequest)(nil), // 48: walletrpc.BumpFeeRequest + (*BumpFeeResponse)(nil), // 49: walletrpc.BumpFeeResponse + (*BumpForceCloseFeeRequest)(nil), // 50: walletrpc.BumpForceCloseFeeRequest + (*BumpForceCloseFeeResponse)(nil), // 51: walletrpc.BumpForceCloseFeeResponse + (*ListSweepsRequest)(nil), // 52: walletrpc.ListSweepsRequest + (*ListSweepsResponse)(nil), // 53: walletrpc.ListSweepsResponse + (*LabelTransactionRequest)(nil), // 54: walletrpc.LabelTransactionRequest + (*LabelTransactionResponse)(nil), // 55: walletrpc.LabelTransactionResponse + (*FundPsbtRequest)(nil), // 56: walletrpc.FundPsbtRequest + (*FundPsbtResponse)(nil), // 57: walletrpc.FundPsbtResponse + (*TxTemplate)(nil), // 58: walletrpc.TxTemplate + (*PsbtCoinSelect)(nil), // 59: walletrpc.PsbtCoinSelect + (*UtxoLease)(nil), // 60: walletrpc.UtxoLease + (*SignPsbtRequest)(nil), // 61: walletrpc.SignPsbtRequest + (*SignPsbtResponse)(nil), // 62: walletrpc.SignPsbtResponse + (*FinalizePsbtRequest)(nil), // 63: walletrpc.FinalizePsbtRequest + (*FinalizePsbtResponse)(nil), // 64: walletrpc.FinalizePsbtResponse + (*ListLeasesRequest)(nil), // 65: walletrpc.ListLeasesRequest + (*ListLeasesResponse)(nil), // 66: walletrpc.ListLeasesResponse + nil, // 67: walletrpc.SubmitPackageResponse.TxResultsEntry + (*ListSweepsResponse_TransactionIDs)(nil), // 68: walletrpc.ListSweepsResponse.TransactionIDs + nil, // 69: walletrpc.TxTemplate.OutputsEntry + (*lnrpc.Utxo)(nil), // 70: lnrpc.Utxo + (*lnrpc.OutPoint)(nil), // 71: lnrpc.OutPoint + (*signrpc.TxOut)(nil), // 72: signrpc.TxOut + (lnrpc.CoinSelectionStrategy)(0), // 73: lnrpc.CoinSelectionStrategy + (*lnrpc.ChannelPoint)(nil), // 74: lnrpc.ChannelPoint + (*lnrpc.TransactionDetails)(nil), // 75: lnrpc.TransactionDetails + (*signrpc.KeyLocator)(nil), // 76: signrpc.KeyLocator + (*signrpc.KeyDescriptor)(nil), // 77: signrpc.KeyDescriptor + (*lnrpc.Transaction)(nil), // 78: lnrpc.Transaction } var file_walletrpc_walletkit_proto_depIdxs = []int32{ - 66, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo - 67, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint - 67, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 70, // 0: walletrpc.ListUnspentResponse.utxos:type_name -> lnrpc.Utxo + 71, // 1: walletrpc.LeaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint + 71, // 2: walletrpc.ReleaseOutputRequest.outpoint:type_name -> lnrpc.OutPoint 0, // 3: walletrpc.AddrRequest.type:type_name -> walletrpc.AddressType 0, // 4: walletrpc.Account.address_type:type_name -> walletrpc.AddressType 0, // 5: walletrpc.AccountWithAddresses.address_type:type_name -> walletrpc.AddressType @@ -4988,85 +5197,89 @@ var file_walletrpc_walletkit_proto_depIdxs = []int32{ 33, // 14: walletrpc.ImportTapscriptRequest.partial_reveal:type_name -> walletrpc.TapscriptPartialReveal 32, // 15: walletrpc.TapscriptFullTree.all_leaves:type_name -> walletrpc.TapLeaf 32, // 16: walletrpc.TapscriptPartialReveal.revealed_leaf:type_name -> walletrpc.TapLeaf - 68, // 17: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut - 69, // 18: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 67, // 19: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint - 1, // 20: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType - 42, // 21: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep - 67, // 22: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint - 70, // 23: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint - 71, // 24: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails - 64, // 25: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs - 55, // 26: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate - 56, // 27: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect - 2, // 28: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType - 69, // 29: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy - 57, // 30: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 67, // 31: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint - 65, // 32: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry - 67, // 33: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint - 57, // 34: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease - 3, // 35: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest - 5, // 36: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest - 7, // 37: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest - 62, // 38: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest - 9, // 39: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq - 72, // 40: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator - 10, // 41: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest - 21, // 42: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest - 15, // 43: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest - 17, // 44: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest - 19, // 45: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest - 22, // 46: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest - 24, // 47: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest - 26, // 48: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest - 28, // 49: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest - 30, // 50: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest - 35, // 51: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction - 21, // 52: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest - 38, // 53: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest - 40, // 54: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest - 43, // 55: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest - 45, // 56: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest - 47, // 57: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest - 49, // 58: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest - 51, // 59: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest - 53, // 60: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest - 58, // 61: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest - 60, // 62: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest - 4, // 63: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse - 6, // 64: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse - 8, // 65: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse - 63, // 66: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse - 73, // 67: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor - 73, // 68: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor - 11, // 69: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse - 74, // 70: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction - 16, // 71: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse - 18, // 72: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse - 20, // 73: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse - 23, // 74: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse - 25, // 75: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse - 27, // 76: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse - 29, // 77: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse - 34, // 78: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse - 36, // 79: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse - 37, // 80: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse - 39, // 81: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse - 41, // 82: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse - 44, // 83: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse - 46, // 84: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse - 48, // 85: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse - 50, // 86: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse - 52, // 87: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse - 54, // 88: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse - 59, // 89: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse - 61, // 90: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse - 63, // [63:91] is the sub-list for method output_type - 35, // [35:63] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 67, // 17: walletrpc.SubmitPackageResponse.tx_results:type_name -> walletrpc.SubmitPackageResponse.TxResultsEntry + 72, // 18: walletrpc.SendOutputsRequest.outputs:type_name -> signrpc.TxOut + 73, // 19: walletrpc.SendOutputsRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 71, // 20: walletrpc.PendingSweep.outpoint:type_name -> lnrpc.OutPoint + 1, // 21: walletrpc.PendingSweep.witness_type:type_name -> walletrpc.WitnessType + 45, // 22: walletrpc.PendingSweepsResponse.pending_sweeps:type_name -> walletrpc.PendingSweep + 71, // 23: walletrpc.BumpFeeRequest.outpoint:type_name -> lnrpc.OutPoint + 74, // 24: walletrpc.BumpForceCloseFeeRequest.chan_point:type_name -> lnrpc.ChannelPoint + 75, // 25: walletrpc.ListSweepsResponse.transaction_details:type_name -> lnrpc.TransactionDetails + 68, // 26: walletrpc.ListSweepsResponse.transaction_ids:type_name -> walletrpc.ListSweepsResponse.TransactionIDs + 58, // 27: walletrpc.FundPsbtRequest.raw:type_name -> walletrpc.TxTemplate + 59, // 28: walletrpc.FundPsbtRequest.coin_select:type_name -> walletrpc.PsbtCoinSelect + 2, // 29: walletrpc.FundPsbtRequest.change_type:type_name -> walletrpc.ChangeAddressType + 73, // 30: walletrpc.FundPsbtRequest.coin_selection_strategy:type_name -> lnrpc.CoinSelectionStrategy + 60, // 31: walletrpc.FundPsbtResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 71, // 32: walletrpc.TxTemplate.inputs:type_name -> lnrpc.OutPoint + 69, // 33: walletrpc.TxTemplate.outputs:type_name -> walletrpc.TxTemplate.OutputsEntry + 71, // 34: walletrpc.UtxoLease.outpoint:type_name -> lnrpc.OutPoint + 60, // 35: walletrpc.ListLeasesResponse.locked_utxos:type_name -> walletrpc.UtxoLease + 38, // 36: walletrpc.SubmitPackageResponse.TxResultsEntry.value:type_name -> walletrpc.SubmitPackageTxResult + 3, // 37: walletrpc.WalletKit.ListUnspent:input_type -> walletrpc.ListUnspentRequest + 5, // 38: walletrpc.WalletKit.LeaseOutput:input_type -> walletrpc.LeaseOutputRequest + 7, // 39: walletrpc.WalletKit.ReleaseOutput:input_type -> walletrpc.ReleaseOutputRequest + 65, // 40: walletrpc.WalletKit.ListLeases:input_type -> walletrpc.ListLeasesRequest + 9, // 41: walletrpc.WalletKit.DeriveNextKey:input_type -> walletrpc.KeyReq + 76, // 42: walletrpc.WalletKit.DeriveKey:input_type -> signrpc.KeyLocator + 10, // 43: walletrpc.WalletKit.NextAddr:input_type -> walletrpc.AddrRequest + 21, // 44: walletrpc.WalletKit.GetTransaction:input_type -> walletrpc.GetTransactionRequest + 15, // 45: walletrpc.WalletKit.ListAccounts:input_type -> walletrpc.ListAccountsRequest + 17, // 46: walletrpc.WalletKit.RequiredReserve:input_type -> walletrpc.RequiredReserveRequest + 19, // 47: walletrpc.WalletKit.ListAddresses:input_type -> walletrpc.ListAddressesRequest + 22, // 48: walletrpc.WalletKit.SignMessageWithAddr:input_type -> walletrpc.SignMessageWithAddrRequest + 24, // 49: walletrpc.WalletKit.VerifyMessageWithAddr:input_type -> walletrpc.VerifyMessageWithAddrRequest + 26, // 50: walletrpc.WalletKit.ImportAccount:input_type -> walletrpc.ImportAccountRequest + 28, // 51: walletrpc.WalletKit.ImportPublicKey:input_type -> walletrpc.ImportPublicKeyRequest + 30, // 52: walletrpc.WalletKit.ImportTapscript:input_type -> walletrpc.ImportTapscriptRequest + 35, // 53: walletrpc.WalletKit.PublishTransaction:input_type -> walletrpc.Transaction + 37, // 54: walletrpc.WalletKit.SubmitPackage:input_type -> walletrpc.SubmitPackageRequest + 21, // 55: walletrpc.WalletKit.RemoveTransaction:input_type -> walletrpc.GetTransactionRequest + 41, // 56: walletrpc.WalletKit.SendOutputs:input_type -> walletrpc.SendOutputsRequest + 43, // 57: walletrpc.WalletKit.EstimateFee:input_type -> walletrpc.EstimateFeeRequest + 46, // 58: walletrpc.WalletKit.PendingSweeps:input_type -> walletrpc.PendingSweepsRequest + 48, // 59: walletrpc.WalletKit.BumpFee:input_type -> walletrpc.BumpFeeRequest + 50, // 60: walletrpc.WalletKit.BumpForceCloseFee:input_type -> walletrpc.BumpForceCloseFeeRequest + 52, // 61: walletrpc.WalletKit.ListSweeps:input_type -> walletrpc.ListSweepsRequest + 54, // 62: walletrpc.WalletKit.LabelTransaction:input_type -> walletrpc.LabelTransactionRequest + 56, // 63: walletrpc.WalletKit.FundPsbt:input_type -> walletrpc.FundPsbtRequest + 61, // 64: walletrpc.WalletKit.SignPsbt:input_type -> walletrpc.SignPsbtRequest + 63, // 65: walletrpc.WalletKit.FinalizePsbt:input_type -> walletrpc.FinalizePsbtRequest + 4, // 66: walletrpc.WalletKit.ListUnspent:output_type -> walletrpc.ListUnspentResponse + 6, // 67: walletrpc.WalletKit.LeaseOutput:output_type -> walletrpc.LeaseOutputResponse + 8, // 68: walletrpc.WalletKit.ReleaseOutput:output_type -> walletrpc.ReleaseOutputResponse + 66, // 69: walletrpc.WalletKit.ListLeases:output_type -> walletrpc.ListLeasesResponse + 77, // 70: walletrpc.WalletKit.DeriveNextKey:output_type -> signrpc.KeyDescriptor + 77, // 71: walletrpc.WalletKit.DeriveKey:output_type -> signrpc.KeyDescriptor + 11, // 72: walletrpc.WalletKit.NextAddr:output_type -> walletrpc.AddrResponse + 78, // 73: walletrpc.WalletKit.GetTransaction:output_type -> lnrpc.Transaction + 16, // 74: walletrpc.WalletKit.ListAccounts:output_type -> walletrpc.ListAccountsResponse + 18, // 75: walletrpc.WalletKit.RequiredReserve:output_type -> walletrpc.RequiredReserveResponse + 20, // 76: walletrpc.WalletKit.ListAddresses:output_type -> walletrpc.ListAddressesResponse + 23, // 77: walletrpc.WalletKit.SignMessageWithAddr:output_type -> walletrpc.SignMessageWithAddrResponse + 25, // 78: walletrpc.WalletKit.VerifyMessageWithAddr:output_type -> walletrpc.VerifyMessageWithAddrResponse + 27, // 79: walletrpc.WalletKit.ImportAccount:output_type -> walletrpc.ImportAccountResponse + 29, // 80: walletrpc.WalletKit.ImportPublicKey:output_type -> walletrpc.ImportPublicKeyResponse + 34, // 81: walletrpc.WalletKit.ImportTapscript:output_type -> walletrpc.ImportTapscriptResponse + 36, // 82: walletrpc.WalletKit.PublishTransaction:output_type -> walletrpc.PublishResponse + 39, // 83: walletrpc.WalletKit.SubmitPackage:output_type -> walletrpc.SubmitPackageResponse + 40, // 84: walletrpc.WalletKit.RemoveTransaction:output_type -> walletrpc.RemoveTransactionResponse + 42, // 85: walletrpc.WalletKit.SendOutputs:output_type -> walletrpc.SendOutputsResponse + 44, // 86: walletrpc.WalletKit.EstimateFee:output_type -> walletrpc.EstimateFeeResponse + 47, // 87: walletrpc.WalletKit.PendingSweeps:output_type -> walletrpc.PendingSweepsResponse + 49, // 88: walletrpc.WalletKit.BumpFee:output_type -> walletrpc.BumpFeeResponse + 51, // 89: walletrpc.WalletKit.BumpForceCloseFee:output_type -> walletrpc.BumpForceCloseFeeResponse + 53, // 90: walletrpc.WalletKit.ListSweeps:output_type -> walletrpc.ListSweepsResponse + 55, // 91: walletrpc.WalletKit.LabelTransaction:output_type -> walletrpc.LabelTransactionResponse + 57, // 92: walletrpc.WalletKit.FundPsbt:output_type -> walletrpc.FundPsbtResponse + 62, // 93: walletrpc.WalletKit.SignPsbt:output_type -> walletrpc.SignPsbtResponse + 64, // 94: walletrpc.WalletKit.FinalizePsbt:output_type -> walletrpc.FinalizePsbtResponse + 66, // [66:95] is the sub-list for method output_type + 37, // [37:66] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_walletrpc_walletkit_proto_init() } @@ -5080,11 +5293,12 @@ func file_walletrpc_walletkit_proto_init() { (*ImportTapscriptRequest_RootHashOnly)(nil), (*ImportTapscriptRequest_FullKeyOnly)(nil), } - file_walletrpc_walletkit_proto_msgTypes[47].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[34].OneofWrappers = []any{} + file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []any{ (*ListSweepsResponse_TransactionDetails)(nil), (*ListSweepsResponse_TransactionIds)(nil), } - file_walletrpc_walletkit_proto_msgTypes[50].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []any{ (*FundPsbtRequest_Psbt)(nil), (*FundPsbtRequest_Raw)(nil), (*FundPsbtRequest_CoinSelect)(nil), @@ -5092,7 +5306,7 @@ func file_walletrpc_walletkit_proto_init() { (*FundPsbtRequest_SatPerVbyte)(nil), (*FundPsbtRequest_SatPerKw)(nil), } - file_walletrpc_walletkit_proto_msgTypes[53].OneofWrappers = []any{ + file_walletrpc_walletkit_proto_msgTypes[56].OneofWrappers = []any{ (*PsbtCoinSelect_ExistingOutputIndex)(nil), (*PsbtCoinSelect_Add)(nil), } @@ -5102,7 +5316,7 @@ func file_walletrpc_walletkit_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_walletrpc_walletkit_proto_rawDesc), len(file_walletrpc_walletkit_proto_rawDesc)), NumEnums: 3, - NumMessages: 63, + NumMessages: 67, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/walletrpc/walletkit.pb.gw.go b/lnrpc/walletrpc/walletkit.pb.gw.go index c4c4db99f33..32cfdc35f9a 100644 --- a/lnrpc/walletrpc/walletkit.pb.gw.go +++ b/lnrpc/walletrpc/walletkit.pb.gw.go @@ -602,6 +602,40 @@ func local_request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler } +func request_WalletKit_SubmitPackage_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubmitPackageRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.SubmitPackage(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_WalletKit_SubmitPackage_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SubmitPackageRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.SubmitPackage(ctx, &protoReq) + return msg, metadata, err + +} + func request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetTransactionRequest var metadata runtime.ServerMetadata @@ -1411,6 +1445,31 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SubmitPackage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/SubmitPackage", runtime.WithHTTPPathPattern("/v2/wallet/tx/package")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_WalletKit_SubmitPackage_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_WalletKit_SubmitPackage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2101,6 +2160,28 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_WalletKit_SubmitPackage_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/walletrpc.WalletKit/SubmitPackage", runtime.WithHTTPPathPattern("/v2/wallet/tx/package")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_WalletKit_SubmitPackage_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_WalletKit_SubmitPackage_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_WalletKit_RemoveTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2381,6 +2462,8 @@ var ( pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, "")) + pattern_WalletKit_SubmitPackage_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tx", "package"}, "")) + pattern_WalletKit_RemoveTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "removetx"}, "")) pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, "")) @@ -2439,6 +2522,8 @@ var ( forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage + forward_WalletKit_SubmitPackage_0 = runtime.ForwardResponseMessage + forward_WalletKit_RemoveTransaction_0 = runtime.ForwardResponseMessage forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage diff --git a/lnrpc/walletrpc/walletkit.pb.json.go b/lnrpc/walletrpc/walletkit.pb.json.go index 8337806926e..ba427b0e4f4 100644 --- a/lnrpc/walletrpc/walletkit.pb.json.go +++ b/lnrpc/walletrpc/walletkit.pb.json.go @@ -447,6 +447,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context callback(string(respBytes), nil) } + registry["walletrpc.WalletKit.SubmitPackage"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SubmitPackageRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewWalletKitClient(conn) + resp, err := client.SubmitPackage(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + registry["walletrpc.WalletKit.RemoveTransaction"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 81176d90910..540c571d36e 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -208,6 +208,20 @@ service WalletKit { */ rpc PublishTransaction (Transaction) returns (PublishResponse); + /* lncli: `wallet submitpackage` + SubmitPackage submits a package of related transactions (topologically + sorted, unconfirmed parents first and the child last) for atomic + validation and acceptance. Real package submission is only performed by + the bitcoind backend, via the node's submitpackage RPC, which lets a + zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The + btcd backend does not support submitpackage and returns an error. A + neutrino light client has no mempool and cannot atomically accept a + package; as a best effort it broadcasts the transactions individually and + relies on a peer's 1p1c package relay, returning an unverified result + (not a package-accept verdict). + */ + rpc SubmitPackage (SubmitPackageRequest) returns (SubmitPackageResponse); + /* lncli: `wallet removetx` RemoveTransaction attempts to remove the provided transaction from the internal transaction store of the wallet. @@ -801,6 +815,48 @@ message PublishResponse { string publish_error = 1; } +message SubmitPackageRequest { + /* + The raw serialized transactions forming the package, topologically sorted + with unconfirmed parents first and the child last. + */ + repeated bytes raw_txs = 1; + + /* + Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the + submitpackage maxfeerate). When unset the node's default is used; an + explicit 0 means no limit, which is required for a CPFP child whose + standalone feerate is high. + */ + optional uint64 sat_per_vbyte = 2; +} + +message SubmitPackageTxResult { + // The transaction id (txid) in hex. + string txid = 1; + + // If non-empty, the reason this transaction was rejected. + string error = 2; + + /* + If non-empty, the wtxid (in hex) of a transaction with the same txid but a + different witness that was already in the mempool; the submitted + transaction was ignored as a duplicate (witness replacement). + */ + string other_wtxid = 3; +} + +message SubmitPackageResponse { + // A summary message; "success" when the whole package was accepted. + string package_msg = 1; + + // Per-transaction results keyed by wtxid (hex). + map tx_results = 2; + + // The txids of transactions evicted via package RBF. + repeated string replaced_transactions = 3; +} + message RemoveTransactionResponse { // The status of the remove transaction operation. string status = 1; diff --git a/lnrpc/walletrpc/walletkit.swagger.json b/lnrpc/walletrpc/walletkit.swagger.json index e5a0946c103..90365c3040b 100644 --- a/lnrpc/walletrpc/walletkit.swagger.json +++ b/lnrpc/walletrpc/walletkit.swagger.json @@ -832,6 +832,39 @@ ] } }, + "/v2/wallet/tx/package": { + "post": { + "summary": "lncli: `wallet submitpackage`\nSubmitPackage submits a package of related transactions (topologically\nsorted, unconfirmed parents first and the child last) for atomic\nvalidation and acceptance. Real package submission is only performed by\nthe bitcoind backend, via the node's submitpackage RPC, which lets a\nzero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The\nbtcd backend does not support submitpackage and returns an error. A\nneutrino light client has no mempool and cannot atomically accept a\npackage; as a best effort it broadcasts the transactions individually and\nrelies on a peer's 1p1c package relay, returning an unverified result\n(not a package-accept verdict).", + "operationId": "WalletKit_SubmitPackage", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/walletrpcSubmitPackageResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/walletrpcSubmitPackageRequest" + } + } + ], + "tags": [ + "WalletKit" + ] + } + }, "/v2/wallet/utxos": { "post": { "summary": "ListUnspent returns a list of all utxos spendable by the wallet with a\nnumber of confirmations between the specified minimum and maximum. By\ndefault, all utxos are listed. To list only the unconfirmed utxos, set\nthe unconfirmed_only to true.", @@ -2180,6 +2213,64 @@ } } }, + "walletrpcSubmitPackageRequest": { + "type": "object", + "properties": { + "raw_txs": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + }, + "description": "The raw serialized transactions forming the package, topologically sorted\nwith unconfirmed parents first and the child last." + }, + "sat_per_vbyte": { + "type": "string", + "format": "uint64", + "description": "Optional per-transaction fee-rate ceiling in sat/vByte (mapped onto the\nsubmitpackage maxfeerate). When unset the node's default is used; an\nexplicit 0 means no limit, which is required for a CPFP child whose\nstandalone feerate is high." + } + } + }, + "walletrpcSubmitPackageResponse": { + "type": "object", + "properties": { + "package_msg": { + "type": "string", + "description": "A summary message; \"success\" when the whole package was accepted." + }, + "tx_results": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/walletrpcSubmitPackageTxResult" + }, + "description": "Per-transaction results keyed by wtxid (hex)." + }, + "replaced_transactions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The txids of transactions evicted via package RBF." + } + } + }, + "walletrpcSubmitPackageTxResult": { + "type": "object", + "properties": { + "txid": { + "type": "string", + "description": "The transaction id (txid) in hex." + }, + "error": { + "type": "string", + "description": "If non-empty, the reason this transaction was rejected." + }, + "other_wtxid": { + "type": "string", + "description": "If non-empty, the wtxid (in hex) of a transaction with the same txid but a\ndifferent witness that was already in the mempool; the submitted\ntransaction was ignored as a duplicate (witness replacement)." + } + } + }, "walletrpcTapLeaf": { "type": "object", "properties": { diff --git a/lnrpc/walletrpc/walletkit.yaml b/lnrpc/walletrpc/walletkit.yaml index b912fea23b9..06c90892316 100644 --- a/lnrpc/walletrpc/walletkit.yaml +++ b/lnrpc/walletrpc/walletkit.yaml @@ -34,6 +34,9 @@ http: - selector: walletrpc.WalletKit.PublishTransaction post: "/v2/wallet/tx" body: "*" + - selector: walletrpc.WalletKit.SubmitPackage + post: "/v2/wallet/tx/package" + body: "*" - selector: walletrpc.WalletKit.SendOutputs post: "/v2/wallet/send" body: "*" diff --git a/lnrpc/walletrpc/walletkit_grpc.pb.go b/lnrpc/walletrpc/walletkit_grpc.pb.go index 4cd79a448a2..8cd991ceee4 100644 --- a/lnrpc/walletrpc/walletkit_grpc.pb.go +++ b/lnrpc/walletrpc/walletkit_grpc.pb.go @@ -156,6 +156,18 @@ type WalletKitClient interface { // attempt to re-broadcast the transaction on start up, until it enters the // chain. PublishTransaction(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*PublishResponse, error) + // lncli: `wallet submitpackage` + // SubmitPackage submits a package of related transactions (topologically + // sorted, unconfirmed parents first and the child last) for atomic + // validation and acceptance. Real package submission is only performed by + // the bitcoind backend, via the node's submitpackage RPC, which lets a + // zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The + // btcd backend does not support submitpackage and returns an error. A + // neutrino light client has no mempool and cannot atomically accept a + // package; as a best effort it broadcasts the transactions individually and + // relies on a peer's 1p1c package relay, returning an unverified result + // (not a package-accept verdict). + SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) // lncli: `wallet removetx` // RemoveTransaction attempts to remove the provided transaction from the // internal transaction store of the wallet. @@ -443,6 +455,15 @@ func (c *walletKitClient) PublishTransaction(ctx context.Context, in *Transactio return out, nil } +func (c *walletKitClient) SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) { + out := new(SubmitPackageResponse) + err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SubmitPackage", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletKitClient) RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error) { out := new(RemoveTransactionResponse) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RemoveTransaction", in, out, opts...) @@ -682,6 +703,18 @@ type WalletKitServer interface { // attempt to re-broadcast the transaction on start up, until it enters the // chain. PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) + // lncli: `wallet submitpackage` + // SubmitPackage submits a package of related transactions (topologically + // sorted, unconfirmed parents first and the child last) for atomic + // validation and acceptance. Real package submission is only performed by + // the bitcoind backend, via the node's submitpackage RPC, which lets a + // zero-fee v3/TRUC parent be accepted via its fee-paying CPFP child. The + // btcd backend does not support submitpackage and returns an error. A + // neutrino light client has no mempool and cannot atomically accept a + // package; as a best effort it broadcasts the transactions individually and + // relies on a peer's 1p1c package relay, returning an unverified result + // (not a package-accept verdict). + SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) // lncli: `wallet removetx` // RemoveTransaction attempts to remove the provided transaction from the // internal transaction store of the wallet. @@ -864,6 +897,9 @@ func (UnimplementedWalletKitServer) ImportTapscript(context.Context, *ImportTaps func (UnimplementedWalletKitServer) PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PublishTransaction not implemented") } +func (UnimplementedWalletKitServer) SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitPackage not implemented") +} func (UnimplementedWalletKitServer) RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveTransaction not implemented") } @@ -1216,6 +1252,24 @@ func _WalletKit_PublishTransaction_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _WalletKit_SubmitPackage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitPackageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletKitServer).SubmitPackage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/walletrpc.WalletKit/SubmitPackage", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletKitServer).SubmitPackage(ctx, req.(*SubmitPackageRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletKit_RemoveTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetTransactionRequest) if err := dec(in); err != nil { @@ -1489,6 +1543,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{ MethodName: "PublishTransaction", Handler: _WalletKit_PublishTransaction_Handler, }, + { + MethodName: "SubmitPackage", + Handler: _WalletKit_SubmitPackage_Handler, + }, { MethodName: "RemoveTransaction", Handler: _WalletKit_RemoveTransaction_Handler, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 1bed7e98a45..456fb20465a 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -94,6 +94,10 @@ var ( Entity: "onchain", Action: "write", }}, + "/walletrpc.WalletKit/SubmitPackage": {{ + Entity: "onchain", + Action: "write", + }}, "/walletrpc.WalletKit/SendOutputs": {{ Entity: "onchain", Action: "write", @@ -696,6 +700,87 @@ func (w *WalletKit) PublishTransaction(ctx context.Context, return &PublishResponse{}, nil } +// maxPackageTxns is the maximum number of transactions accepted in a single +// SubmitPackage request. It mirrors bitcoind's MAX_PACKAGE_COUNT (the limit +// its submitpackage RPC enforces), so any package the backend could accept +// fits, while bounding the work an authenticated caller can force from +// deserializing an arbitrarily long raw_txs list. +const maxPackageTxns = 25 + +// SubmitPackage submits a package of related transactions (topologically +// sorted, unconfirmed parents first and the child last) to the wallet's chain +// backend for atomic validation and acceptance. This lets a zero-fee v3/TRUC +// parent confirm via its fee-paying CPFP child without the caller needing a +// separate connection to the chain backend. +func (w *WalletKit) SubmitPackage(_ context.Context, + req *SubmitPackageRequest) (*SubmitPackageResponse, error) { + + if len(req.RawTxs) == 0 { + return nil, fmt.Errorf("must provide at least one transaction") + } + if len(req.RawTxs) > maxPackageTxns { + return nil, fmt.Errorf("package of %d transactions exceeds "+ + "the maximum of %d", len(req.RawTxs), maxPackageTxns) + } + + txns := make([]*wire.MsgTx, 0, len(req.RawTxs)) + for _, raw := range req.RawTxs { + tx := &wire.MsgTx{} + if err := tx.Deserialize(bytes.NewReader(raw)); err != nil { + return nil, fmt.Errorf("unable to decode tx: %w", err) + } + + txns = append(txns, tx) + } + + // Map the optional sat/vByte ceiling onto the backend. An unset value + // uses the node's default; an explicit value (including 0, meaning no + // limit) is passed through unchanged. + var maxFeeRate *chainfee.SatPerVByte + if req.SatPerVbyte != nil { + rate := chainfee.SatPerVByte(*req.SatPerVbyte) + maxFeeRate = &rate + } + + result, err := w.cfg.Wallet.SubmitPackage(txns, maxFeeRate) + if err != nil { + return nil, err + } + + // Some backends (e.g. the no-chain source or mocks) may return a nil + // result; guard against a nil dereference below. + if result == nil { + return nil, fmt.Errorf("nil result from wallet backend") + } + + numResults := len(result.TxResults) + resp := &SubmitPackageResponse{ + PackageMsg: result.PackageMsg, + TxResults: make(map[string]*SubmitPackageTxResult, numResults), + ReplacedTransactions: make( + []string, 0, len(result.ReplacedTransactions), + ), + } + for _, replaced := range result.ReplacedTransactions { + resp.ReplacedTransactions = append( + resp.ReplacedTransactions, replaced.String(), + ) + } + for wtxid, txResult := range result.TxResults { + entry := &SubmitPackageTxResult{Txid: txResult.TxID.String()} + if txResult.Error != nil { + entry.Error = *txResult.Error + } + if txResult.OtherWtxid != nil { + entry.OtherWtxid = txResult.OtherWtxid.String() + } + + resp.TxResults[wtxid] = entry + } + + return resp, nil +} + // RemoveTransaction attempts to remove the transaction and all of its // descendants resulting from further spends of the outputs of the provided // transaction id. diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go index b5535994cab..90a3310ddf8 100644 --- a/lntest/mock/walletcontroller.go +++ b/lntest/mock/walletcontroller.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil/v2" "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" "github.com/btcsuite/btcd/chaincfg/v2" @@ -241,6 +242,18 @@ func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error { return nil } +// SubmitPackage publishes each transaction in the package individually, +// mirroring PublishTransaction. +func (w *WalletController) SubmitPackage(txns []*wire.MsgTx, + _ *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, error) { + + for _, tx := range txns { + w.PublishedTransactions <- tx + } + + return &btcjson.SubmitPackageResult{}, nil +} + // GetTransactionDetails currently does nothing. func (w *WalletController) GetTransactionDetails( txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) { diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 74bc92c1582..ecb7b57e0b8 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil/v2" "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" "github.com/btcsuite/btcd/chaincfg/v2" @@ -1231,6 +1232,91 @@ func (b *BtcWallet) PublishTransaction(tx *wire.MsgTx, label string) error { return mapRpcclientError(err) } +// neutrinoBroadcastMsg is the PackageMsg returned by the neutrino best-effort +// path. It is deliberately not "success": a neutrino light client has no +// mempool, so it cannot confirm the package was accepted. It broadcasts the +// transactions and reports them as broadcast-but-unverified, which callers +// must treat as an unverified relay attempt, not a package-accept verdict. +const neutrinoBroadcastMsg = "broadcast-unverified" + +// SubmitPackage submits a package of related transactions (topologically +// sorted, parents first and child last) for atomic validation and acceptance. +// +// Only the bitcoind backend performs real package submission, via the node's +// submitpackage RPC, which lets a zero-fee v3/TRUC parent be accepted via its +// fee-paying CPFP child (which sendrawtransaction rejects on its own). The +// btcd backend has no submitpackage handler and returns ErrUnimplemented. +// +// A neutrino light client has no mempool and cannot validate or atomically +// accept a package. As a best effort it broadcasts each transaction +// individually over the P2P network and relies on a peer's 1p1c package relay +// to assemble them. The returned PackageMsg is deliberately not "success": a +// light client cannot confirm acceptance, so callers must treat the result as +// an unverified broadcast rather than a package-accept verdict. +func (b *BtcWallet) SubmitPackage(txns []*wire.MsgTx, + maxFeeRate *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, + error) { + + if b.chain.BackEnd() == "neutrino" { + // The best-effort neutrino broadcast goes through plain + // SendRawTransaction, which cannot enforce a fee-rate ceiling, + // so reject a caller-provided limit rather than silently + // ignoring it and giving a false sense of protection. + if maxFeeRate != nil { + return nil, fmt.Errorf("max fee rate is not " + + "supported for neutrino package broadcast") + } + + for i, tx := range txns { + if err := b.PublishTransaction(tx, ""); err != nil { + return nil, fmt.Errorf("unable to "+ + "broadcast package tx %d (%v): %w", + i, tx.TxHash(), err) + } + } + + results := make( + map[string]btcjson.SubmitPackageTxResult, len(txns), + ) + for _, tx := range txns { + results[tx.WitnessHash().String()] = + btcjson.SubmitPackageTxResult{TxID: tx.TxHash()} + } + + return &btcjson.SubmitPackageResult{ + PackageMsg: neutrinoBroadcastMsg, + TxResults: results, + }, nil + } + + // bitcoind's submitpackage maxfeerate is expressed in BTC/kvB, so map + // the optional sat/vByte ceiling onto it. A nil ceiling leaves the node + // default unchanged; an explicit 0 disables the limit. + var maxFeeRateBTCPerKvB *float64 + if maxFeeRate != nil { + btcPerKvB := satPerVByteToBTCPerKvB(*maxFeeRate) + maxFeeRateBTCPerKvB = &btcPerKvB + } + + return b.chain.SubmitPackage(txns, maxFeeRateBTCPerKvB) +} + +// vBytesPerKvB is the number of virtual bytes in a kilo-virtual-byte, used to +// convert a sat/vByte fee rate into the per-kvB unit bitcoind expects. +const vBytesPerKvB = 1000 + +// satPerVByteToBTCPerKvB converts a sat/vByte fee rate into the BTC/kvB unit +// expected by bitcoind's submitpackage maxfeerate argument: 1 sat/vByte is +// 1000 sat/kvB, and SatoshiPerBitcoin sats make a BTC, so +// BTC/kvB = sat/vByte * 1000 / SatoshiPerBitcoin. +// +// NOTE: the sat/vByte input is integer, so only whole-sat/vByte ceilings are +// expressible, and very large values lose precision once the float64 product +// exceeds 2^53. +func satPerVByteToBTCPerKvB(rate chainfee.SatPerVByte) float64 { + return float64(rate) * vBytesPerKvB / btcutil.SatoshiPerBitcoin +} + // LabelTransaction adds a label to a transaction. If the tx already // has a label, this call will fail unless the overwrite parameter // is set. Labels must not be empty, and they are limited to 500 chars. diff --git a/lnwallet/btcwallet/submitpackage_test.go b/lnwallet/btcwallet/submitpackage_test.go new file mode 100644 index 00000000000..63286f01107 --- /dev/null +++ b/lnwallet/btcwallet/submitpackage_test.go @@ -0,0 +1,38 @@ +package btcwallet + +import ( + "testing" + + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/stretchr/testify/require" +) + +// TestSatPerVByteToBTCPerKvB checks the sat/vByte -> BTC/kvB conversion used to +// map an lnd fee-rate ceiling onto bitcoind's submitpackage maxfeerate +// argument. A regression here would silently relax or tighten the user's fee +// ceiling, so the known reference points are pinned. +func TestSatPerVByteToBTCPerKvB(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rate chainfee.SatPerVByte + want float64 + }{ + {name: "zero", rate: 0, want: 0}, + {name: "1 sat/vByte", rate: 1, want: 0.00001}, + {name: "10 sat/vByte", rate: 10, want: 0.0001}, + {name: "250 sat/vByte", rate: 250, want: 0.0025}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + require.InDelta( + t, tc.want, satPerVByteToBTCPerKvB(tc.rate), + 1e-12, + ) + }) + } +} diff --git a/lnwallet/interface.go b/lnwallet/interface.go index b0be6fe7d99..d2adfd2eed2 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil/v2" "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" "github.com/btcsuite/btcd/chaincfg/v2" @@ -441,6 +442,19 @@ type WalletController interface { // published transaction. PublishTransaction(tx *wire.MsgTx, label string) error + // SubmitPackage submits a package of related transactions + // (topologically sorted, unconfirmed parents first and the child + // last) to the chain backend for atomic validation and acceptance. + // This lets a zero-fee v3/TRUC parent be accepted via its fee-paying + // CPFP child, which a standalone broadcast rejects. maxFeeRate is an + // optional per-transaction fee-rate ceiling in sat/vByte (nil leaves + // the node default unchanged). Backends without a mempool (e.g. + // neutrino) broadcast each transaction individually and rely on P2P + // 1p1c package relay instead. + SubmitPackage(txns []*wire.MsgTx, + maxFeeRate *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, + error) + // LabelTransaction adds a label to a transaction. If the tx already // has a label, this call will fail unless the overwrite parameter // is set. Labels must not be empty, and they are limited to 500 chars. diff --git a/lnwallet/mock.go b/lnwallet/mock.go index 22041670edf..5583073e6c4 100644 --- a/lnwallet/mock.go +++ b/lnwallet/mock.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil/v2" "github.com/btcsuite/btcd/btcutil/v2/hdkeychain" "github.com/btcsuite/btcd/chaincfg/v2" @@ -256,6 +257,19 @@ func (w *mockWalletController) PublishTransaction(tx *wire.MsgTx, return nil } +// SubmitPackage publishes each transaction in the package individually, +// mirroring PublishTransaction. The mock has no real chain backend so it +// returns an empty result. +func (w *mockWalletController) SubmitPackage(txns []*wire.MsgTx, + _ *chainfee.SatPerVByte) (*btcjson.SubmitPackageResult, error) { + + for _, tx := range txns { + w.PublishedTransactions <- tx + } + + return &btcjson.SubmitPackageResult{}, nil +} + // GetTransactionDetails currently does nothing. func (w *mockWalletController) GetTransactionDetails(*chainhash.Hash) ( *TransactionDetail, error) { From ea88a268707089634bb07fb8e112b5968ebf86c9 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 11 Jun 2026 13:35:23 -0700 Subject: [PATCH 2/4] lncli: add wallet submitpackage command Add a `wallet submitpackage` command that takes one or more hex-encoded raw transactions (topologically sorted, parents first and the child last) and an optional --max_fee_rate, and submits them as a package via the WalletKit.SubmitPackage RPC. --- cmd/commands/walletrpc_active.go | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/cmd/commands/walletrpc_active.go b/cmd/commands/walletrpc_active.go index 4d6c28134e0..126a1340efa 100644 --- a/cmd/commands/walletrpc_active.go +++ b/cmd/commands/walletrpc_active.go @@ -87,6 +87,7 @@ func walletCommands() []cli.Command { listSweepsCommand, labelTxCommand, publishTxCommand, + submitPackageCommand, getTxCommand, removeTxCommand, releaseOutputCommand, @@ -715,6 +716,77 @@ func publishTransaction(ctx *cli.Context) error { return nil } +var submitPackageCommand = cli.Command{ + Name: "submitpackage", + Usage: "Submit a package of related transactions for atomic " + + "validation and acceptance.", + ArgsUsage: "parent_tx_hex... child_tx_hex", + Description: ` + Submit a package of related, topologically-sorted raw transactions + (unconfirmed parents first and the child last) to the chain backend + for atomic validation and acceptance via the submitpackage RPC. + + This allows a zero-fee v3/TRUC parent to be accepted via its + fee-paying CPFP child, which a standalone broadcast would reject. + Each argument is a hex-encoded raw transaction. + `, + Flags: []cli.Flag{ + cli.Uint64Flag{ + Name: "sat_per_vbyte", + Usage: "(optional) the maximum fee rate in sat/vByte " + + "allowed for any transaction in the package; " + + "omit to use the node default, set 0 to " + + "disable the limit", + }, + }, + Action: actionDecorator(submitPackage), +} + +func submitPackage(ctx *cli.Context) error { + ctxc := getContext() + + // Display the command's help message if we do not have at least one + // transaction. + if ctx.NArg() == 0 { + return cli.ShowCommandHelp(ctx, "submitpackage") + } + + walletClient, cleanUp := getWalletClient(ctx) + defer cleanUp() + + rawTxs := make([][]byte, 0, ctx.NArg()) + for _, arg := range ctx.Args() { + tx, err := hex.DecodeString(arg) + if err != nil { + return err + } + + rawTxs = append(rawTxs, tx) + } + + // Only set the max fee rate when explicitly provided; otherwise leave + // it unset so the node applies its default. + var satPerVByte *uint64 + if ctx.IsSet("sat_per_vbyte") { + rate := ctx.Uint64("sat_per_vbyte") + satPerVByte = &rate + } + + resp, err := walletClient.SubmitPackage( + ctxc, &walletrpc.SubmitPackageRequest{ + RawTxs: rawTxs, + SatPerVbyte: satPerVByte, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + var getTxCommand = cli.Command{ Name: "gettx", Usage: "Returns details of a transaction.", From 47661cea868c5a1374d4a427507925c7b653fa81 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 11 Jun 2026 13:35:23 -0700 Subject: [PATCH 3/4] itest: add SubmitPackage integration test Add an integration test that exercises WalletKit.SubmitPackage: it builds a zero-fee v3 (TRUC) parent that a standalone broadcast would reject, pairs it with a fee-paying v3 CPFP child, and asserts the package is accepted. A zero-fee transaction can only enter the mempool via package evaluation, so this proves the CPFP package path end to end. submitpackage is a bitcoind RPC, so the test skips on the btcd and neutrino backends. Also adds the SubmitPackage wrapper to the integration-test RPC harness. --- itest/list_on_test.go | 4 + itest/lnd_submit_package_test.go | 209 +++++++++++++++++++++++++++++++ lntest/rpc/wallet_kit.go | 13 ++ 3 files changed, 226 insertions(+) create mode 100644 itest/lnd_submit_package_test.go diff --git a/itest/list_on_test.go b/itest/list_on_test.go index d5b3d5051d1..b0e7ec4000d 100644 --- a/itest/list_on_test.go +++ b/itest/list_on_test.go @@ -495,6 +495,10 @@ var allTestCases = []*lntest.TestCase{ Name: "sign output raw", TestFunc: testSignOutputRaw, }, + { + Name: "submit package", + TestFunc: testSubmitPackage, + }, { Name: "sign verify message", TestFunc: testSignVerifyMessage, diff --git a/itest/lnd_submit_package_test.go b/itest/lnd_submit_package_test.go new file mode 100644 index 00000000000..c995220dc63 --- /dev/null +++ b/itest/lnd_submit_package_test.go @@ -0,0 +1,209 @@ +package itest + +import ( + "bytes" + + btcaddr "github.com/btcsuite/btcd/address/v2" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/txscript/v2" + "github.com/btcsuite/btcd/wire/v2" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" +) + +// testSubmitPackage tests that the WalletKit.SubmitPackage RPC relays a v3 +// (TRUC) transaction package: a zero-fee parent that would be rejected by a +// standalone broadcast (below the minimum relay fee) is accepted together with +// a fee-paying CPFP child whose combined package feerate clears policy. +// +// This requires a bitcoind chain backend, as btcd has no submitpackage RPC and +// cannot relay zero-fee v3 transactions; run with backend=bitcoind. The +// zero-fee parent can only enter the mempool via package evaluation (a +// standalone submission is rejected for the min relay fee), so a successful +// SubmitPackage proves the CPFP package path worked end to end. +func testSubmitPackage(ht *lntest.HarnessTest) { + // submitpackage is a bitcoind RPC: btcd has no equivalent and neutrino + // has no mempool, so this test only applies to the bitcoind backend. + if ht.ChainBackendName() != "bitcoind" { + ht.Skipf("submitpackage requires the bitcoind backend, got %v", + ht.ChainBackendName()) + } + + // The zero-fee v3 parent only propagates to (and is observable in) the + // mempool of a package-relay-capable node, so the miner must also be + // bitcoind. With the default btcd miner the package is submitted to + // Alice's bitcoind successfully but never relays to the miner, so the + // mempool assertions below would time out. + if ht.Miner().BackendName() != "bitcoind" { + ht.Skipf("submitpackage requires a bitcoind miner for the "+ + "zero-fee v3 package to relay, got %v miner", + ht.Miner().BackendName()) + } + + alice := ht.NewNodeWithCoins("Alice", nil) + + const ( + fundAmt = int64(btcutil.SatoshiPerBitcoin) + + // childFee is paid by the child for the whole package. It + // must cover both transactions' weight at >= the min relay + // fee; a few thousand sats is comfortably above that. + childFee = int64(20_000) + + // p2wkhKeyFamily is a custom key family so the derived keys + // (and thus the addresses we control via SignOutputRaw) are + // independent of the node's normal key usage. + p2wkhKeyFamily = 44 + ) + + // p2wkhKey derives a fresh key and returns it together with the p2wkh + // address/pkScript it controls, which we can later spend via the + // SignOutputRaw RPC. + p2wkhKey := func() (*signrpc.KeyDescriptor, *btcec.PublicKey, + btcaddr.Address, []byte) { + + keyDesc := alice.RPC.DeriveNextKey(&walletrpc.KeyReq{ + KeyFamily: p2wkhKeyFamily, + }) + + pubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes) + require.NoError(ht, err) + + addr, err := btcaddr.NewAddressWitnessPubKeyHash( + btcaddr.Hash160(pubKey.SerializeCompressed()), + harnessNetParams, + ) + require.NoError(ht, err) + + pkScript, err := txscript.PayToAddrScript(addr) + require.NoError(ht, err) + + return keyDesc, pubKey, addr, pkScript + } + + // signP2WKHInput signs input idx of tx (spending a p2wkh output + // with the given pkScript and value) via SignOutputRaw and attaches + // the witness. + signP2WKHInput := func(tx *wire.MsgTx, idx int, pkScript []byte, + value int64, keyDesc *signrpc.KeyDescriptor, + pubKey *btcec.PublicKey) { + + var buf bytes.Buffer + require.NoError(ht, tx.Serialize(&buf)) + + signResp := alice.RPC.SignOutputRaw(&signrpc.SignReq{ + RawTxBytes: buf.Bytes(), + SignDescs: []*signrpc.SignDescriptor{{ + Output: &signrpc.TxOut{ + PkScript: pkScript, + Value: value, + }, + InputIndex: int32(idx), + KeyDesc: keyDesc, + Sighash: uint32(txscript.SigHashAll), + WitnessScript: pkScript, + }}, + }) + + tx.TxIn[idx].Witness = wire.TxWitness{ + append(signResp.RawSigs[0], byte(txscript.SigHashAll)), + pubKey.SerializeCompressed(), + } + } + + serialize := func(tx *wire.MsgTx) []byte { + var buf bytes.Buffer + require.NoError(ht, tx.Serialize(&buf)) + + return buf.Bytes() + } + + // Fund a p2wkh output we control: send coins to a key-derived + // address and confirm it, so the parent has a confirmed input to spend. + parentInKey, parentInPub, parentInAddr, parentInScript := p2wkhKey() + alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{ + Addr: parentInAddr.String(), + Amount: fundAmt, + TargetConf: 6, + }) + fundTxid := ht.AssertNumTxsInMempool(1)[0] + fundOutIdx := ht.GetOutputIndex(fundTxid, parentInAddr.String()) + ht.MineBlocksAndAssertNumTxes(1, 1) + + // The child will spend the parent's output, so derive a key we control + // for it and use its script as the parent's output. + childInKey, childInPub, _, childInScript := p2wkhKey() + + // Build the zero-fee v3 parent: spend the confirmed input and pay the + // full value to the child-input script, leaving no fee. + parent := wire.NewMsgTx(3) + parent.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: fundTxid, + Index: uint32(fundOutIdx), + }, + }) + parent.AddTxOut(wire.NewTxOut(fundAmt, childInScript)) + signP2WKHInput( + parent, 0, parentInScript, fundAmt, parentInKey, parentInPub, + ) + + // Build the v3 CPFP child: spend the parent's unconfirmed output + // and pay childFee, which covers the whole package. + childOut := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{ + Type: AddrTypeWitnessPubkeyHash, + }) + childOutAddr, err := btcaddr.DecodeAddress( + childOut.Address, harnessNetParams, + ) + require.NoError(ht, err) + childOutScript, err := txscript.PayToAddrScript(childOutAddr) + require.NoError(ht, err) + + child := wire.NewMsgTx(3) + child.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: parent.TxHash(), + Index: 0, + }, + }) + child.AddTxOut(wire.NewTxOut(fundAmt-childFee, childOutScript)) + signP2WKHInput(child, 0, childInScript, fundAmt, childInKey, childInPub) + + // Submit the two transactions as a package. A max fee rate of 0 + // disables the fee-rate ceiling so a high-feerate CPFP child is + // never rejected. + noFeeLimit := uint64(0) + resp := alice.RPC.SubmitPackage(&walletrpc.SubmitPackageRequest{ + RawTxs: [][]byte{serialize(parent), serialize(child)}, + SatPerVbyte: &noFeeLimit, + }) + + // The whole package must be accepted, with a per-tx result (keyed by + // wtxid) for each transaction and no per-tx error. + require.Equal(ht, "success", resp.PackageMsg) + require.Len(ht, resp.TxResults, 2) + for _, txResult := range resp.TxResults { + require.Emptyf( + ht, txResult.Error, "tx %s rejected", txResult.Txid, + ) + } + + // The accepted package must now be in the mempool: both the zero-fee + // parent and its fee-paying CPFP child. This proves the package + // actually relayed, not merely that the RPC returned success. + ht.AssertTxInMempool(parent.TxHash()) + ht.AssertTxInMempool(child.TxHash()) + + // Mine the package so it confirms (the strongest end-to-end proof the + // CPFP package relayed) and the mempool is clean for the harness's + // end-of-test teardown check. Both the parent and child must land in + // the mined block. + block := ht.MineBlocksAndAssertNumTxes(1, 2) + ht.AssertTxInBlock(block[0], parent.TxHash()) + ht.AssertTxInBlock(block[0], child.TxHash()) +} diff --git a/lntest/rpc/wallet_kit.go b/lntest/rpc/wallet_kit.go index ffa5acdd01a..1251555c210 100644 --- a/lntest/rpc/wallet_kit.go +++ b/lntest/rpc/wallet_kit.go @@ -208,6 +208,19 @@ func (h *HarnessRPC) PublishTransaction( return resp } +// SubmitPackage makes a RPC call to the node's WalletKitClient and asserts. +func (h *HarnessRPC) SubmitPackage( + req *walletrpc.SubmitPackageRequest) *walletrpc.SubmitPackageResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.WalletKit.SubmitPackage(ctxt, req) + h.NoError(err, "SubmitPackage") + + return resp +} + // GetTransaction makes a RPC call to the node's WalletKitClient and asserts. func (h *HarnessRPC) GetTransaction( req *walletrpc.GetTransactionRequest) *lnrpc.Transaction { From 68c47407d7b8ebc2f290d5e9cc5b115558457cb8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 18 Jun 2026 08:25:05 -0700 Subject: [PATCH 4/4] docs: add release note for WalletKit.SubmitPackage Document the new WalletKit.SubmitPackage RPC and the lncli wallet submitpackage command in the 0.22.0 release notes. --- docs/release-notes/release-notes-0.22.0.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/release-notes/release-notes-0.22.0.md b/docs/release-notes/release-notes-0.22.0.md index 2f7f0ca1b7e..f802501ca65 100644 --- a/docs/release-notes/release-notes-0.22.0.md +++ b/docs/release-notes/release-notes-0.22.0.md @@ -53,6 +53,12 @@ channels](https://github.com/lightningnetwork/lnd/pull/10501) via the new `outgoing_chan_ids` field in `RouteFeeRequest`. +* A new + [`walletrpc.SubmitPackage`](https://github.com/lightningnetwork/lnd/pull/10900) + RPC submits a package of related transactions (parents first, child last) to + the chain backend via bitcoind's `submitpackage`, allowing a zero-fee v3/TRUC + parent to be accepted together with a fee-paying CPFP child. + ## lncli Additions * The `estimateroutefee` command now supports [restricting fee estimates to @@ -60,6 +66,11 @@ channels](https://github.com/lightningnetwork/lnd/pull/10501) via the new `--outgoing_chan_id` flag. +* A new + [`wallet submitpackage`](https://github.com/lightningnetwork/lnd/pull/10900) + command submits a package of hex-encoded transactions via the new + `SubmitPackage` RPC. + # Improvements ## Functional Updates