From f72981e5f48950fd20d52c992b61dd452360aef9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 24 Jul 2024 18:28:46 +0200 Subject: [PATCH 1/5] mod: bump lndclient version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9687e56ac..e9ed5aa33 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/lib/pq v1.10.9 github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030 github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 - github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf + github.com/lightninglabs/lndclient v1.0.1-0.20240725080034-64a756aa4c36 github.com/lightninglabs/neutrino/cache v1.1.2 github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4 github.com/lightningnetwork/lnd/cert v1.2.2 diff --git a/go.sum b/go.sum index f4c25a08d..d9159df35 100644 --- a/go.sum +++ b/go.sum @@ -480,8 +480,8 @@ github.com/lightninglabs/lightning-node-connect v0.2.5-alpha h1:ZRVChwczFXK0CEbx github.com/lightninglabs/lightning-node-connect v0.2.5-alpha/go.mod h1:A9Pof9fETkH+F67BnOmrBDThPKstqp73wlImWOZvTXQ= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 h1:Er1miPZD2XZwcfE4xoS5AILqP1mj7kqnhbBSxW9BDxY= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4= -github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf h1:VcTK/juPtAqwEBckCcSHCsVRSbHGbWtDZgnXL5JOLkg= -github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= +github.com/lightninglabs/lndclient v1.0.1-0.20240725080034-64a756aa4c36 h1:gfJ3TOuqSnuXEo1Boj1H9P6tpxPSH9cvi+rB10L0svI= +github.com/lightninglabs/lndclient v1.0.1-0.20240725080034-64a756aa4c36/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= From 266165a75bdcd8960458f92cf82a92f8f737b679 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 24 Jul 2024 18:29:14 +0200 Subject: [PATCH 2/5] taprpc: allow importing/referencing lnd proto files This commit allows us to use the gRPC import statement to re-use lnd protobuf definitions. We'll use that to be able to embed lnd RPC messages in some of the new tapchannelrpc calls. --- taprpc/Dockerfile | 3 +++ taprpc/gen_protos.sh | 5 +++++ taprpc/gen_protos_docker.sh | 2 ++ 3 files changed, 10 insertions(+) diff --git a/taprpc/Dockerfile b/taprpc/Dockerfile index 33144798f..ee1248c51 100644 --- a/taprpc/Dockerfile +++ b/taprpc/Dockerfile @@ -10,11 +10,13 @@ RUN apt-get update && apt-get install -y \ # update them here if we bump the versions. ARG PROTOBUF_VERSION ARG GRPC_GATEWAY_VERSION +ARG LND_VERSION ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0" ENV FALAFEL_VERSION="v0.9.1" ENV GOCACHE=/tmp/build/.cache ENV GOMODCACHE=/tmp/build/.modcache +ENV LND_VERSION=${LND_VERSION} RUN cd /tmp \ && mkdir -p /tmp/build/.cache \ @@ -25,6 +27,7 @@ RUN cd /tmp \ && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} \ && go install github.com/lightninglabs/falafel@${FALAFEL_VERSION} \ && go install golang.org/x/tools/cmd/goimports@v0.1.7 \ + && go mod download github.com/lightningnetwork/lnd@${LND_VERSION} \ && chmod -R 777 /tmp/build/ WORKDIR /build diff --git a/taprpc/gen_protos.sh b/taprpc/gen_protos.sh index fe03f3f40..46d8f5ae0 100755 --- a/taprpc/gen_protos.sh +++ b/taprpc/gen_protos.sh @@ -19,6 +19,7 @@ tapchannelrpc/tapchannel.proto" # Generate the protos. protoc -I/usr/local/include -I. \ + -I/tmp/build/.modcache/github.com/lightningnetwork/lnd@${LND_VERSION}/lnrpc \ --go_out . --go_opt paths=source_relative \ --go-grpc_out . --go-grpc_opt paths=source_relative \ "${file}" @@ -26,6 +27,7 @@ tapchannelrpc/tapchannel.proto" # Generate the REST reverse proxy. annotationsFile=${file//proto/yaml} protoc -I/usr/local/include -I. \ + -I/tmp/build/.modcache/github.com/lightningnetwork/lnd@${LND_VERSION}/lnrpc \ --grpc-gateway_out . \ --grpc-gateway_opt logtostderr=true \ --grpc-gateway_opt paths=source_relative \ @@ -34,6 +36,7 @@ tapchannelrpc/tapchannel.proto" # Generate the swagger file which describes the REST API in detail. protoc -I/usr/local/include -I. \ + -I/tmp/build/.modcache/github.com/lightningnetwork/lnd@${LND_VERSION}/lnrpc \ --openapiv2_out . \ --openapiv2_opt logtostderr=true \ --openapiv2_opt grpc_api_configuration=${annotationsFile} \ @@ -46,6 +49,7 @@ tapchannelrpc/tapchannel.proto" pkg="taprpc" opts="package_name=$pkg,js_stubs=1" protoc -I/usr/local/include -I. -I.. \ + -I/tmp/build/.modcache/github.com/lightningnetwork/lnd@${LND_VERSION}/lnrpc \ --plugin=protoc-gen-custom=$falafel\ --custom_out=. \ --custom_opt="$opts" \ @@ -58,6 +62,7 @@ tapchannelrpc/tapchannel.proto" opts="package_name=$package,manual_import=$manual_import,js_stubs=1" pushd $package protoc -I/usr/local/include -I. -I.. \ + -I/tmp/build/.modcache/github.com/lightningnetwork/lnd@${LND_VERSION}/lnrpc \ --plugin=protoc-gen-custom=$falafel\ --custom_out=. \ --custom_opt="$opts" \ diff --git a/taprpc/gen_protos_docker.sh b/taprpc/gen_protos_docker.sh index 6c3bed551..41993cacb 100755 --- a/taprpc/gen_protos_docker.sh +++ b/taprpc/gen_protos_docker.sh @@ -7,11 +7,13 @@ DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)" PROTOBUF_VERSION=$(go list -f '{{.Version}}' -m google.golang.org/protobuf) GRPC_GATEWAY_VERSION=$(go list -f '{{.Version}}' -m github.com/grpc-ecosystem/grpc-gateway/v2) +LND_VERSION=$(go list -f '{{.Version}}' -m github.com/lightningnetwork/lnd) echo "Building protobuf compiler docker image..." docker build -t taproot-assets-protobuf-builder \ --build-arg PROTOBUF_VERSION="$PROTOBUF_VERSION" \ --build-arg GRPC_GATEWAY_VERSION="$GRPC_GATEWAY_VERSION" \ + --build-arg LND_VERSION="$LND_VERSION" \ . echo "Compiling and formatting *.proto files..." From db317998eecc9efbdef9e13d2a799c79f3ddbcd6 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 29 Jul 2024 13:22:48 +0200 Subject: [PATCH 3/5] multi: add SendPayment RPC method --- perms/perms.go | 4 + rpcserver.go | 325 ++++++++ taprpc/tapchannelrpc/tapchannel.pb.go | 429 ++++++++-- taprpc/tapchannelrpc/tapchannel.pb.gw.go | 58 ++ taprpc/tapchannelrpc/tapchannel.proto | 58 +- taprpc/tapchannelrpc/tapchannel.swagger.json | 764 ++++++++++++++++++ taprpc/tapchannelrpc/tapchannel.yaml | 3 + taprpc/tapchannelrpc/tapchannel_grpc.pb.go | 74 +- .../taprootassetchannels.pb.json.go | 42 + 9 files changed, 1666 insertions(+), 91 deletions(-) diff --git a/perms/perms.go b/perms/perms.go index 9fcfa6ad3..13c5f5f2b 100644 --- a/perms/perms.go +++ b/perms/perms.go @@ -268,6 +268,10 @@ var ( Entity: "channels", Action: "write", }}, + "/tapchannelrpc.TaprootAssetChannels/SendPayment": {{ + Entity: "channels", + Action: "write", + }}, "/tapchannelrpc.TaprootAssetChannels/EncodeCustomRecords": { // This RPC is completely stateless and doesn't require // any permissions to use. diff --git a/rpcserver.go b/rpcserver.go index f62dd53bb..9d67e4e03 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -54,9 +55,11 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/tlv" + "github.com/lightningnetwork/lnd/zpay32" "golang.org/x/exp/maps" "golang.org/x/time/rate" "google.golang.org/grpc" @@ -6730,6 +6733,219 @@ func (r *rpcServer) EncodeCustomRecords(_ context.Context, } } +// SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method +// with asset specific parameters. It allows RPC users to send asset keysend +// payments (direct payments) or payments to an invoice with a specified asset +// amount. +func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, + stream tchrpc.TaprootAssetChannels_SendPaymentServer) error { + + if req.PaymentRequest == nil { + return fmt.Errorf("payment request must be specified") + } + pReq := req.PaymentRequest + ctx := stream.Context() + + // Do some preliminary checks on the asset ID and make sure we have any + // balance for that asset. + if len(req.AssetId) != sha256.Size { + return fmt.Errorf("asset ID must be 32 bytes") + } + var assetID asset.ID + copy(assetID[:], req.AssetId) + + // Now that we know we have at least _some_ asset balance, we'll figure + // out what kind of payment this is, so we can determine _how many_ + // asset units we need. + destRecords := pReq.DestCustomRecords + _, isKeysend := destRecords[record.KeySendType] + firstHopRecords := pReq.FirstHopCustomRecords + + switch { + // Both payment request and keysend is set, which isn't a supported + // combination. + case pReq.PaymentRequest != "" && isKeysend: + return fmt.Errorf("payment request and keysend custom " + + "records cannot be set at the same time") + + // There are custom records for the first hop set, which means RFQ + // negotiation has already happened (or this is a keysend payment and + // the correct asset amount is already encoded). So we don't need to do + // anything special and can just forward the payment to lnd. + case len(firstHopRecords) > 0: + // Continue below. + + // The request wants to pay a specific invoice. + case pReq.PaymentRequest != "": + invoice, err := zpay32.Decode( + pReq.PaymentRequest, r.cfg.Lnd.ChainParams, + ) + if err != nil { + return fmt.Errorf("error decoding payment request: %w", + err) + } + + // The peer public key is optional if there is only a single + // asset channel. + var peerPubKey *route.Vertex + if len(req.PeerPubkey) > 0 { + parsedKey, err := route.NewVertexFromBytes( + req.PeerPubkey, + ) + if err != nil { + return fmt.Errorf("error parsing peer pubkey: "+ + "%w", err) + } + + peerPubKey = &parsedKey + } + + // We can now query the asset channels we have. + assetChan, err := r.rfqChannel(ctx, assetID, peerPubKey) + if err != nil { + return fmt.Errorf("error finding asset channel to "+ + "use: %w", err) + } + + // Even if the user didn't specify the peer public key before, + // we definitely know it now. So let's make sure it's always + // set. + peerPubKey = &assetChan.channelInfo.PubKeyBytes + + // TODO(guggero): This should actually be the max BTC amount + // (invoice amount plus fee limit) in milli-satoshi, not the + // asset amount. Need to change the whole RFQ API to do that + // though. + maxAssetAmount := assetChan.assetInfo.LocalBalance + + expiryTimestamp := invoice.Timestamp.Add(invoice.Expiry()) + resp, err := r.AddAssetSellOrder( + ctx, &rfqrpc.AddAssetSellOrderRequest{ + AssetSpecifier: &rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_AssetId{ + AssetId: assetID[:], + }, + }, + MaxAssetAmount: maxAssetAmount, + MinAsk: uint64(*invoice.MilliSat), + Expiry: uint64(expiryTimestamp.Unix()), + PeerPubKey: peerPubKey[:], + TimeoutSeconds: uint32( + rfq.DefaultTimeout.Seconds(), + ), + }, + ) + if err != nil { + return fmt.Errorf("error adding sell order: %w", err) + } + + var acceptedQuote *rfqrpc.PeerAcceptedSellQuote + switch r := resp.Response.(type) { + case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote: + acceptedQuote = r.AcceptedQuote + + case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote: + return fmt.Errorf("peer %v sent back an invalid "+ + "quote, status: %v", r.InvalidQuote.Peer, + r.InvalidQuote.Status.String()) + + case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote: + return fmt.Errorf("peer %v rejected the quote, code: "+ + "%v, error message: %v", r.RejectedQuote.Peer, + r.RejectedQuote.ErrorCode, + r.RejectedQuote.ErrorMessage) + + default: + return fmt.Errorf("unexpected response type: %T", r) + } + + // Send out the information about the quote on the stream. + err = stream.Send(&tchrpc.SendPaymentResponse{ + Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{ + AcceptedSellOrder: acceptedQuote, + }, + }) + if err != nil { + return err + } + + msatPerUnit := acceptedQuote.BidPrice + numUnits := uint64(*invoice.MilliSat) / msatPerUnit + rpcsLog.Infof("Got quote for %v asset units at %v msat/unit "+ + "from peer %x with SCID %d", numUnits, msatPerUnit, + peerPubKey, acceptedQuote.Scid) + + var rfqID rfqmsg.ID + copy(rfqID[:], acceptedQuote.Id) + + htlc := rfqmsg.NewHtlc(nil, fn.Some(rfqID)) + + // We'll now map the HTLC struct into a set of TLV records, + // which we can then encode into the expected map format. + htlcMapRecords, err := tlv.RecordsToMap(htlc.Records()) + if err != nil { + return fmt.Errorf("unable to encode records as map: %w", + err) + } + + pReq.FirstHopCustomRecords = htlcMapRecords + + // The payment request is a keysend payment. + case isKeysend: + if req.AssetAmount == 0 { + return fmt.Errorf("asset amount must be specified " + + "for keysend payment") + } + + balances := []*rfqmsg.AssetBalance{ + rfqmsg.NewAssetBalance(assetID, req.AssetAmount), + } + htlc := rfqmsg.NewHtlc(balances, fn.None[rfqmsg.ID]()) + + // We'll now map the HTLC struct into a set of TLV records, + // which we can then encode into the map format expected. + htlcMapRecords, err := tlv.RecordsToMap(htlc.Records()) + if err != nil { + return fmt.Errorf("unable to encode records as map: %w", + err) + } + + pReq.FirstHopCustomRecords = htlcMapRecords + } + + rpcCtx, _, routerClient := r.cfg.Lnd.Router.RawClientWithMacAuth(ctx) + updateStream, err := routerClient.SendPaymentV2(rpcCtx, pReq) + if err != nil { + return err + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + + case <-r.quit: + return fmt.Errorf("server shutting down") + + default: + } + + update, err := updateStream.Recv() + if err != nil { + return err + } + + err = stream.Send(&tchrpc.SendPaymentResponse{ + Result: &tchrpc.SendPaymentResponse_PaymentResult{ + PaymentResult: update, + }, + }) + if err != nil { + return err + } + } +} + // DeclareScriptKey declares a new script key to the wallet. This is useful // when the script key contains scripts, which would mean it wouldn't be // recognized by the wallet automatically. Declaring a script key will make any @@ -6841,3 +7057,112 @@ func getDecimalDisplayNonStrict( return fn.Some(decDisplay), nil } + +// rfqChannel returns the channel to use for RFQ operations. If a peer public +// key is specified, the channels are filtered by that peer. If there are +// multiple channels for the same asset, the user must specify the peer public +// key. +func (r *rpcServer) rfqChannel(ctx context.Context, id asset.ID, + peerPubKey *route.Vertex) (*channelWithAsset, error) { + + balances, err := r.computeChannelAssetBalance(ctx) + if err != nil { + return nil, fmt.Errorf("error computing available asset "+ + "channel balance: %w", err) + } + + assetBalances, haveBalance := balances[id] + if !haveBalance || len(assetBalances) == 0 { + return nil, fmt.Errorf("no asset channel balance found for "+ + "asset %s", id.String()) + } + + // If a peer public key was specified, we always want to use that to + // filter the asset channels. + if peerPubKey != nil { + assetBalances = fn.Filter( + assetBalances, func(c channelWithAsset) bool { + return c.channelInfo.PubKeyBytes == *peerPubKey + }, + ) + } + + switch { + // If there are multiple asset channels for the same asset, we need to + // ask the user to specify the peer public key. Otherwise, we don't know + // who to ask for a quote. + case len(assetBalances) > 1 && peerPubKey == nil: + return nil, fmt.Errorf("multiple asset channels found for "+ + "asset %s, please specify the peer pubkey", id.String()) + + // If the user specified a peer public key, and we still have multiple + // channels, it means we have multiple channels with the same asset and + // the same peer. So we just return the first channel. + case len(assetBalances) >= 1: + return &assetBalances[0], nil + + // The default case is: We don't have any channels with that asset ID + // and peer. + default: + return nil, fmt.Errorf("no asset channel found for asset %s "+ + "and peer %s", id.String(), peerPubKey.String()) + } +} + +// channelWithAsset is a helper struct that combines the information of a single +// asset within a channel with the channels' general information. +type channelWithAsset struct { + // assetInfo is the information about one of the assets in a channel. + assetInfo rfqmsg.JsonAssetChanInfo + + // channelInfo is the information about the channel the asset is + // committed to. + channelInfo lndclient.ChannelInfo +} + +// computeChannelAssetBalance computes the total local and remote balance for +// each asset channel. +func (r *rpcServer) computeChannelAssetBalance( + ctx context.Context) (map[asset.ID][]channelWithAsset, error) { + + activeChannels, err := r.cfg.Lnd.Client.ListChannels(ctx, true, false) + if err != nil { + return nil, fmt.Errorf("unable to fetch channels: %w", err) + } + + channelsByID := make(map[asset.ID][]channelWithAsset) + for chanIdx := range activeChannels { + openChan := activeChannels[chanIdx] + if len(openChan.CustomChannelData) == 0 { + continue + } + + var assetData rfqmsg.JsonAssetChannel + err = json.Unmarshal(openChan.CustomChannelData, &assetData) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal asset "+ + "data: %w", err) + } + + for assetIdx := range assetData.Assets { + assetOutput := assetData.Assets[assetIdx] + assetIDStr := assetOutput.AssetInfo.AssetGenesis.AssetID + assetIDBytes, err := hex.DecodeString(assetIDStr) + if err != nil { + return nil, fmt.Errorf("error decoding asset "+ + "ID: %w", err) + } + var assetID asset.ID + copy(assetID[:], assetIDBytes) + + channelsByID[assetID] = append( + channelsByID[assetID], channelWithAsset{ + assetInfo: assetOutput, + channelInfo: openChan, + }, + ) + } + } + + return channelsByID, nil +} diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index baee599d1..bcc54cc48 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -7,6 +7,9 @@ package tapchannelrpc import ( + rfqrpc "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + routerrpc "github.com/lightningnetwork/lnd/lnrpc/routerrpc" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -342,81 +345,291 @@ func (x *EncodeCustomRecordsResponse) GetCustomRecords() map[uint64][]byte { return nil } +type SendPaymentRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The asset ID to use for the payment. This must be set for both invoice + // and keysend payments, unless RFQ negotiation was already done beforehand + // and payment_request.first_hop_custom_records already contains valid RFQ + // data. + AssetId []byte `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + // The asset amount to send in a keysend payment. This amount is ignored for + // invoice payments as the asset amount is negotiated through RFQ with the + // peer, depending on the invoice amount. This can also be left unset if RFQ + // negotiation was already done beforehand and + // payment_request.first_hop_custom_records already contains valid RFQ data. + AssetAmount uint64 `protobuf:"varint,2,opt,name=asset_amount,json=assetAmount,proto3" json:"asset_amount,omitempty"` + // The node identity public key of the peer to ask for a quote for sending + // out the assets and converting them to satoshis. This must be specified if + // there are multiple channels with the given asset ID. + PeerPubkey []byte `protobuf:"bytes,3,opt,name=peer_pubkey,json=peerPubkey,proto3" json:"peer_pubkey,omitempty"` + // The full lnd payment request to send. All fields behave the same way as + // they do for lnd's routerrpc.SendPaymentV2 RPC method (see the API docs + // at https://lightning.engineering/api-docs/api/lnd/router/send-payment-v2 + // for more details). + // To send a keysend payment, the payment_request.dest_custom_records must + // contain a valid keysend record (key 5482373484 and a 32-byte preimage + // that corresponds to the payment hash). + PaymentRequest *routerrpc.SendPaymentRequest `protobuf:"bytes,4,opt,name=payment_request,json=paymentRequest,proto3" json:"payment_request,omitempty"` +} + +func (x *SendPaymentRequest) Reset() { + *x = SendPaymentRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendPaymentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendPaymentRequest) ProtoMessage() {} + +func (x *SendPaymentRequest) ProtoReflect() protoreflect.Message { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendPaymentRequest.ProtoReflect.Descriptor instead. +func (*SendPaymentRequest) Descriptor() ([]byte, []int) { + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{5} +} + +func (x *SendPaymentRequest) GetAssetId() []byte { + if x != nil { + return x.AssetId + } + return nil +} + +func (x *SendPaymentRequest) GetAssetAmount() uint64 { + if x != nil { + return x.AssetAmount + } + return 0 +} + +func (x *SendPaymentRequest) GetPeerPubkey() []byte { + if x != nil { + return x.PeerPubkey + } + return nil +} + +func (x *SendPaymentRequest) GetPaymentRequest() *routerrpc.SendPaymentRequest { + if x != nil { + return x.PaymentRequest + } + return nil +} + +type SendPaymentResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Result: + // + // *SendPaymentResponse_AcceptedSellOrder + // *SendPaymentResponse_PaymentResult + Result isSendPaymentResponse_Result `protobuf_oneof:"result"` +} + +func (x *SendPaymentResponse) Reset() { + *x = SendPaymentResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendPaymentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendPaymentResponse) ProtoMessage() {} + +func (x *SendPaymentResponse) ProtoReflect() protoreflect.Message { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendPaymentResponse.ProtoReflect.Descriptor instead. +func (*SendPaymentResponse) Descriptor() ([]byte, []int) { + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{6} +} + +func (m *SendPaymentResponse) GetResult() isSendPaymentResponse_Result { + if m != nil { + return m.Result + } + return nil +} + +func (x *SendPaymentResponse) GetAcceptedSellOrder() *rfqrpc.PeerAcceptedSellQuote { + if x, ok := x.GetResult().(*SendPaymentResponse_AcceptedSellOrder); ok { + return x.AcceptedSellOrder + } + return nil +} + +func (x *SendPaymentResponse) GetPaymentResult() *lnrpc.Payment { + if x, ok := x.GetResult().(*SendPaymentResponse_PaymentResult); ok { + return x.PaymentResult + } + return nil +} + +type isSendPaymentResponse_Result interface { + isSendPaymentResponse_Result() +} + +type SendPaymentResponse_AcceptedSellOrder struct { + // In case channel assets need to be swapped to another asset, an asset + // sell order is negotiated with the channel peer. The result will be + // the first message in the response stream. If no swap is needed, the + // payment results will be streamed directly. + AcceptedSellOrder *rfqrpc.PeerAcceptedSellQuote `protobuf:"bytes,1,opt,name=accepted_sell_order,json=acceptedSellOrder,proto3,oneof"` +} + +type SendPaymentResponse_PaymentResult struct { + // The payment result of a single payment attempt. Multiple attempts may + // be returned per payment request until either the payment succeeds or + // the payment times out. + PaymentResult *lnrpc.Payment `protobuf:"bytes,2,opt,name=payment_result,json=paymentResult,proto3,oneof"` +} + +func (*SendPaymentResponse_AcceptedSellOrder) isSendPaymentResponse_Result() {} + +func (*SendPaymentResponse_PaymentResult) isSendPaymentResponse_Result() {} + var File_tapchannelrpc_tapchannel_proto protoreflect.FileDescriptor var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x0a, 0x1e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x0d, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x22, - 0xc2, 0x01, 0x0a, 0x12, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, - 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, - 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x16, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, - 0x65, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x53, 0x61, - 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, - 0x68, 0x5f, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, - 0x68, 0x53, 0x61, 0x74, 0x22, 0x4c, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x22, 0xcc, 0x01, 0x0a, 0x15, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, - 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x5b, 0x0a, 0x0d, - 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, 0x73, 0x73, - 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x66, 0x71, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x66, 0x71, 0x49, 0x64, - 0x1a, 0x3f, 0x0a, 0x11, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x7d, 0x0a, 0x1a, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x56, 0x0a, 0x13, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, - 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, - 0x74, 0x61, 0x48, 0x00, 0x52, 0x11, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x22, 0xc5, 0x01, 0x0a, 0x1b, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, - 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x64, 0x0a, 0x0e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, - 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xda, 0x01, 0x0a, 0x14, 0x54, 0x61, 0x70, - 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, - 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, - 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, + 0x12, 0x0d, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x1a, + 0x10, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x66, 0x71, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x16, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc2, 0x01, 0x0a, 0x12, 0x46, + 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x12, 0x32, 0x0a, 0x16, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x62, 0x79, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x12, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, + 0x62, 0x79, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x73, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x70, 0x75, 0x73, 0x68, 0x53, 0x61, 0x74, 0x22, + 0x4c, 0x0a, 0x13, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xcc, 0x01, + 0x0a, 0x15, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x5b, 0x0a, 0x0d, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, + 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x44, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x66, 0x71, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x66, 0x71, 0x49, 0x64, 0x1a, 0x3f, 0x0a, 0x11, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7d, 0x0a, 0x1a, + 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x13, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, + 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, + 0x11, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0xc5, 0x01, 0x0a, 0x1b, + 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0e, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0xbb, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, + 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, + 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x46, 0x0a, 0x0f, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x52, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x13, 0x61, 0x63, 0x63, + 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x6c, 0x6c, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x48, 0x00, 0x52, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x53, 0x65, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0e, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xb2, 0x02, + 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, + 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, - 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, - 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, - 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, + 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, + 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, + 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, + 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -431,29 +644,39 @@ func file_tapchannelrpc_tapchannel_proto_rawDescGZIP() []byte { return file_tapchannelrpc_tapchannel_proto_rawDescData } -var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_tapchannelrpc_tapchannel_proto_goTypes = []interface{}{ - (*FundChannelRequest)(nil), // 0: tapchannelrpc.FundChannelRequest - (*FundChannelResponse)(nil), // 1: tapchannelrpc.FundChannelResponse - (*RouterSendPaymentData)(nil), // 2: tapchannelrpc.RouterSendPaymentData - (*EncodeCustomRecordsRequest)(nil), // 3: tapchannelrpc.EncodeCustomRecordsRequest - (*EncodeCustomRecordsResponse)(nil), // 4: tapchannelrpc.EncodeCustomRecordsResponse - nil, // 5: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry - nil, // 6: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + (*FundChannelRequest)(nil), // 0: tapchannelrpc.FundChannelRequest + (*FundChannelResponse)(nil), // 1: tapchannelrpc.FundChannelResponse + (*RouterSendPaymentData)(nil), // 2: tapchannelrpc.RouterSendPaymentData + (*EncodeCustomRecordsRequest)(nil), // 3: tapchannelrpc.EncodeCustomRecordsRequest + (*EncodeCustomRecordsResponse)(nil), // 4: tapchannelrpc.EncodeCustomRecordsResponse + (*SendPaymentRequest)(nil), // 5: tapchannelrpc.SendPaymentRequest + (*SendPaymentResponse)(nil), // 6: tapchannelrpc.SendPaymentResponse + nil, // 7: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + nil, // 8: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + (*routerrpc.SendPaymentRequest)(nil), // 9: routerrpc.SendPaymentRequest + (*rfqrpc.PeerAcceptedSellQuote)(nil), // 10: rfqrpc.PeerAcceptedSellQuote + (*lnrpc.Payment)(nil), // 11: lnrpc.Payment } var file_tapchannelrpc_tapchannel_proto_depIdxs = []int32{ - 5, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry - 2, // 1: tapchannelrpc.EncodeCustomRecordsRequest.router_send_payment:type_name -> tapchannelrpc.RouterSendPaymentData - 6, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry - 0, // 3: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest - 3, // 4: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest - 1, // 5: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse - 4, // 6: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse - 5, // [5:7] is the sub-list for method output_type - 3, // [3:5] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 7, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + 2, // 1: tapchannelrpc.EncodeCustomRecordsRequest.router_send_payment:type_name -> tapchannelrpc.RouterSendPaymentData + 8, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + 9, // 3: tapchannelrpc.SendPaymentRequest.payment_request:type_name -> routerrpc.SendPaymentRequest + 10, // 4: tapchannelrpc.SendPaymentResponse.accepted_sell_order:type_name -> rfqrpc.PeerAcceptedSellQuote + 11, // 5: tapchannelrpc.SendPaymentResponse.payment_result:type_name -> lnrpc.Payment + 0, // 6: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest + 3, // 7: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest + 5, // 8: tapchannelrpc.TaprootAssetChannels.SendPayment:input_type -> tapchannelrpc.SendPaymentRequest + 1, // 9: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse + 4, // 10: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse + 6, // 11: tapchannelrpc.TaprootAssetChannels.SendPayment:output_type -> tapchannelrpc.SendPaymentResponse + 9, // [9:12] is the sub-list for method output_type + 6, // [6:9] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_tapchannelrpc_tapchannel_proto_init() } @@ -522,17 +745,45 @@ func file_tapchannelrpc_tapchannel_proto_init() { return nil } } + file_tapchannelrpc_tapchannel_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendPaymentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tapchannelrpc_tapchannel_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendPaymentResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_tapchannelrpc_tapchannel_proto_msgTypes[3].OneofWrappers = []interface{}{ (*EncodeCustomRecordsRequest_RouterSendPayment)(nil), } + file_tapchannelrpc_tapchannel_proto_msgTypes[6].OneofWrappers = []interface{}{ + (*SendPaymentResponse_AcceptedSellOrder)(nil), + (*SendPaymentResponse_PaymentResult)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tapchannelrpc_tapchannel_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 9, NumExtensions: 0, NumServices: 1, }, diff --git a/taprpc/tapchannelrpc/tapchannel.pb.gw.go b/taprpc/tapchannelrpc/tapchannel.pb.gw.go index 39f3c9e80..5cf7a22ed 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.gw.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.gw.go @@ -101,6 +101,31 @@ func local_request_TaprootAssetChannels_EncodeCustomRecords_0(ctx context.Contex } +func request_TaprootAssetChannels_SendPayment_0(ctx context.Context, marshaler runtime.Marshaler, client TaprootAssetChannelsClient, req *http.Request, pathParams map[string]string) (TaprootAssetChannels_SendPaymentClient, runtime.ServerMetadata, error) { + var protoReq SendPaymentRequest + 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) + } + + stream, err := client.SendPayment(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + // RegisterTaprootAssetChannelsHandlerServer registers the http handlers for service TaprootAssetChannels to "mux". // UnaryRPC :call TaprootAssetChannelsServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -157,6 +182,13 @@ func RegisterTaprootAssetChannelsHandlerServer(ctx context.Context, mux *runtime }) + mux.Handle("POST", pattern_TaprootAssetChannels_SendPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + return nil } @@ -242,6 +274,28 @@ func RegisterTaprootAssetChannelsHandlerClient(ctx context.Context, mux *runtime }) + mux.Handle("POST", pattern_TaprootAssetChannels_SendPayment_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, "/tapchannelrpc.TaprootAssetChannels/SendPayment", runtime.WithHTTPPathPattern("/v1/taproot-assets/channels/send-payment")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TaprootAssetChannels_SendPayment_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_TaprootAssetChannels_SendPayment_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -249,10 +303,14 @@ var ( pattern_TaprootAssetChannels_FundChannel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "fund"}, "")) pattern_TaprootAssetChannels_EncodeCustomRecords_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "encode-custom-data"}, "")) + + pattern_TaprootAssetChannels_SendPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "send-payment"}, "")) ) var ( forward_TaprootAssetChannels_FundChannel_0 = runtime.ForwardResponseMessage forward_TaprootAssetChannels_EncodeCustomRecords_0 = runtime.ForwardResponseMessage + + forward_TaprootAssetChannels_SendPayment_0 = runtime.ForwardResponseStream ) diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index d2871c074..5de40ec5f 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -4,6 +4,10 @@ package tapchannelrpc; option go_package = "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"; +import "rfqrpc/rfq.proto"; +import "lightning.proto"; +import "routerrpc/router.proto"; + service TaprootAssetChannels { /* FundChannel initiates the channel funding negotiation with a peer for the @@ -20,6 +24,14 @@ service TaprootAssetChannels { */ rpc EncodeCustomRecords (EncodeCustomRecordsRequest) returns (EncodeCustomRecordsResponse); + + /* + SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method + with asset specific parameters. It allows RPC users to send asset keysend + payments (direct payments) or payments to an invoice with a specified asset + amount. + */ + rpc SendPayment (SendPaymentRequest) returns (stream SendPaymentResponse); } message FundChannelRequest { @@ -73,4 +85,48 @@ message EncodeCustomRecordsRequest { message EncodeCustomRecordsResponse { // The encoded custom records in TLV format. map custom_records = 1; -} \ No newline at end of file +} + +message SendPaymentRequest { + // The asset ID to use for the payment. This must be set for both invoice + // and keysend payments, unless RFQ negotiation was already done beforehand + // and payment_request.first_hop_custom_records already contains valid RFQ + // data. + bytes asset_id = 1; + + // The asset amount to send in a keysend payment. This amount is ignored for + // invoice payments as the asset amount is negotiated through RFQ with the + // peer, depending on the invoice amount. This can also be left unset if RFQ + // negotiation was already done beforehand and + // payment_request.first_hop_custom_records already contains valid RFQ data. + uint64 asset_amount = 2; + + // The node identity public key of the peer to ask for a quote for sending + // out the assets and converting them to satoshis. This must be specified if + // there are multiple channels with the given asset ID. + bytes peer_pubkey = 3; + + // The full lnd payment request to send. All fields behave the same way as + // they do for lnd's routerrpc.SendPaymentV2 RPC method (see the API docs + // at https://lightning.engineering/api-docs/api/lnd/router/send-payment-v2 + // for more details). + // To send a keysend payment, the payment_request.dest_custom_records must + // contain a valid keysend record (key 5482373484 and a 32-byte preimage + // that corresponds to the payment hash). + routerrpc.SendPaymentRequest payment_request = 4; +} + +message SendPaymentResponse { + oneof result { + // In case channel assets need to be swapped to another asset, an asset + // sell order is negotiated with the channel peer. The result will be + // the first message in the response stream. If no swap is needed, the + // payment results will be streamed directly. + rfqrpc.PeerAcceptedSellQuote accepted_sell_order = 1; + + // The payment result of a single payment attempt. Multiple attempts may + // be returned per payment request until either the payment succeeds or + // the payment times out. + lnrpc.Payment payment_result = 2; + } +} diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index d8f118ebb..829d1e2e7 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -113,9 +113,567 @@ "TaprootAssetChannels" ] } + }, + "/v1/taproot-assets/channels/send-payment": { + "post": { + "summary": "SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method\nwith asset specific parameters. It allows RPC users to send asset keysend\npayments (direct payments) or payments to an invoice with a specified asset\namount.", + "operationId": "TaprootAssetChannels_SendPayment", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/tapchannelrpcSendPaymentResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of tapchannelrpcSendPaymentResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tapchannelrpcSendPaymentRequest" + } + } + ], + "tags": [ + "TaprootAssetChannels" + ] + } } }, "definitions": { + "FailureFailureCode": { + "type": "string", + "enum": [ + "RESERVED", + "INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS", + "INCORRECT_PAYMENT_AMOUNT", + "FINAL_INCORRECT_CLTV_EXPIRY", + "FINAL_INCORRECT_HTLC_AMOUNT", + "FINAL_EXPIRY_TOO_SOON", + "INVALID_REALM", + "EXPIRY_TOO_SOON", + "INVALID_ONION_VERSION", + "INVALID_ONION_HMAC", + "INVALID_ONION_KEY", + "AMOUNT_BELOW_MINIMUM", + "FEE_INSUFFICIENT", + "INCORRECT_CLTV_EXPIRY", + "CHANNEL_DISABLED", + "TEMPORARY_CHANNEL_FAILURE", + "REQUIRED_NODE_FEATURE_MISSING", + "REQUIRED_CHANNEL_FEATURE_MISSING", + "UNKNOWN_NEXT_PEER", + "TEMPORARY_NODE_FAILURE", + "PERMANENT_NODE_FAILURE", + "PERMANENT_CHANNEL_FAILURE", + "EXPIRY_TOO_FAR", + "MPP_TIMEOUT", + "INVALID_ONION_PAYLOAD", + "INVALID_ONION_BLINDING", + "INTERNAL_FAILURE", + "UNKNOWN_FAILURE", + "UNREADABLE_FAILURE" + ], + "default": "RESERVED", + "description": " - RESERVED: The numbers assigned in this enumeration match the failure codes as\ndefined in BOLT #4. Because protobuf 3 requires enums to start with 0,\na RESERVED value is added.\n - INTERNAL_FAILURE: An internal error occurred.\n - UNKNOWN_FAILURE: The error source is known, but the failure itself couldn't be decoded.\n - UNREADABLE_FAILURE: An unreadable failure result is returned if the received failure message\ncannot be decrypted. In that case the error source is unknown." + }, + "HTLCAttemptHTLCStatus": { + "type": "string", + "enum": [ + "IN_FLIGHT", + "SUCCEEDED", + "FAILED" + ], + "default": "IN_FLIGHT" + }, + "lnrpcAMPRecord": { + "type": "object", + "properties": { + "root_share": { + "type": "string", + "format": "byte" + }, + "set_id": { + "type": "string", + "format": "byte" + }, + "child_index": { + "type": "integer", + "format": "int64" + } + } + }, + "lnrpcChannelUpdate": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "format": "byte", + "description": "The signature that validates the announced data and proves the ownership\nof node id." + }, + "chain_hash": { + "type": "string", + "format": "byte", + "description": "The target chain that this channel was opened within. This value\nshould be the genesis hash of the target chain. Along with the short\nchannel ID, this uniquely identifies the channel globally in a\nblockchain." + }, + "chan_id": { + "type": "string", + "format": "uint64", + "description": "The unique description of the funding transaction." + }, + "timestamp": { + "type": "integer", + "format": "int64", + "description": "A timestamp that allows ordering in the case of multiple announcements.\nWe should ignore the message if timestamp is not greater than the\nlast-received." + }, + "message_flags": { + "type": "integer", + "format": "int64", + "description": "The bitfield that describes whether optional fields are present in this\nupdate. Currently, the least-significant bit must be set to 1 if the\noptional field MaxHtlc is present." + }, + "channel_flags": { + "type": "integer", + "format": "int64", + "description": "The bitfield that describes additional meta-data concerning how the\nupdate is to be interpreted. Currently, the least-significant bit must be\nset to 0 if the creating node corresponds to the first node in the\npreviously sent channel announcement and 1 otherwise. If the second bit\nis set, then the channel is set to be disabled." + }, + "time_lock_delta": { + "type": "integer", + "format": "int64", + "description": "The minimum number of blocks this node requires to be added to the expiry\nof HTLCs. This is a security parameter determined by the node operator.\nThis value represents the required gap between the time locks of the\nincoming and outgoing HTLC's set to this node." + }, + "htlc_minimum_msat": { + "type": "string", + "format": "uint64", + "description": "The minimum HTLC value which will be accepted." + }, + "base_fee": { + "type": "integer", + "format": "int64", + "description": "The base fee that must be used for incoming HTLC's to this particular\nchannel. This value will be tacked onto the required for a payment\nindependent of the size of the payment." + }, + "fee_rate": { + "type": "integer", + "format": "int64", + "description": "The fee rate that will be charged per millionth of a satoshi." + }, + "htlc_maximum_msat": { + "type": "string", + "format": "uint64", + "description": "The maximum HTLC value which will be accepted." + }, + "extra_opaque_data": { + "type": "string", + "format": "byte", + "description": "The set of data that was appended to this message, some of which we may\nnot actually know how to iterate or parse. By holding onto this data, we\nensure that we're able to properly validate the set of signatures that\ncover these new fields, and ensure we're able to make upgrades to the\nnetwork in a forwards compatible manner." + } + } + }, + "lnrpcFailure": { + "type": "object", + "properties": { + "code": { + "$ref": "#/definitions/FailureFailureCode", + "title": "Failure code as defined in the Lightning spec" + }, + "channel_update": { + "$ref": "#/definitions/lnrpcChannelUpdate", + "description": "An optional channel update message." + }, + "htlc_msat": { + "type": "string", + "format": "uint64", + "description": "A failure type-dependent htlc value." + }, + "onion_sha_256": { + "type": "string", + "format": "byte", + "description": "The sha256 sum of the onion payload." + }, + "cltv_expiry": { + "type": "integer", + "format": "int64", + "description": "A failure type-dependent cltv expiry value." + }, + "flags": { + "type": "integer", + "format": "int64", + "description": "A failure type-dependent flags value." + }, + "failure_source_index": { + "type": "integer", + "format": "int64", + "description": "The position in the path of the intermediate or final node that generated\nthe failure message. Position zero is the sender node." + }, + "height": { + "type": "integer", + "format": "int64", + "description": "A failure type-dependent block height." + } + } + }, + "lnrpcFeatureBit": { + "type": "string", + "enum": [ + "DATALOSS_PROTECT_REQ", + "DATALOSS_PROTECT_OPT", + "INITIAL_ROUING_SYNC", + "UPFRONT_SHUTDOWN_SCRIPT_REQ", + "UPFRONT_SHUTDOWN_SCRIPT_OPT", + "GOSSIP_QUERIES_REQ", + "GOSSIP_QUERIES_OPT", + "TLV_ONION_REQ", + "TLV_ONION_OPT", + "EXT_GOSSIP_QUERIES_REQ", + "EXT_GOSSIP_QUERIES_OPT", + "STATIC_REMOTE_KEY_REQ", + "STATIC_REMOTE_KEY_OPT", + "PAYMENT_ADDR_REQ", + "PAYMENT_ADDR_OPT", + "MPP_REQ", + "MPP_OPT", + "WUMBO_CHANNELS_REQ", + "WUMBO_CHANNELS_OPT", + "ANCHORS_REQ", + "ANCHORS_OPT", + "ANCHORS_ZERO_FEE_HTLC_REQ", + "ANCHORS_ZERO_FEE_HTLC_OPT", + "ROUTE_BLINDING_REQUIRED", + "ROUTE_BLINDING_OPTIONAL", + "AMP_REQ", + "AMP_OPT" + ], + "default": "DATALOSS_PROTECT_REQ" + }, + "lnrpcHTLCAttempt": { + "type": "object", + "properties": { + "attempt_id": { + "type": "string", + "format": "uint64", + "description": "The unique ID that is used for this attempt." + }, + "status": { + "$ref": "#/definitions/HTLCAttemptHTLCStatus", + "description": "The status of the HTLC." + }, + "route": { + "$ref": "#/definitions/lnrpcRoute", + "description": "The route taken by this HTLC." + }, + "attempt_time_ns": { + "type": "string", + "format": "int64", + "description": "The time in UNIX nanoseconds at which this HTLC was sent." + }, + "resolve_time_ns": { + "type": "string", + "format": "int64", + "description": "The time in UNIX nanoseconds at which this HTLC was settled or failed.\nThis value will not be set if the HTLC is still IN_FLIGHT." + }, + "failure": { + "$ref": "#/definitions/lnrpcFailure", + "description": "Detailed htlc failure info." + }, + "preimage": { + "type": "string", + "format": "byte", + "description": "The preimage that was used to settle the HTLC." + } + } + }, + "lnrpcHop": { + "type": "object", + "properties": { + "chan_id": { + "type": "string", + "format": "uint64", + "description": "The unique channel ID for the channel. The first 3 bytes are the block\nheight, the next 3 the index within the block, and the last 2 bytes are the\noutput index for the channel." + }, + "chan_capacity": { + "type": "string", + "format": "int64" + }, + "amt_to_forward": { + "type": "string", + "format": "int64" + }, + "fee": { + "type": "string", + "format": "int64" + }, + "expiry": { + "type": "integer", + "format": "int64" + }, + "amt_to_forward_msat": { + "type": "string", + "format": "int64" + }, + "fee_msat": { + "type": "string", + "format": "int64" + }, + "pub_key": { + "type": "string", + "description": "An optional public key of the hop. If the public key is given, the payment\ncan be executed without relying on a copy of the channel graph." + }, + "tlv_payload": { + "type": "boolean", + "description": "If set to true, then this hop will be encoded using the new variable length\nTLV format. Note that if any custom tlv_records below are specified, then\nthis field MUST be set to true for them to be encoded properly." + }, + "mpp_record": { + "$ref": "#/definitions/lnrpcMPPRecord", + "description": "An optional TLV record that signals the use of an MPP payment. If present,\nthe receiver will enforce that the same mpp_record is included in the final\nhop payload of all non-zero payments in the HTLC set. If empty, a regular\nsingle-shot payment is or was attempted." + }, + "amp_record": { + "$ref": "#/definitions/lnrpcAMPRecord", + "description": "An optional TLV record that signals the use of an AMP payment. If present,\nthe receiver will treat all received payments including the same\n(payment_addr, set_id) pair as being part of one logical payment. The\npayment will be settled by XORing the root_share's together and deriving the\nchild hashes and preimages according to BOLT XX. Must be used in conjunction\nwith mpp_record." + }, + "custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion." + }, + "metadata": { + "type": "string", + "format": "byte", + "description": "The payment metadata to send along with the payment to the payee." + }, + "blinding_point": { + "type": "string", + "format": "byte", + "description": "Blinding point is an optional blinding point included for introduction\nnodes in blinded paths. This field is mandatory for hops that represents\nthe introduction point in a blinded path." + }, + "encrypted_data": { + "type": "string", + "format": "byte", + "description": "Encrypted data is a receiver-produced blob of data that provides hops\nin a blinded route with forwarding data. As this data is encrypted by\nthe recipient, we will not be able to parse it - it is essentially an\narbitrary blob of data from our node's perspective. This field is\nmandatory for all hops in a blinded path, including the introduction\nnode." + }, + "total_amt_msat": { + "type": "string", + "format": "uint64", + "description": "The total amount that is sent to the recipient (possibly across multiple\nHTLCs), as specified by the sender when making a payment to a blinded path.\nThis value is only set in the final hop payload of a blinded payment. This\nvalue is analogous to the MPPRecord that is used for regular (non-blinded)\nMPP payments." + } + } + }, + "lnrpcHopHint": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "The public key of the node at the start of the channel." + }, + "chan_id": { + "type": "string", + "format": "uint64", + "description": "The unique identifier of the channel." + }, + "fee_base_msat": { + "type": "integer", + "format": "int64", + "description": "The base fee of the channel denominated in millisatoshis." + }, + "fee_proportional_millionths": { + "type": "integer", + "format": "int64", + "description": "The fee rate of the channel for sending one satoshi across it denominated in\nmillionths of a satoshi." + }, + "cltv_expiry_delta": { + "type": "integer", + "format": "int64", + "description": "The time-lock delta of the channel." + } + } + }, + "lnrpcMPPRecord": { + "type": "object", + "properties": { + "payment_addr": { + "type": "string", + "format": "byte", + "description": "A unique, random identifier used to authenticate the sender as the intended\npayer of a multi-path payment. The payment_addr must be the same for all\nsubpayments, and match the payment_addr provided in the receiver's invoice.\nThe same payment_addr must be used on all subpayments. This is also called\npayment secret in specifications (e.g. BOLT 11)." + }, + "total_amt_msat": { + "type": "string", + "format": "int64", + "description": "The total amount in milli-satoshis being sent as part of a larger multi-path\npayment. The caller is responsible for ensuring subpayments to the same node\nand payment_hash sum exactly to total_amt_msat. The same\ntotal_amt_msat must be used on all subpayments." + } + } + }, + "lnrpcPayment": { + "type": "object", + "properties": { + "payment_hash": { + "type": "string", + "title": "The payment hash" + }, + "value": { + "type": "string", + "format": "int64", + "description": "Deprecated, use value_sat or value_msat." + }, + "creation_date": { + "type": "string", + "format": "int64", + "title": "Deprecated, use creation_time_ns" + }, + "fee": { + "type": "string", + "format": "int64", + "description": "Deprecated, use fee_sat or fee_msat." + }, + "payment_preimage": { + "type": "string", + "title": "The payment preimage" + }, + "value_sat": { + "type": "string", + "format": "int64", + "title": "The value of the payment in satoshis" + }, + "value_msat": { + "type": "string", + "format": "int64", + "title": "The value of the payment in milli-satoshis" + }, + "payment_request": { + "type": "string", + "description": "The optional payment request being fulfilled." + }, + "status": { + "$ref": "#/definitions/lnrpcPaymentPaymentStatus", + "description": "The status of the payment." + }, + "fee_sat": { + "type": "string", + "format": "int64", + "title": "The fee paid for this payment in satoshis" + }, + "fee_msat": { + "type": "string", + "format": "int64", + "title": "The fee paid for this payment in milli-satoshis" + }, + "creation_time_ns": { + "type": "string", + "format": "int64", + "description": "The time in UNIX nanoseconds at which the payment was created." + }, + "htlcs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcHTLCAttempt" + }, + "description": "The HTLCs made in attempt to settle the payment." + }, + "payment_index": { + "type": "string", + "format": "uint64", + "description": "The creation index of this payment. Each payment can be uniquely identified\nby this index, which may not strictly increment by 1 for payments made in\nolder versions of lnd." + }, + "failure_reason": { + "$ref": "#/definitions/lnrpcPaymentFailureReason" + } + } + }, + "lnrpcPaymentFailureReason": { + "type": "string", + "enum": [ + "FAILURE_REASON_NONE", + "FAILURE_REASON_TIMEOUT", + "FAILURE_REASON_NO_ROUTE", + "FAILURE_REASON_ERROR", + "FAILURE_REASON_INCORRECT_PAYMENT_DETAILS", + "FAILURE_REASON_INSUFFICIENT_BALANCE" + ], + "default": "FAILURE_REASON_NONE", + "description": " - FAILURE_REASON_NONE: Payment isn't failed (yet).\n - FAILURE_REASON_TIMEOUT: There are more routes to try, but the payment timeout was exceeded.\n - FAILURE_REASON_NO_ROUTE: All possible routes were tried and failed permanently. Or were no\nroutes to the destination at all.\n - FAILURE_REASON_ERROR: A non-recoverable error has occured.\n - FAILURE_REASON_INCORRECT_PAYMENT_DETAILS: Payment details incorrect (unknown hash, invalid amt or\ninvalid final cltv delta)\n - FAILURE_REASON_INSUFFICIENT_BALANCE: Insufficient local balance." + }, + "lnrpcPaymentPaymentStatus": { + "type": "string", + "enum": [ + "UNKNOWN", + "IN_FLIGHT", + "SUCCEEDED", + "FAILED", + "INITIATED" + ], + "default": "UNKNOWN", + "description": " - UNKNOWN: Deprecated. This status will never be returned.\n - IN_FLIGHT: Payment has inflight HTLCs.\n - SUCCEEDED: Payment is settled.\n - FAILED: Payment is failed.\n - INITIATED: Payment is created and has not attempted any HTLCs." + }, + "lnrpcRoute": { + "type": "object", + "properties": { + "total_time_lock": { + "type": "integer", + "format": "int64", + "description": "The cumulative (final) time lock across the entire route. This is the CLTV\nvalue that should be extended to the first hop in the route. All other hops\nwill decrement the time-lock as advertised, leaving enough time for all\nhops to wait for or present the payment preimage to complete the payment." + }, + "total_fees": { + "type": "string", + "format": "int64", + "description": "The sum of the fees paid at each hop within the final route. In the case\nof a one-hop payment, this value will be zero as we don't need to pay a fee\nto ourselves." + }, + "total_amt": { + "type": "string", + "format": "int64", + "description": "The total amount of funds required to complete a payment over this route.\nThis value includes the cumulative fees at each hop. As a result, the HTLC\nextended to the first-hop in the route will need to have at least this many\nsatoshis, otherwise the route will fail at an intermediate node due to an\ninsufficient amount of fees." + }, + "hops": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcHop" + }, + "description": "Contains details concerning the specific forwarding details at each hop." + }, + "total_fees_msat": { + "type": "string", + "format": "int64", + "description": "The total fees in millisatoshis." + }, + "total_amt_msat": { + "type": "string", + "format": "int64", + "description": "The total amount in millisatoshis." + } + }, + "description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for." + }, + "lnrpcRouteHint": { + "type": "object", + "properties": { + "hop_hints": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcHopHint" + }, + "description": "A list of hop hints that when chained together can assist in reaching a\nspecific destination." + } + } + }, "protobufAny": { "type": "object", "properties": { @@ -125,6 +683,175 @@ }, "additionalProperties": {} }, + "rfqrpcPeerAcceptedSellQuote": { + "type": "object", + "properties": { + "peer": { + "type": "string", + "description": "Quote counterparty peer." + }, + "id": { + "type": "string", + "format": "byte", + "description": "The unique identifier of the quote request." + }, + "scid": { + "type": "string", + "format": "uint64", + "description": "scid is the short channel ID of the channel over which the payment for\nthe quote should be made." + }, + "asset_amount": { + "type": "string", + "format": "uint64", + "description": "asset_amount is the amount of the subject asset." + }, + "bid_price": { + "type": "string", + "format": "uint64", + "description": "bid_price is the price in milli-satoshi per asset unit." + }, + "expiry": { + "type": "string", + "format": "uint64", + "description": "The unix timestamp in seconds after which the quote is no longer valid." + } + } + }, + "routerrpcSendPaymentRequest": { + "type": "object", + "properties": { + "dest": { + "type": "string", + "format": "byte", + "title": "The identity pubkey of the payment recipient" + }, + "amt": { + "type": "string", + "format": "int64", + "description": "Number of satoshis to send.\n\nThe fields amt and amt_msat are mutually exclusive." + }, + "payment_hash": { + "type": "string", + "format": "byte", + "title": "The hash to use within the payment's HTLC" + }, + "final_cltv_delta": { + "type": "integer", + "format": "int32", + "description": "The CLTV delta from the current height that should be used to set the\ntimelock for the final hop." + }, + "payment_request": { + "type": "string", + "description": "A bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient. The amount in the payment request may be zero. In\nthat case it is required to set the amt field as well. If no payment request\nis specified, the following fields are required: dest, amt and payment_hash." + }, + "timeout_seconds": { + "type": "integer", + "format": "int32", + "description": "An upper limit on the amount of time we should spend when attempting to\nfulfill the payment. This is expressed in seconds. If we cannot make a\nsuccessful payment within this time frame, an error will be returned.\nThis field must be non-zero." + }, + "fee_limit_sat": { + "type": "string", + "format": "int64", + "description": "The maximum number of satoshis that will be paid as a fee of the payment.\nIf this field is left to the default value of 0, only zero-fee routes will\nbe considered. This usually means single hop routes connecting directly to\nthe destination. To send the payment without a fee limit, use max int here.\n\nThe fields fee_limit_sat and fee_limit_msat are mutually exclusive." + }, + "outgoing_chan_id": { + "type": "string", + "format": "uint64", + "description": "Deprecated, use outgoing_chan_ids. The channel id of the channel that must\nbe taken to the first hop. If zero, any channel may be used (unless\noutgoing_chan_ids are set)." + }, + "cltv_limit": { + "type": "integer", + "format": "int32", + "description": "An optional maximum total time lock for the route. This should not\nexceed lnd's `--max-cltv-expiry` setting. If zero, then the value of\n`--max-cltv-expiry` is enforced." + }, + "route_hints": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcRouteHint" + }, + "description": "Optional route hints to reach the destination through private channels." + }, + "dest_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto a peer which understands the new records. This can be used to pass\napplication specific data during the payment attempt. Record types are\nrequired to be in the custom range \u003e= 65536. When using REST, the values\nmust be encoded as base64." + }, + "amt_msat": { + "type": "string", + "format": "int64", + "description": "Number of millisatoshis to send.\n\nThe fields amt and amt_msat are mutually exclusive." + }, + "fee_limit_msat": { + "type": "string", + "format": "int64", + "description": "The maximum number of millisatoshis that will be paid as a fee of the\npayment. If this field is left to the default value of 0, only zero-fee\nroutes will be considered. This usually means single hop routes connecting\ndirectly to the destination. To send the payment without a fee limit, use\nmax int here.\n\nThe fields fee_limit_sat and fee_limit_msat are mutually exclusive." + }, + "last_hop_pubkey": { + "type": "string", + "format": "byte", + "description": "The pubkey of the last hop of the route. If empty, any hop may be used." + }, + "allow_self_payment": { + "type": "boolean", + "description": "If set, circular payments to self are permitted." + }, + "dest_features": { + "type": "array", + "items": { + "$ref": "#/definitions/lnrpcFeatureBit" + }, + "description": "Features assumed to be supported by the final node. All transitive feature\ndependencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both. If this field is nil or empty,\nthe router will try to load destination features from the graph as a\nfallback." + }, + "max_parts": { + "type": "integer", + "format": "int64", + "description": "The maximum number of partial payments that may be use to complete the full\namount." + }, + "no_inflight_updates": { + "type": "boolean", + "description": "If set, only the final payment update is streamed back. Intermediate updates\nthat show which htlcs are still in flight are suppressed." + }, + "outgoing_chan_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uint64" + }, + "description": "The channel ids of the channels are allowed for the first hop. If empty,\nany channel may be used." + }, + "payment_addr": { + "type": "string", + "format": "byte", + "description": "An optional payment addr to be included within the last hop of the route.\nThis is also called payment secret in specifications (e.g. BOLT 11)." + }, + "max_shard_size_msat": { + "type": "string", + "format": "uint64", + "description": "The largest payment split that should be attempted when making a payment if\nsplitting is necessary. Setting this value will effectively cause lnd to\nsplit more aggressively, vs only when it thinks it needs to. Note that this\nvalue is in milli-satoshis." + }, + "amp": { + "type": "boolean", + "description": "If set, an AMP-payment will be attempted." + }, + "time_pref": { + "type": "number", + "format": "double", + "description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix." + }, + "first_hop_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64." + } + } + }, "rpcStatus": { "type": "object", "properties": { @@ -196,6 +923,43 @@ "description": "The RFQ ID to use for the payment. Can be empty for a direct keysend\npayment that doesn't involve any conversion (and thus no RFQ)." } } + }, + "tapchannelrpcSendPaymentRequest": { + "type": "object", + "properties": { + "asset_id": { + "type": "string", + "format": "byte", + "description": "The asset ID to use for the payment. This must be set for both invoice\nand keysend payments, unless RFQ negotiation was already done beforehand\nand payment_request.first_hop_custom_records already contains valid RFQ\ndata." + }, + "asset_amount": { + "type": "string", + "format": "uint64", + "description": "The asset amount to send in a keysend payment. This amount is ignored for\ninvoice payments as the asset amount is negotiated through RFQ with the\npeer, depending on the invoice amount. This can also be left unset if RFQ\nnegotiation was already done beforehand and\npayment_request.first_hop_custom_records already contains valid RFQ data." + }, + "peer_pubkey": { + "type": "string", + "format": "byte", + "description": "The node identity public key of the peer to ask for a quote for sending\nout the assets and converting them to satoshis. This must be specified if\nthere are multiple channels with the given asset ID." + }, + "payment_request": { + "$ref": "#/definitions/routerrpcSendPaymentRequest", + "description": "The full lnd payment request to send. All fields behave the same way as\nthey do for lnd's routerrpc.SendPaymentV2 RPC method (see the API docs\nat https://lightning.engineering/api-docs/api/lnd/router/send-payment-v2\nfor more details).\nTo send a keysend payment, the payment_request.dest_custom_records must\ncontain a valid keysend record (key 5482373484 and a 32-byte preimage\nthat corresponds to the payment hash)." + } + } + }, + "tapchannelrpcSendPaymentResponse": { + "type": "object", + "properties": { + "accepted_sell_order": { + "$ref": "#/definitions/rfqrpcPeerAcceptedSellQuote", + "description": "In case channel assets need to be swapped to another asset, an asset\nsell order is negotiated with the channel peer. The result will be\nthe first message in the response stream. If no swap is needed, the\npayment results will be streamed directly." + }, + "payment_result": { + "$ref": "#/definitions/lnrpcPayment", + "description": "The payment result of a single payment attempt. Multiple attempts may\nbe returned per payment request until either the payment succeeds or\nthe payment times out." + } + } } } } diff --git a/taprpc/tapchannelrpc/tapchannel.yaml b/taprpc/tapchannelrpc/tapchannel.yaml index 3c8afb0ea..3e7f63eb8 100644 --- a/taprpc/tapchannelrpc/tapchannel.yaml +++ b/taprpc/tapchannelrpc/tapchannel.yaml @@ -8,3 +8,6 @@ http: - selector: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords post: "/v1/taproot-assets/channels/encode-custom-data" body: "*" + - selector: tapchannelrpc.TaprootAssetChannels.SendPayment + post: "/v1/taproot-assets/channels/send-payment" + body: "*" diff --git a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go index 7bf1483af..cc5ec5bf6 100644 --- a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go +++ b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go @@ -27,6 +27,11 @@ type TaprootAssetChannelsClient interface { // does not perform any checks on the data provided, other than pure format // validation. EncodeCustomRecords(ctx context.Context, in *EncodeCustomRecordsRequest, opts ...grpc.CallOption) (*EncodeCustomRecordsResponse, error) + // SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method + // with asset specific parameters. It allows RPC users to send asset keysend + // payments (direct payments) or payments to an invoice with a specified asset + // amount. + SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (TaprootAssetChannels_SendPaymentClient, error) } type taprootAssetChannelsClient struct { @@ -55,6 +60,38 @@ func (c *taprootAssetChannelsClient) EncodeCustomRecords(ctx context.Context, in return out, nil } +func (c *taprootAssetChannelsClient) SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (TaprootAssetChannels_SendPaymentClient, error) { + stream, err := c.cc.NewStream(ctx, &TaprootAssetChannels_ServiceDesc.Streams[0], "/tapchannelrpc.TaprootAssetChannels/SendPayment", opts...) + if err != nil { + return nil, err + } + x := &taprootAssetChannelsSendPaymentClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type TaprootAssetChannels_SendPaymentClient interface { + Recv() (*SendPaymentResponse, error) + grpc.ClientStream +} + +type taprootAssetChannelsSendPaymentClient struct { + grpc.ClientStream +} + +func (x *taprootAssetChannelsSendPaymentClient) Recv() (*SendPaymentResponse, error) { + m := new(SendPaymentResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // TaprootAssetChannelsServer is the server API for TaprootAssetChannels service. // All implementations must embed UnimplementedTaprootAssetChannelsServer // for forward compatibility @@ -68,6 +105,11 @@ type TaprootAssetChannelsServer interface { // does not perform any checks on the data provided, other than pure format // validation. EncodeCustomRecords(context.Context, *EncodeCustomRecordsRequest) (*EncodeCustomRecordsResponse, error) + // SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method + // with asset specific parameters. It allows RPC users to send asset keysend + // payments (direct payments) or payments to an invoice with a specified asset + // amount. + SendPayment(*SendPaymentRequest, TaprootAssetChannels_SendPaymentServer) error mustEmbedUnimplementedTaprootAssetChannelsServer() } @@ -81,6 +123,9 @@ func (UnimplementedTaprootAssetChannelsServer) FundChannel(context.Context, *Fun func (UnimplementedTaprootAssetChannelsServer) EncodeCustomRecords(context.Context, *EncodeCustomRecordsRequest) (*EncodeCustomRecordsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method EncodeCustomRecords not implemented") } +func (UnimplementedTaprootAssetChannelsServer) SendPayment(*SendPaymentRequest, TaprootAssetChannels_SendPaymentServer) error { + return status.Errorf(codes.Unimplemented, "method SendPayment not implemented") +} func (UnimplementedTaprootAssetChannelsServer) mustEmbedUnimplementedTaprootAssetChannelsServer() {} // UnsafeTaprootAssetChannelsServer may be embedded to opt out of forward compatibility for this service. @@ -130,6 +175,27 @@ func _TaprootAssetChannels_EncodeCustomRecords_Handler(srv interface{}, ctx cont return interceptor(ctx, in, info, handler) } +func _TaprootAssetChannels_SendPayment_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SendPaymentRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TaprootAssetChannelsServer).SendPayment(m, &taprootAssetChannelsSendPaymentServer{stream}) +} + +type TaprootAssetChannels_SendPaymentServer interface { + Send(*SendPaymentResponse) error + grpc.ServerStream +} + +type taprootAssetChannelsSendPaymentServer struct { + grpc.ServerStream +} + +func (x *taprootAssetChannelsSendPaymentServer) Send(m *SendPaymentResponse) error { + return x.ServerStream.SendMsg(m) +} + // TaprootAssetChannels_ServiceDesc is the grpc.ServiceDesc for TaprootAssetChannels service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -146,6 +212,12 @@ var TaprootAssetChannels_ServiceDesc = grpc.ServiceDesc{ Handler: _TaprootAssetChannels_EncodeCustomRecords_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "SendPayment", + Handler: _TaprootAssetChannels_SendPayment_Handler, + ServerStreams: true, + }, + }, Metadata: "tapchannelrpc/tapchannel.proto", } diff --git a/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go b/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go index ffa315f6b..be6c3f30b 100644 --- a/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go +++ b/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go @@ -70,4 +70,46 @@ func RegisterTaprootAssetChannelsJSONCallbacks(registry map[string]func(ctx cont } callback(string(respBytes), nil) } + + registry["tapchannelrpc.TaprootAssetChannels.SendPayment"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &SendPaymentRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewTaprootAssetChannelsClient(conn) + stream, err := client.SendPayment(ctx, req) + if err != nil { + callback("", err) + return + } + + go func() { + for { + select { + case <-stream.Context().Done(): + callback("", stream.Context().Err()) + return + default: + } + + resp, err := stream.Recv() + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + }() + } } From 66be6de3490215c31c0da368fb332fbdc5a7bcf6 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 29 Jul 2024 15:55:42 +0200 Subject: [PATCH 4/5] multi: add AddInvoice RPC method --- perms/perms.go | 4 + rfq/manager.go | 4 + rpcserver.go | 155 +++++++ taprpc/tapchannelrpc/tapchannel.pb.go | 292 ++++++++++-- taprpc/tapchannelrpc/tapchannel.pb.gw.go | 85 ++++ taprpc/tapchannelrpc/tapchannel.proto | 37 ++ taprpc/tapchannelrpc/tapchannel.swagger.json | 430 ++++++++++++++++++ taprpc/tapchannelrpc/tapchannel.yaml | 3 + taprpc/tapchannelrpc/tapchannel_grpc.pb.go | 42 ++ .../taprootassetchannels.pb.json.go | 25 + 10 files changed, 1031 insertions(+), 46 deletions(-) diff --git a/perms/perms.go b/perms/perms.go index 13c5f5f2b..8fd2214f5 100644 --- a/perms/perms.go +++ b/perms/perms.go @@ -272,6 +272,10 @@ var ( Entity: "channels", Action: "write", }}, + "/tapchannelrpc.TaprootAssetChannels/AddInvoice": {{ + Entity: "channels", + Action: "write", + }}, "/tapchannelrpc.TaprootAssetChannels/EncodeCustomRecords": { // This RPC is completely stateless and doesn't require // any permissions to use. diff --git a/rfq/manager.go b/rfq/manager.go index a63ec2a3f..3b7e6404e 100644 --- a/rfq/manager.go +++ b/rfq/manager.go @@ -21,6 +21,10 @@ const ( // DefaultTimeout is the default timeout used for context operations. DefaultTimeout = 30 * time.Second + // DefaultInvoiceExpiry is the default expiry time for asset invoices. + // The current value corresponds to 5 minutes. + DefaultInvoiceExpiry = time.Second * 300 + // CacheCleanupInterval is the interval at which local runtime caches // are cleaned up. CacheCleanupInterval = 30 * time.Second diff --git a/rpcserver.go b/rpcserver.go index 9d67e4e03..eaef1f4cb 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6946,6 +6946,140 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest, } } +// AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset +// specific parameters. It allows RPC users to create invoices that correspond +// to the specified asset amount. +func (r *rpcServer) AddInvoice(ctx context.Context, + req *tchrpc.AddInvoiceRequest) (*tchrpc.AddInvoiceResponse, error) { + + if req.InvoiceRequest == nil { + return nil, fmt.Errorf("invoice request must be specified") + } + iReq := req.InvoiceRequest + + // Do some preliminary checks on the asset ID and make sure we have any + // balance for that asset. + if len(req.AssetId) != sha256.Size { + return nil, fmt.Errorf("asset ID must be 32 bytes") + } + var assetID asset.ID + copy(assetID[:], req.AssetId) + + // The peer public key is optional if there is only a single asset + // channel. + var peerPubKey *route.Vertex + if len(req.PeerPubkey) > 0 { + parsedKey, err := route.NewVertexFromBytes(req.PeerPubkey) + if err != nil { + return nil, fmt.Errorf("error parsing peer pubkey: %w", + err) + } + + peerPubKey = &parsedKey + } + + // We can now query the asset channels we have. + assetChan, err := r.rfqChannel(ctx, assetID, peerPubKey) + if err != nil { + return nil, fmt.Errorf("error finding asset channel to use: %w", + err) + } + + // Even if the user didn't specify the peer public key before, we + // definitely know it now. So let's make sure it's always set. + peerPubKey = &assetChan.channelInfo.PubKeyBytes + + expirySeconds := iReq.Expiry + if expirySeconds == 0 { + expirySeconds = int64(rfq.DefaultInvoiceExpiry.Seconds()) + } + expiryTimestamp := time.Now().Add( + time.Duration(expirySeconds) * time.Second, + ) + + resp, err := r.AddAssetBuyOrder(ctx, &rfqrpc.AddAssetBuyOrderRequest{ + AssetSpecifier: &rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_AssetId{ + AssetId: assetID[:], + }, + }, + MinAssetAmount: req.AssetAmount, + Expiry: uint64(expiryTimestamp.Unix()), + PeerPubKey: peerPubKey[:], + TimeoutSeconds: uint32( + rfq.DefaultTimeout.Seconds(), + ), + }) + if err != nil { + return nil, fmt.Errorf("error adding buy order: %w", err) + } + + var acceptedQuote *rfqrpc.PeerAcceptedBuyQuote + switch r := resp.Response.(type) { + case *rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote: + acceptedQuote = r.AcceptedQuote + + case *rfqrpc.AddAssetBuyOrderResponse_InvalidQuote: + return nil, fmt.Errorf("peer %v sent back an invalid quote, "+ + "status: %v", r.InvalidQuote.Peer, + r.InvalidQuote.Status.String()) + + case *rfqrpc.AddAssetBuyOrderResponse_RejectedQuote: + return nil, fmt.Errorf("peer %v rejected the quote, code: %v, "+ + "error message: %v", r.RejectedQuote.Peer, + r.RejectedQuote.ErrorCode, r.RejectedQuote.ErrorMessage) + + default: + return nil, fmt.Errorf("unexpected response type: %T", r) + } + + // Now that we have the accepted quote, we know the amount in Satoshi + // that we need to pay. We can now update the invoice with this amount. + mSatPerUnit := acceptedQuote.AskPrice + iReq.ValueMsat = int64(req.AssetAmount * mSatPerUnit) + + // The last step is to create a hop hint that includes the fake SCID of + // the quote, alongside the channel's routing policy. We need to choose + // the policy that points towards us, as the payment will be flowing in. + // So we get the policy that's being set by the remote peer. + channelID := assetChan.channelInfo.ChannelID + inboundPolicy, err := r.getInboundPolicy( + ctx, channelID, peerPubKey.String(), + ) + if err != nil { + return nil, fmt.Errorf("unable to get inbound channel policy "+ + "for channel with ID %d: %w", channelID, err) + } + + hopHint := &lnrpc.HopHint{ + NodeId: peerPubKey.String(), + ChanId: acceptedQuote.Scid, + FeeBaseMsat: uint32(inboundPolicy.FeeBaseMsat), + FeeProportionalMillionths: uint32( + inboundPolicy.FeeRateMilliMsat, + ), + CltvExpiryDelta: inboundPolicy.TimeLockDelta, + } + iReq.RouteHints = []*lnrpc.RouteHint{ + { + HopHints: []*lnrpc.HopHint{ + hopHint, + }, + }, + } + + rpcCtx, _, rawClient := r.cfg.Lnd.Client.RawClientWithMacAuth(ctx) + invoiceResp, err := rawClient.AddInvoice(rpcCtx, iReq) + if err != nil { + return nil, fmt.Errorf("error creating invoice: %w", err) + } + + return &tchrpc.AddInvoiceResponse{ + AcceptedBuyQuote: acceptedQuote, + InvoiceResult: invoiceResp, + }, nil +} + // DeclareScriptKey declares a new script key to the wallet. This is useful // when the script key contains scripts, which would mean it wouldn't be // recognized by the wallet automatically. Declaring a script key will make any @@ -7166,3 +7300,24 @@ func (r *rpcServer) computeChannelAssetBalance( return channelsByID, nil } + +// getInboundPolicy returns the policy of the given channel that points towards +// our node, so it's the policy set by the remote peer. +func (r *rpcServer) getInboundPolicy(ctx context.Context, chanID uint64, + remotePubStr string) (*lnrpc.RoutingPolicy, error) { + + rpcCtx, _, rawClient := r.cfg.Lnd.Client.RawClientWithMacAuth(ctx) + edge, err := rawClient.GetChanInfo(rpcCtx, &lnrpc.ChanInfoRequest{ + ChanId: chanID, + }) + if err != nil { + return nil, fmt.Errorf("unable to fetch channel: %w", err) + } + + policy := edge.Node2Policy + if edge.Node2Pub == remotePubStr { + policy = edge.Node1Policy + } + + return policy, nil +} diff --git a/taprpc/tapchannelrpc/tapchannel.pb.go b/taprpc/tapchannelrpc/tapchannel.pb.go index bcc54cc48..e037a6f7f 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.go @@ -523,6 +523,146 @@ func (*SendPaymentResponse_AcceptedSellOrder) isSendPaymentResponse_Result() {} func (*SendPaymentResponse_PaymentResult) isSendPaymentResponse_Result() {} +type AddInvoiceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The asset ID to use for the invoice. + AssetId []byte `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + // The asset amount to receive. + AssetAmount uint64 `protobuf:"varint,2,opt,name=asset_amount,json=assetAmount,proto3" json:"asset_amount,omitempty"` + // The node identity public key of the peer to ask for a quote for receiving + // assets and converting them from satoshis. This must be specified if + // there are multiple channels with the given asset ID. + PeerPubkey []byte `protobuf:"bytes,3,opt,name=peer_pubkey,json=peerPubkey,proto3" json:"peer_pubkey,omitempty"` + // The full lnd invoice request to send. All fields (except for the value + // and the route hints) behave the same way as they do for lnd's + // lnrpc.AddInvoice RPC method (see the API docs at + // https://lightning.engineering/api-docs/api/lnd/lightning/add-invoice + // for more details). The value/value_msat fields will be overwritten by the + // satoshi (or milli-satoshi) equivalent of the asset amount, after + // negotiating a quote with a peer that supports the given asset ID. + InvoiceRequest *lnrpc.Invoice `protobuf:"bytes,4,opt,name=invoice_request,json=invoiceRequest,proto3" json:"invoice_request,omitempty"` +} + +func (x *AddInvoiceRequest) Reset() { + *x = AddInvoiceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddInvoiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddInvoiceRequest) ProtoMessage() {} + +func (x *AddInvoiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddInvoiceRequest.ProtoReflect.Descriptor instead. +func (*AddInvoiceRequest) Descriptor() ([]byte, []int) { + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{7} +} + +func (x *AddInvoiceRequest) GetAssetId() []byte { + if x != nil { + return x.AssetId + } + return nil +} + +func (x *AddInvoiceRequest) GetAssetAmount() uint64 { + if x != nil { + return x.AssetAmount + } + return 0 +} + +func (x *AddInvoiceRequest) GetPeerPubkey() []byte { + if x != nil { + return x.PeerPubkey + } + return nil +} + +func (x *AddInvoiceRequest) GetInvoiceRequest() *lnrpc.Invoice { + if x != nil { + return x.InvoiceRequest + } + return nil +} + +type AddInvoiceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The quote for the purchase of assets that was accepted by the peer. + AcceptedBuyQuote *rfqrpc.PeerAcceptedBuyQuote `protobuf:"bytes,1,opt,name=accepted_buy_quote,json=acceptedBuyQuote,proto3" json:"accepted_buy_quote,omitempty"` + // The result of the invoice creation. + InvoiceResult *lnrpc.AddInvoiceResponse `protobuf:"bytes,2,opt,name=invoice_result,json=invoiceResult,proto3" json:"invoice_result,omitempty"` +} + +func (x *AddInvoiceResponse) Reset() { + *x = AddInvoiceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddInvoiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddInvoiceResponse) ProtoMessage() {} + +func (x *AddInvoiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_tapchannelrpc_tapchannel_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddInvoiceResponse.ProtoReflect.Descriptor instead. +func (*AddInvoiceResponse) Descriptor() ([]byte, []int) { + return file_tapchannelrpc_tapchannel_proto_rawDescGZIP(), []int{8} +} + +func (x *AddInvoiceResponse) GetAcceptedBuyQuote() *rfqrpc.PeerAcceptedBuyQuote { + if x != nil { + return x.AcceptedBuyQuote + } + return nil +} + +func (x *AddInvoiceResponse) GetInvoiceResult() *lnrpc.AddInvoiceResponse { + if x != nil { + return x.InvoiceResult + } + return nil +} + var File_tapchannelrpc_tapchannel_proto protoreflect.FileDescriptor var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ @@ -605,31 +745,57 @@ var file_tapchannelrpc_tapchannel_proto_rawDesc = []byte{ 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xb2, 0x02, - 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x75, 0x6c, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xab, 0x01, + 0x0a, 0x11, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x12, 0x37, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x12, + 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x62, + 0x75, 0x79, 0x5f, 0x71, 0x75, 0x6f, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x72, 0x66, 0x71, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x10, 0x61, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x42, 0x75, 0x79, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x40, + 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x32, 0x85, 0x03, 0x0a, 0x14, 0x54, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x46, 0x75, 0x6e, + 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x43, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, - 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, - 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x53, 0x65, - 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, + 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x6c, 0x0a, 0x13, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x29, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, + 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x30, 0x01, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, - 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, - 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, - 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x22, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x12, 0x20, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x61, 0x70, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x2f, 0x74, 0x61, 0x70, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x61, 0x70, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -644,7 +810,7 @@ func file_tapchannelrpc_tapchannel_proto_rawDescGZIP() []byte { return file_tapchannelrpc_tapchannel_proto_rawDescData } -var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_tapchannelrpc_tapchannel_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_tapchannelrpc_tapchannel_proto_goTypes = []interface{}{ (*FundChannelRequest)(nil), // 0: tapchannelrpc.FundChannelRequest (*FundChannelResponse)(nil), // 1: tapchannelrpc.FundChannelResponse @@ -653,30 +819,40 @@ var file_tapchannelrpc_tapchannel_proto_goTypes = []interface{}{ (*EncodeCustomRecordsResponse)(nil), // 4: tapchannelrpc.EncodeCustomRecordsResponse (*SendPaymentRequest)(nil), // 5: tapchannelrpc.SendPaymentRequest (*SendPaymentResponse)(nil), // 6: tapchannelrpc.SendPaymentResponse - nil, // 7: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry - nil, // 8: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry - (*routerrpc.SendPaymentRequest)(nil), // 9: routerrpc.SendPaymentRequest - (*rfqrpc.PeerAcceptedSellQuote)(nil), // 10: rfqrpc.PeerAcceptedSellQuote - (*lnrpc.Payment)(nil), // 11: lnrpc.Payment + (*AddInvoiceRequest)(nil), // 7: tapchannelrpc.AddInvoiceRequest + (*AddInvoiceResponse)(nil), // 8: tapchannelrpc.AddInvoiceResponse + nil, // 9: tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + nil, // 10: tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + (*routerrpc.SendPaymentRequest)(nil), // 11: routerrpc.SendPaymentRequest + (*rfqrpc.PeerAcceptedSellQuote)(nil), // 12: rfqrpc.PeerAcceptedSellQuote + (*lnrpc.Payment)(nil), // 13: lnrpc.Payment + (*lnrpc.Invoice)(nil), // 14: lnrpc.Invoice + (*rfqrpc.PeerAcceptedBuyQuote)(nil), // 15: rfqrpc.PeerAcceptedBuyQuote + (*lnrpc.AddInvoiceResponse)(nil), // 16: lnrpc.AddInvoiceResponse } var file_tapchannelrpc_tapchannel_proto_depIdxs = []int32{ - 7, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry + 9, // 0: tapchannelrpc.RouterSendPaymentData.asset_amounts:type_name -> tapchannelrpc.RouterSendPaymentData.AssetAmountsEntry 2, // 1: tapchannelrpc.EncodeCustomRecordsRequest.router_send_payment:type_name -> tapchannelrpc.RouterSendPaymentData - 8, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry - 9, // 3: tapchannelrpc.SendPaymentRequest.payment_request:type_name -> routerrpc.SendPaymentRequest - 10, // 4: tapchannelrpc.SendPaymentResponse.accepted_sell_order:type_name -> rfqrpc.PeerAcceptedSellQuote - 11, // 5: tapchannelrpc.SendPaymentResponse.payment_result:type_name -> lnrpc.Payment - 0, // 6: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest - 3, // 7: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest - 5, // 8: tapchannelrpc.TaprootAssetChannels.SendPayment:input_type -> tapchannelrpc.SendPaymentRequest - 1, // 9: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse - 4, // 10: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse - 6, // 11: tapchannelrpc.TaprootAssetChannels.SendPayment:output_type -> tapchannelrpc.SendPaymentResponse - 9, // [9:12] is the sub-list for method output_type - 6, // [6:9] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 10, // 2: tapchannelrpc.EncodeCustomRecordsResponse.custom_records:type_name -> tapchannelrpc.EncodeCustomRecordsResponse.CustomRecordsEntry + 11, // 3: tapchannelrpc.SendPaymentRequest.payment_request:type_name -> routerrpc.SendPaymentRequest + 12, // 4: tapchannelrpc.SendPaymentResponse.accepted_sell_order:type_name -> rfqrpc.PeerAcceptedSellQuote + 13, // 5: tapchannelrpc.SendPaymentResponse.payment_result:type_name -> lnrpc.Payment + 14, // 6: tapchannelrpc.AddInvoiceRequest.invoice_request:type_name -> lnrpc.Invoice + 15, // 7: tapchannelrpc.AddInvoiceResponse.accepted_buy_quote:type_name -> rfqrpc.PeerAcceptedBuyQuote + 16, // 8: tapchannelrpc.AddInvoiceResponse.invoice_result:type_name -> lnrpc.AddInvoiceResponse + 0, // 9: tapchannelrpc.TaprootAssetChannels.FundChannel:input_type -> tapchannelrpc.FundChannelRequest + 3, // 10: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:input_type -> tapchannelrpc.EncodeCustomRecordsRequest + 5, // 11: tapchannelrpc.TaprootAssetChannels.SendPayment:input_type -> tapchannelrpc.SendPaymentRequest + 7, // 12: tapchannelrpc.TaprootAssetChannels.AddInvoice:input_type -> tapchannelrpc.AddInvoiceRequest + 1, // 13: tapchannelrpc.TaprootAssetChannels.FundChannel:output_type -> tapchannelrpc.FundChannelResponse + 4, // 14: tapchannelrpc.TaprootAssetChannels.EncodeCustomRecords:output_type -> tapchannelrpc.EncodeCustomRecordsResponse + 6, // 15: tapchannelrpc.TaprootAssetChannels.SendPayment:output_type -> tapchannelrpc.SendPaymentResponse + 8, // 16: tapchannelrpc.TaprootAssetChannels.AddInvoice:output_type -> tapchannelrpc.AddInvoiceResponse + 13, // [13:17] is the sub-list for method output_type + 9, // [9:13] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_tapchannelrpc_tapchannel_proto_init() } @@ -769,6 +945,30 @@ func file_tapchannelrpc_tapchannel_proto_init() { return nil } } + file_tapchannelrpc_tapchannel_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddInvoiceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tapchannelrpc_tapchannel_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddInvoiceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_tapchannelrpc_tapchannel_proto_msgTypes[3].OneofWrappers = []interface{}{ (*EncodeCustomRecordsRequest_RouterSendPayment)(nil), @@ -783,7 +983,7 @@ func file_tapchannelrpc_tapchannel_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_tapchannelrpc_tapchannel_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/taprpc/tapchannelrpc/tapchannel.pb.gw.go b/taprpc/tapchannelrpc/tapchannel.pb.gw.go index 5cf7a22ed..ea154ac21 100644 --- a/taprpc/tapchannelrpc/tapchannel.pb.gw.go +++ b/taprpc/tapchannelrpc/tapchannel.pb.gw.go @@ -126,6 +126,40 @@ func request_TaprootAssetChannels_SendPayment_0(ctx context.Context, marshaler r } +func request_TaprootAssetChannels_AddInvoice_0(ctx context.Context, marshaler runtime.Marshaler, client TaprootAssetChannelsClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddInvoiceRequest + 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.AddInvoice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TaprootAssetChannels_AddInvoice_0(ctx context.Context, marshaler runtime.Marshaler, server TaprootAssetChannelsServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddInvoiceRequest + 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.AddInvoice(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterTaprootAssetChannelsHandlerServer registers the http handlers for service TaprootAssetChannels to "mux". // UnaryRPC :call TaprootAssetChannelsServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -189,6 +223,31 @@ func RegisterTaprootAssetChannelsHandlerServer(ctx context.Context, mux *runtime return }) + mux.Handle("POST", pattern_TaprootAssetChannels_AddInvoice_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, "/tapchannelrpc.TaprootAssetChannels/AddInvoice", runtime.WithHTTPPathPattern("/v1/taproot-assets/channels/invoice")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TaprootAssetChannels_AddInvoice_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_TaprootAssetChannels_AddInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -296,6 +355,28 @@ func RegisterTaprootAssetChannelsHandlerClient(ctx context.Context, mux *runtime }) + mux.Handle("POST", pattern_TaprootAssetChannels_AddInvoice_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, "/tapchannelrpc.TaprootAssetChannels/AddInvoice", runtime.WithHTTPPathPattern("/v1/taproot-assets/channels/invoice")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TaprootAssetChannels_AddInvoice_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_TaprootAssetChannels_AddInvoice_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -305,6 +386,8 @@ var ( pattern_TaprootAssetChannels_EncodeCustomRecords_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "encode-custom-data"}, "")) pattern_TaprootAssetChannels_SendPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "send-payment"}, "")) + + pattern_TaprootAssetChannels_AddInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "taproot-assets", "channels", "invoice"}, "")) ) var ( @@ -313,4 +396,6 @@ var ( forward_TaprootAssetChannels_EncodeCustomRecords_0 = runtime.ForwardResponseMessage forward_TaprootAssetChannels_SendPayment_0 = runtime.ForwardResponseStream + + forward_TaprootAssetChannels_AddInvoice_0 = runtime.ForwardResponseMessage ) diff --git a/taprpc/tapchannelrpc/tapchannel.proto b/taprpc/tapchannelrpc/tapchannel.proto index 5de40ec5f..ed30fa60f 100644 --- a/taprpc/tapchannelrpc/tapchannel.proto +++ b/taprpc/tapchannelrpc/tapchannel.proto @@ -32,6 +32,13 @@ service TaprootAssetChannels { amount. */ rpc SendPayment (SendPaymentRequest) returns (stream SendPaymentResponse); + + /* + AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset + specific parameters. It allows RPC users to create invoices that correspond + to the specified asset amount. + */ + rpc AddInvoice (AddInvoiceRequest) returns (AddInvoiceResponse); } message FundChannelRequest { @@ -130,3 +137,33 @@ message SendPaymentResponse { lnrpc.Payment payment_result = 2; } } + +message AddInvoiceRequest { + // The asset ID to use for the invoice. + bytes asset_id = 1; + + // The asset amount to receive. + uint64 asset_amount = 2; + + // The node identity public key of the peer to ask for a quote for receiving + // assets and converting them from satoshis. This must be specified if + // there are multiple channels with the given asset ID. + bytes peer_pubkey = 3; + + // The full lnd invoice request to send. All fields (except for the value + // and the route hints) behave the same way as they do for lnd's + // lnrpc.AddInvoice RPC method (see the API docs at + // https://lightning.engineering/api-docs/api/lnd/lightning/add-invoice + // for more details). The value/value_msat fields will be overwritten by the + // satoshi (or milli-satoshi) equivalent of the asset amount, after + // negotiating a quote with a peer that supports the given asset ID. + lnrpc.Invoice invoice_request = 4; +} + +message AddInvoiceResponse { + // The quote for the purchase of assets that was accepted by the peer. + rfqrpc.PeerAcceptedBuyQuote accepted_buy_quote = 1; + + // The result of the invoice creation. + lnrpc.AddInvoiceResponse invoice_result = 2; +} diff --git a/taprpc/tapchannelrpc/tapchannel.swagger.json b/taprpc/tapchannelrpc/tapchannel.swagger.json index 829d1e2e7..b9cd9d6d1 100644 --- a/taprpc/tapchannelrpc/tapchannel.swagger.json +++ b/taprpc/tapchannelrpc/tapchannel.swagger.json @@ -114,6 +114,39 @@ ] } }, + "/v1/taproot-assets/channels/invoice": { + "post": { + "summary": "AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset\nspecific parameters. It allows RPC users to create invoices that correspond\nto the specified asset amount.", + "operationId": "TaprootAssetChannels_AddInvoice", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/tapchannelrpcAddInvoiceResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tapchannelrpcAddInvoiceRequest" + } + } + ], + "tags": [ + "TaprootAssetChannels" + ] + } + }, "/v1/taproot-assets/channels/send-payment": { "post": { "summary": "SendPayment is a wrapper around lnd's routerrpc.SendPaymentV2 RPC method\nwith asset specific parameters. It allows RPC users to send asset keysend\npayments (direct payments) or payments to an invoice with a specified asset\namount.", @@ -203,6 +236,71 @@ ], "default": "IN_FLIGHT" }, + "InvoiceInvoiceState": { + "type": "string", + "enum": [ + "OPEN", + "SETTLED", + "CANCELED", + "ACCEPTED" + ], + "default": "OPEN" + }, + "lnrpcAMP": { + "type": "object", + "properties": { + "root_share": { + "type": "string", + "format": "byte", + "description": "An n-of-n secret share of the root seed from which child payment hashes\nand preimages are derived." + }, + "set_id": { + "type": "string", + "format": "byte", + "description": "An identifier for the HTLC set that this HTLC belongs to." + }, + "child_index": { + "type": "integer", + "format": "int64", + "description": "A nonce used to randomize the child preimage and child hash from a given\nroot_share." + }, + "hash": { + "type": "string", + "format": "byte", + "description": "The payment hash of the AMP HTLC." + }, + "preimage": { + "type": "string", + "format": "byte", + "description": "The preimage used to settle this AMP htlc. This field will only be\npopulated if the invoice is in InvoiceState_ACCEPTED or\nInvoiceState_SETTLED." + } + }, + "description": "Details specific to AMP HTLCs." + }, + "lnrpcAMPInvoiceState": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/lnrpcInvoiceHTLCState", + "description": "The state the HTLCs associated with this setID are in." + }, + "settle_index": { + "type": "string", + "format": "uint64", + "description": "The settle index of this HTLC set, if the invoice state is settled." + }, + "settle_time": { + "type": "string", + "format": "int64", + "description": "The time this HTLC set was settled expressed in unix epoch." + }, + "amt_paid_msat": { + "type": "string", + "format": "int64", + "description": "The total amount paid for the sub-invoice expressed in milli satoshis." + } + } + }, "lnrpcAMPRecord": { "type": "object", "properties": { @@ -220,6 +318,29 @@ } } }, + "lnrpcAddInvoiceResponse": { + "type": "object", + "properties": { + "r_hash": { + "type": "string", + "format": "byte" + }, + "payment_request": { + "type": "string", + "description": "A bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient." + }, + "add_index": { + "type": "string", + "format": "uint64", + "description": "The \"add\" index of this invoice. Each newly created invoice will increment\nthis index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all added\ninvoices with an add_index greater than this one." + }, + "payment_addr": { + "type": "string", + "format": "byte", + "description": "The payment address of the generated invoice. This is also called\npayment secret in specifications (e.g. BOLT 11). This value should be used\nin all payments for this invoice as we require it for end to end security." + } + } + }, "lnrpcChannelUpdate": { "type": "object", "properties": { @@ -328,6 +449,20 @@ } } }, + "lnrpcFeature": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "is_required": { + "type": "boolean" + }, + "is_known": { + "type": "boolean" + } + } + }, "lnrpcFeatureBit": { "type": "string", "enum": [ @@ -505,6 +640,230 @@ } } }, + "lnrpcInvoice": { + "type": "object", + "properties": { + "memo": { + "type": "string", + "description": "An optional memo to attach along with the invoice. Used for record keeping\npurposes for the invoice's creator, and will also be set in the description\nfield of the encoded payment request if the description_hash field is not\nbeing used." + }, + "r_preimage": { + "type": "string", + "format": "byte", + "description": "The hex-encoded preimage (32 byte) which will allow settling an incoming\nHTLC payable to this preimage. When using REST, this field must be encoded\nas base64." + }, + "r_hash": { + "type": "string", + "format": "byte", + "description": "The hash of the preimage. When using REST, this field must be encoded as\nbase64.\nNote: Output only, don't specify for creating an invoice." + }, + "value": { + "type": "string", + "format": "int64", + "description": "The fields value and value_msat are mutually exclusive.", + "title": "The value of this invoice in satoshis" + }, + "value_msat": { + "type": "string", + "format": "int64", + "description": "The fields value and value_msat are mutually exclusive.", + "title": "The value of this invoice in millisatoshis" + }, + "settled": { + "type": "boolean", + "description": "Whether this invoice has been fulfilled.\n\nThe field is deprecated. Use the state field instead (compare to SETTLED)." + }, + "creation_date": { + "type": "string", + "format": "int64", + "description": "When this invoice was created.\nMeasured in seconds since the unix epoch.\nNote: Output only, don't specify for creating an invoice." + }, + "settle_date": { + "type": "string", + "format": "int64", + "description": "When this invoice was settled.\nMeasured in seconds since the unix epoch.\nNote: Output only, don't specify for creating an invoice." + }, + "payment_request": { + "type": "string", + "description": "A bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient.\nNote: Output only, don't specify for creating an invoice." + }, + "description_hash": { + "type": "string", + "format": "byte", + "description": "Hash (SHA-256) of a description of the payment. Used if the description of\npayment (memo) is too long to naturally fit within the description field\nof an encoded payment request. When using REST, this field must be encoded\nas base64." + }, + "expiry": { + "type": "string", + "format": "int64", + "description": "Payment request expiry time in seconds. Default is 86400 (24 hours)." + }, + "fallback_addr": { + "type": "string", + "description": "Fallback on-chain address." + }, + "cltv_expiry": { + "type": "string", + "format": "uint64", + "description": "Delta to use for the time-lock of the CLTV extended to the final hop." + }, + "route_hints": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcRouteHint" + }, + "description": "Route hints that can each be individually used to assist in reaching the\ninvoice's destination." + }, + "private": { + "type": "boolean", + "description": "Whether this invoice should include routing hints for private channels.\nNote: When enabled, if value and value_msat are zero, a large number of\nhints with these channels can be included, which might not be desirable." + }, + "add_index": { + "type": "string", + "format": "uint64", + "description": "The \"add\" index of this invoice. Each newly created invoice will increment\nthis index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all added\ninvoices with an add_index greater than this one.\nNote: Output only, don't specify for creating an invoice." + }, + "settle_index": { + "type": "string", + "format": "uint64", + "description": "The \"settle\" index of this invoice. Each newly settled invoice will\nincrement this index making it monotonically increasing. Callers to the\nSubscribeInvoices call can use this to instantly get notified of all\nsettled invoices with an settle_index greater than this one.\nNote: Output only, don't specify for creating an invoice." + }, + "amt_paid": { + "type": "string", + "format": "int64", + "description": "Deprecated, use amt_paid_sat or amt_paid_msat." + }, + "amt_paid_sat": { + "type": "string", + "format": "int64", + "description": "The amount that was accepted for this invoice, in satoshis. This will ONLY\nbe set if this invoice has been settled or accepted. We provide this field\nas if the invoice was created with a zero value, then we need to record what\namount was ultimately accepted. Additionally, it's possible that the sender\npaid MORE that was specified in the original invoice. So we'll record that\nhere as well.\nNote: Output only, don't specify for creating an invoice." + }, + "amt_paid_msat": { + "type": "string", + "format": "int64", + "description": "The amount that was accepted for this invoice, in millisatoshis. This will\nONLY be set if this invoice has been settled or accepted. We provide this\nfield as if the invoice was created with a zero value, then we need to\nrecord what amount was ultimately accepted. Additionally, it's possible that\nthe sender paid MORE that was specified in the original invoice. So we'll\nrecord that here as well.\nNote: Output only, don't specify for creating an invoice." + }, + "state": { + "$ref": "#/definitions/InvoiceInvoiceState", + "description": "The state the invoice is in.\nNote: Output only, don't specify for creating an invoice." + }, + "htlcs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/lnrpcInvoiceHTLC" + }, + "description": "List of HTLCs paying to this invoice [EXPERIMENTAL].\nNote: Output only, don't specify for creating an invoice." + }, + "features": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/lnrpcFeature" + }, + "description": "List of features advertised on the invoice.\nNote: Output only, don't specify for creating an invoice." + }, + "is_keysend": { + "type": "boolean", + "description": "Indicates if this invoice was a spontaneous payment that arrived via keysend\n[EXPERIMENTAL].\nNote: Output only, don't specify for creating an invoice." + }, + "payment_addr": { + "type": "string", + "format": "byte", + "description": "The payment address of this invoice. This is also called payment secret in\nspecifications (e.g. BOLT 11). This value will be used in MPP payments, and\nalso for newer invoices that always require the MPP payload for added\nend-to-end security.\nNote: Output only, don't specify for creating an invoice." + }, + "is_amp": { + "type": "boolean", + "description": "Signals whether or not this is an AMP invoice." + }, + "amp_invoice_state": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/lnrpcAMPInvoiceState" + }, + "description": "Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the\ngiven set ID. This field is always populated for AMP invoices, and can be\nused along side LookupInvoice to obtain the HTLC information related to a\ngiven sub-invoice.\nNote: Output only, don't specify for creating an invoice.", + "title": "[EXPERIMENTAL]:" + } + } + }, + "lnrpcInvoiceHTLC": { + "type": "object", + "properties": { + "chan_id": { + "type": "string", + "format": "uint64", + "description": "Short channel id over which the htlc was received." + }, + "htlc_index": { + "type": "string", + "format": "uint64", + "description": "Index identifying the htlc on the channel." + }, + "amt_msat": { + "type": "string", + "format": "uint64", + "description": "The amount of the htlc in msat." + }, + "accept_height": { + "type": "integer", + "format": "int32", + "description": "Block height at which this htlc was accepted." + }, + "accept_time": { + "type": "string", + "format": "int64", + "description": "Time at which this htlc was accepted." + }, + "resolve_time": { + "type": "string", + "format": "int64", + "description": "Time at which this htlc was settled or canceled." + }, + "expiry_height": { + "type": "integer", + "format": "int32", + "description": "Block height at which this htlc expires." + }, + "state": { + "$ref": "#/definitions/lnrpcInvoiceHTLCState", + "description": "Current state the htlc is in." + }, + "custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "Custom tlv records." + }, + "mpp_total_amt_msat": { + "type": "string", + "format": "uint64", + "description": "The total amount of the mpp payment in msat." + }, + "amp": { + "$ref": "#/definitions/lnrpcAMP", + "description": "Details relevant to AMP HTLCs, only populated if this is an AMP HTLC." + }, + "wire_custom_records": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "description": "Custom tlv records that were only sent on the p2p wire message, not in\nthe onion." + } + }, + "title": "Details of an HTLC that paid to an invoice" + }, + "lnrpcInvoiceHTLCState": { + "type": "string", + "enum": [ + "ACCEPTED", + "SETTLED", + "CANCELED" + ], + "default": "ACCEPTED" + }, "lnrpcMPPRecord": { "type": "object", "properties": { @@ -683,6 +1042,40 @@ }, "additionalProperties": {} }, + "rfqrpcPeerAcceptedBuyQuote": { + "type": "object", + "properties": { + "peer": { + "type": "string", + "description": "Quote counterparty peer." + }, + "id": { + "type": "string", + "format": "byte", + "description": "The unique identifier of the quote request." + }, + "scid": { + "type": "string", + "format": "uint64", + "description": "scid is the short channel ID of the channel over which the payment for\nthe quote should be made." + }, + "asset_amount": { + "type": "string", + "format": "uint64", + "description": "asset_amount is the amount of the subject asset." + }, + "ask_price": { + "type": "string", + "format": "uint64", + "description": "ask_price is the price in milli-satoshi per asset unit." + }, + "expiry": { + "type": "string", + "format": "uint64", + "description": "The unix timestamp in seconds after which the quote is no longer valid." + } + } + }, "rfqrpcPeerAcceptedSellQuote": { "type": "object", "properties": { @@ -871,6 +1264,43 @@ } } }, + "tapchannelrpcAddInvoiceRequest": { + "type": "object", + "properties": { + "asset_id": { + "type": "string", + "format": "byte", + "description": "The asset ID to use for the invoice." + }, + "asset_amount": { + "type": "string", + "format": "uint64", + "description": "The asset amount to receive." + }, + "peer_pubkey": { + "type": "string", + "format": "byte", + "description": "The node identity public key of the peer to ask for a quote for receiving\nassets and converting them from satoshis. This must be specified if\nthere are multiple channels with the given asset ID." + }, + "invoice_request": { + "$ref": "#/definitions/lnrpcInvoice", + "description": "The full lnd invoice request to send. All fields (except for the value\nand the route hints) behave the same way as they do for lnd's\nlnrpc.AddInvoice RPC method (see the API docs at\nhttps://lightning.engineering/api-docs/api/lnd/lightning/add-invoice\nfor more details). The value/value_msat fields will be overwritten by the\nsatoshi (or milli-satoshi) equivalent of the asset amount, after\nnegotiating a quote with a peer that supports the given asset ID." + } + } + }, + "tapchannelrpcAddInvoiceResponse": { + "type": "object", + "properties": { + "accepted_buy_quote": { + "$ref": "#/definitions/rfqrpcPeerAcceptedBuyQuote", + "description": "The quote for the purchase of assets that was accepted by the peer." + }, + "invoice_result": { + "$ref": "#/definitions/lnrpcAddInvoiceResponse", + "description": "The result of the invoice creation." + } + } + }, "tapchannelrpcEncodeCustomRecordsRequest": { "type": "object", "properties": { diff --git a/taprpc/tapchannelrpc/tapchannel.yaml b/taprpc/tapchannelrpc/tapchannel.yaml index 3e7f63eb8..a396b5078 100644 --- a/taprpc/tapchannelrpc/tapchannel.yaml +++ b/taprpc/tapchannelrpc/tapchannel.yaml @@ -11,3 +11,6 @@ http: - selector: tapchannelrpc.TaprootAssetChannels.SendPayment post: "/v1/taproot-assets/channels/send-payment" body: "*" + - selector: tapchannelrpc.TaprootAssetChannels.AddInvoice + post: "/v1/taproot-assets/channels/invoice" + body: "*" diff --git a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go index cc5ec5bf6..d7fb1307b 100644 --- a/taprpc/tapchannelrpc/tapchannel_grpc.pb.go +++ b/taprpc/tapchannelrpc/tapchannel_grpc.pb.go @@ -32,6 +32,10 @@ type TaprootAssetChannelsClient interface { // payments (direct payments) or payments to an invoice with a specified asset // amount. SendPayment(ctx context.Context, in *SendPaymentRequest, opts ...grpc.CallOption) (TaprootAssetChannels_SendPaymentClient, error) + // AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset + // specific parameters. It allows RPC users to create invoices that correspond + // to the specified asset amount. + AddInvoice(ctx context.Context, in *AddInvoiceRequest, opts ...grpc.CallOption) (*AddInvoiceResponse, error) } type taprootAssetChannelsClient struct { @@ -92,6 +96,15 @@ func (x *taprootAssetChannelsSendPaymentClient) Recv() (*SendPaymentResponse, er return m, nil } +func (c *taprootAssetChannelsClient) AddInvoice(ctx context.Context, in *AddInvoiceRequest, opts ...grpc.CallOption) (*AddInvoiceResponse, error) { + out := new(AddInvoiceResponse) + err := c.cc.Invoke(ctx, "/tapchannelrpc.TaprootAssetChannels/AddInvoice", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // TaprootAssetChannelsServer is the server API for TaprootAssetChannels service. // All implementations must embed UnimplementedTaprootAssetChannelsServer // for forward compatibility @@ -110,6 +123,10 @@ type TaprootAssetChannelsServer interface { // payments (direct payments) or payments to an invoice with a specified asset // amount. SendPayment(*SendPaymentRequest, TaprootAssetChannels_SendPaymentServer) error + // AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset + // specific parameters. It allows RPC users to create invoices that correspond + // to the specified asset amount. + AddInvoice(context.Context, *AddInvoiceRequest) (*AddInvoiceResponse, error) mustEmbedUnimplementedTaprootAssetChannelsServer() } @@ -126,6 +143,9 @@ func (UnimplementedTaprootAssetChannelsServer) EncodeCustomRecords(context.Conte func (UnimplementedTaprootAssetChannelsServer) SendPayment(*SendPaymentRequest, TaprootAssetChannels_SendPaymentServer) error { return status.Errorf(codes.Unimplemented, "method SendPayment not implemented") } +func (UnimplementedTaprootAssetChannelsServer) AddInvoice(context.Context, *AddInvoiceRequest) (*AddInvoiceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddInvoice not implemented") +} func (UnimplementedTaprootAssetChannelsServer) mustEmbedUnimplementedTaprootAssetChannelsServer() {} // UnsafeTaprootAssetChannelsServer may be embedded to opt out of forward compatibility for this service. @@ -196,6 +216,24 @@ func (x *taprootAssetChannelsSendPaymentServer) Send(m *SendPaymentResponse) err return x.ServerStream.SendMsg(m) } +func _TaprootAssetChannels_AddInvoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddInvoiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaprootAssetChannelsServer).AddInvoice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/tapchannelrpc.TaprootAssetChannels/AddInvoice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaprootAssetChannelsServer).AddInvoice(ctx, req.(*AddInvoiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // TaprootAssetChannels_ServiceDesc is the grpc.ServiceDesc for TaprootAssetChannels service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -211,6 +249,10 @@ var TaprootAssetChannels_ServiceDesc = grpc.ServiceDesc{ MethodName: "EncodeCustomRecords", Handler: _TaprootAssetChannels_EncodeCustomRecords_Handler, }, + { + MethodName: "AddInvoice", + Handler: _TaprootAssetChannels_AddInvoice_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go b/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go index be6c3f30b..30abf12a5 100644 --- a/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go +++ b/taprpc/tapchannelrpc/taprootassetchannels.pb.json.go @@ -112,4 +112,29 @@ func RegisterTaprootAssetChannelsJSONCallbacks(registry map[string]func(ctx cont } }() } + + registry["tapchannelrpc.TaprootAssetChannels.AddInvoice"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &AddInvoiceRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewTaprootAssetChannelsClient(conn) + resp, err := client.AddInvoice(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } From 23c09ff3b01758f45d4d1f235059689000dc1d7e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 7 Aug 2024 14:27:03 +0200 Subject: [PATCH 5/5] itest: add basic minimal functionality tests for tchrpc With this commit we make sure that the taproot asset channel functionality RPCs are correctly configured and return the expected error when being called. We can't actually test any of their intended functionality because that would require us to run within a litd context. But the integration tests in the litd repo make sure these RPCs work as expected. --- itest/channels_test.go | 92 ++++++++++++++++++++++++++++++++++++++ itest/tapd_harness.go | 5 +++ itest/test_list_on_test.go | 4 ++ 3 files changed, 101 insertions(+) create mode 100644 itest/channels_test.go diff --git a/itest/channels_test.go b/itest/channels_test.go new file mode 100644 index 000000000..1eb880b04 --- /dev/null +++ b/itest/channels_test.go @@ -0,0 +1,92 @@ +package itest + +import ( + "context" + + "github.com/lightninglabs/taproot-assets/rfqmsg" + tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/record" + "github.com/stretchr/testify/require" +) + +var ( + dummyByteArr = [32]byte{0x01, 0x02, 0x03, 0x04} +) + +// testChannelRPCs tests that we can call all Taproot Asset Channel related +// RPCs and get the correct error message back (as we can't really test the +// actual functionality without running within litd). This at least makes sure +// we've set up everything correctly in terms of macaroons and permissions. +func testChannelRPCs(t *harnessTest) { + ctx := context.Background() + + // The EncodeCustomRecords RPC is available, as it only does static + // encoding. + encodeReq := &tchrpc.EncodeCustomRecordsRequest_RouterSendPayment{ + RouterSendPayment: &tchrpc.RouterSendPaymentData{ + RfqId: dummyByteArr[:], + }, + } + encodeResp, err := t.tapd.EncodeCustomRecords( + ctx, &tchrpc.EncodeCustomRecordsRequest{ + Input: encodeReq, + }, + ) + require.NoError(t.t, err) + require.Len(t.t, encodeResp.CustomRecords, 2) + + var rfqIdType rfqmsg.HtlcRfqIDType + rfqIdTlvTypeNumber := uint64(rfqIdType.TypeVal()) + require.Len(t.t, encodeResp.CustomRecords[rfqIdTlvTypeNumber], 32) + + // All the following calls can't go fully through, and we'll encounter + // an error at some point. + _, err = t.tapd.FundChannel(ctx, &tchrpc.FundChannelRequest{}) + require.ErrorContains(t.t, err, "only available when running inside") + + // Try the keysend path first, which should go all the way through to + // lnd, where it should fail because we didn't set a timeout. + stream, err := t.tapd.SendPayment(ctx, &tchrpc.SendPaymentRequest{ + AssetAmount: 123, + AssetId: dummyByteArr[:], + PaymentRequest: &routerrpc.SendPaymentRequest{ + DestCustomRecords: map[uint64][]byte{ + record.KeySendType: dummyByteArr[:], + }, + }, + }) + require.NoError(t.t, err) + + _, err = stream.Recv() + require.ErrorContains(t.t, err, "timeout_seconds") + + // Now let's also try the invoice path, which should fail because we + // don't have any asset channels with peers that we could ask for a + // quote. + invoiceResp := t.lndHarness.Alice.RPC.AddInvoice(&lnrpc.Invoice{ + AmtPaidSat: 1234, + }) + stream, err = t.tapd.SendPayment(ctx, &tchrpc.SendPaymentRequest{ + AssetId: dummyByteArr[:], + PaymentRequest: &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + }, + }) + require.NoError(t.t, err) + + _, err = stream.Recv() + require.ErrorContains(t.t, err, "no asset channel balance found") + + // We can't add an invoice either, because we have no peers to do RFQ + // negotiation with. + _, err = t.tapd.AddInvoice(ctx, &tchrpc.AddInvoiceRequest{ + AssetAmount: 123, + AssetId: dummyByteArr[:], + InvoiceRequest: &lnrpc.Invoice{ + Private: false, + }, + }) + require.ErrorContains(t.t, err, "no asset channel balance found") +} diff --git a/itest/tapd_harness.go b/itest/tapd_harness.go index 9a061b975..bcdd0ad96 100644 --- a/itest/tapd_harness.go +++ b/itest/tapd_harness.go @@ -23,6 +23,7 @@ import ( "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" + tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightningnetwork/lnd/lnrpc" @@ -96,6 +97,7 @@ type tapdHarness struct { assetwalletrpc.AssetWalletClient mintrpc.MintClient rfqrpc.RfqClient + tchrpc.TaprootAssetChannelsClient universerpc.UniverseClient tapdevrpc.TapDevClient } @@ -348,6 +350,9 @@ func (hs *tapdHarness) start(expectErrExit bool) error { hs.AssetWalletClient = assetwalletrpc.NewAssetWalletClient(rpcConn) hs.MintClient = mintrpc.NewMintClient(rpcConn) hs.RfqClient = rfqrpc.NewRfqClient(rpcConn) + hs.TaprootAssetChannelsClient = tchrpc.NewTaprootAssetChannelsClient( + rpcConn, + ) hs.UniverseClient = universerpc.NewUniverseClient(rpcConn) hs.TapDevClient = tapdevrpc.NewTapDevClient(rpcConn) diff --git a/itest/test_list_on_test.go b/itest/test_list_on_test.go index 7f6c2c731..9941e38e4 100644 --- a/itest/test_list_on_test.go +++ b/itest/test_list_on_test.go @@ -300,6 +300,10 @@ var testCases = []*testCase{ name: "anchor multiple virtual transactions", test: testAnchorMultipleVirtualTransactions, }, + { + name: "channel RPCs", + test: testChannelRPCs, + }, } var optionalTestCases = []*testCase{