From 1c767b1cbe4e7073a3d0d86d801f1d6aff0c4745 Mon Sep 17 00:00:00 2001 From: djkazic Date: Mon, 15 Jan 2024 10:22:29 -0500 Subject: [PATCH] Initial mobilev2 implementation --- .gitignore | 6 +- Makefile | 41 + cmd/litd/main.go | 2 +- config.go | 3 +- litrpc/Dockerfile | 1 + litrpc/gen_protos.sh | 8 +- litrpc/gen_protos_docker.sh | 2 + mobile/README.md | 239 ++ mobile/bindings.go | 145 + mobile/docs/proto_folder.png | Bin 0 -> 21726 bytes mobile/docs/separate_gradle_module.png | Bin 0 -> 23401 bytes mobile/gen_bindings.sh | 282 ++ mobile/go.mod | 3 + mobile/sample_lnd.conf | 14 + proto/assetwalletrpc/assetwallet.proto | 223 ++ proto/autopilotrpc/autopilot.proto | 80 + proto/chainrpc/chainnotifier.proto | 200 + proto/gen_protos_docker.sh | 1 + proto/invoicesrpc/invoices.proto | 175 + proto/lightning.proto | 4957 ++++++++++++++++++++++++ proto/neutrinorpc/neutrino.proto | 228 ++ proto/routerrpc/router.proto | 952 +++++ proto/signrpc/signer.proto | 697 ++++ proto/stateservice.proto | 73 + proto/taprootassets.proto | 1025 +++++ proto/walletrpc/walletkit.proto | 1245 ++++++ proto/walletunlocker.proto | 338 ++ proto/watchtowerrpc/watchtower.proto | 30 + proto/wtclientrpc/wtclient.proto | 224 ++ terminal.go | 42 +- 30 files changed, 11230 insertions(+), 6 deletions(-) create mode 100644 mobile/README.md create mode 100644 mobile/bindings.go create mode 100644 mobile/docs/proto_folder.png create mode 100644 mobile/docs/separate_gradle_module.png create mode 100755 mobile/gen_bindings.sh create mode 100644 mobile/go.mod create mode 100644 mobile/sample_lnd.conf create mode 100644 proto/assetwalletrpc/assetwallet.proto create mode 100644 proto/autopilotrpc/autopilot.proto create mode 100644 proto/chainrpc/chainnotifier.proto create mode 100644 proto/invoicesrpc/invoices.proto create mode 100644 proto/lightning.proto create mode 100644 proto/neutrinorpc/neutrino.proto create mode 100644 proto/routerrpc/router.proto create mode 100644 proto/signrpc/signer.proto create mode 100644 proto/stateservice.proto create mode 100644 proto/taprootassets.proto create mode 100644 proto/walletrpc/walletkit.proto create mode 100644 proto/walletunlocker.proto create mode 100644 proto/watchtowerrpc/watchtower.proto create mode 100644 proto/wtclientrpc/wtclient.proto diff --git a/.gitignore b/.gitignore index 17833a841..c7078ebdd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,13 @@ litcli-debug # MacOS junk .DS_Store +# Files from mobile build. +mobile/build +mobile/*_generated.go + itest/btcd-itest itest/litd-itest itest/lnd-itest itest/itest.test itest/.logs -itest/*.log \ No newline at end of file +itest/*.log diff --git a/Makefile b/Makefile index 070f0860b..b4fe6189e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ PKG := github.com/lightninglabs/lightning-terminal +MOBILE_PKG := $(PKG)/mobile ESCPKG := github.com\/lightninglabs\/lightning-terminal LND_PKG := github.com/lightningnetwork/lnd LOOP_PKG := github.com/lightninglabs/loop @@ -13,6 +14,13 @@ TOOLS_DIR := tools GO_BIN := ${GOPATH}/bin GOACC_BIN := $(GO_BIN)/go-acc GOIMPORTS_BIN := $(GO_BIN)/gosimports +GOMOBILE_BIN := GO111MODULE=off $(GO_BIN)/gomobile + +MOBILE_BUILD_DIR :=${GOPATH}/src/$(MOBILE_PKG)/build +IOS_BUILD_DIR := $(MOBILE_BUILD_DIR)/ios +IOS_BUILD := $(IOS_BUILD_DIR)/Litdmobile.xcframework +ANDROID_BUILD_DIR := $(MOBILE_BUILD_DIR)/android +ANDROID_BUILD := $(ANDROID_BUILD_DIR)/Litdmobile.aar COMMIT := $(shell git describe --abbrev=40 --dirty --tags) COMMIT_HASH := $(shell git rev-parse HEAD) @@ -247,3 +255,36 @@ clean: $(RM) ./litcli-debug $(RM) ./litd-debug $(RM) coverage.txt + +# ============= +# MOBILE +# ============= +mobile-rpc: + @$(call print, "Creating mobile RPC from protos.") + cd ./litrpc; COMPILE_MOBILE=1 SUBSERVER_PREFIX=1 ./gen_protos_docker.sh + +vendor: + @$(call print, "Re-creating vendor directory.") + rm -r vendor/; go mod vendor + +apple: vendor mobile-rpc + @$(call print, "Building iOS and macOS cxframework ($(IOS_BUILD)).") + mkdir -p $(IOS_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=ios,iossimulator,macos -tags="mobile $(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG) + +ios: vendor mobile-rpc + @$(call print, "Building iOS cxframework ($(IOS_BUILD)).") + mkdir -p $(IOS_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=ios,iossimulator -tags="mobile $(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG) + +macos: vendor mobile-rpc + @$(call print, "Building macOS cxframework ($(IOS_BUILD)).") + mkdir -p $(IOS_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=macos -tags="mobile $(RELEASE_LDFLAGS)" -v -o $(IOS_BUILD) $(MOBILE_PKG) + +android: vendor mobile-rpc + @$(call print, "Building Android library ($(ANDROID_BUILD)).") + mkdir -p $(ANDROID_BUILD_DIR) + $(GOMOBILE_BIN) bind -target=android -androidapi 21 -tags="mobile $(LND_RELEASE_TAGS)" -v -o $(ANDROID_BUILD) $(MOBILE_PKG) + +mobile: ios android diff --git a/cmd/litd/main.go b/cmd/litd/main.go index 7e4f655d4..6cbe19188 100644 --- a/cmd/litd/main.go +++ b/cmd/litd/main.go @@ -11,7 +11,7 @@ import ( // main starts the lightning-terminal application. func main() { - err := terminal.New().Run() + err := terminal.New(nil, nil).Run() var flagErr *flags.Error isFlagErr := errors.As(err, &flagErr) if err != nil && (!isFlagErr || flagErr.Type != flags.ErrHelp) { diff --git a/config.go b/config.go index e62fda99b..6a32e65cc 100644 --- a/config.go +++ b/config.go @@ -830,9 +830,10 @@ func buildTLSConfigForHttp2(config *Config) (*tls.Config, error) { if !lnrpc.FileExists(tlsCertPath) && !lnrpc.FileExists(tlsKeyPath) { + //TODO(kevin): make this a config option, not a hardcoded flag certBytes, keyBytes, err := cert.GenCertPair( defaultSelfSignedCertOrganization, nil, nil, - false, DefaultAutogenValidity, + true, DefaultAutogenValidity, ) if err != nil { return nil, fmt.Errorf("failed creating "+ diff --git a/litrpc/Dockerfile b/litrpc/Dockerfile index b2cf12b2e..f2e01a667 100644 --- a/litrpc/Dockerfile +++ b/litrpc/Dockerfile @@ -19,6 +19,7 @@ RUN cd /tmp \ && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \ && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@${GRPC_GATEWAY_VERSION} \ && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} \ + && go install golang.org/x/tools/cmd/goimports@v0.1.7 \ && go install github.com/lightninglabs/falafel@${FALAFEL_VERSION} WORKDIR /build diff --git a/litrpc/gen_protos.sh b/litrpc/gen_protos.sh index 06734e674..067d74507 100755 --- a/litrpc/gen_protos.sh +++ b/litrpc/gen_protos.sh @@ -59,4 +59,10 @@ popd pushd autopilotserverrpc format generate no-rest -popd \ No newline at end of file +popd + +if [[ "$COMPILE_MOBILE" == "1" ]]; then + pushd mobile + ./gen_bindings.sh $FALAFEL_VERSION + popd +fi \ No newline at end of file diff --git a/litrpc/gen_protos_docker.sh b/litrpc/gen_protos_docker.sh index 0f755c2fd..f351f3232 100755 --- a/litrpc/gen_protos_docker.sh +++ b/litrpc/gen_protos_docker.sh @@ -19,5 +19,7 @@ docker run \ --rm \ --user $UID:$UID \ -e UID=$UID \ + -e COMPILE_MOBILE \ + -e SUBSERVER_PREFIX \ -v "$DIR/../:/build" \ lit-protobuf-builder diff --git a/mobile/README.md b/mobile/README.md new file mode 100644 index 000000000..c57eb5290 --- /dev/null +++ b/mobile/README.md @@ -0,0 +1,239 @@ +# Building mobile libraries + +## Prerequisites + +To build for iOS, you need to run macOS with either +[Command Line Tools](https://developer.apple.com/download/all/?q=command%20line%20tools) +or [Xcode](https://apps.apple.com/app/xcode/id497799835) installed. + +To build for Android, you need either +[Android Studio](https://developer.android.com/studio) or +[Command Line Tools](https://developer.android.com/studio#downloads) installed, which in turn must be used to install [Android NDK](https://developer.android.com/ndk/). + + +### Go and Go mobile + +First, follow the instructions to [install Go](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md#building-a-development-version-from-source). + +Then, install [Go mobile](https://github.com/golang/go/wiki/Mobile) and +initialize it: + +```shell +⛰ go install golang.org/x/mobile/cmd/gomobile@latest +⛰ go mod download golang.org/x/mobile +⛰ gomobile init +``` + +### Docker + +Install and run [Docker](https://www.docker.com/products/docker-desktop). + +### Make + +Check that `make` is available by running the following command without errors: + +```shell +⛰ make --version +``` + +## Building the libraries + +Note that `gomobile` only supports building projects from `$GOPATH` at this +point. However, with the introduction of Go modules, the source code files are +no longer installed there by default. + +To be able to do so, we must turn off module and using the now deprecated +`go get` command before turning modules back on again. + +```shell +⛰ go env -w GO111MODULE="off" +⛰ go get github.com/lightninglabs/lightning-terminal +⛰ go get golang.org/x/mobile/bind +⛰ go env -w GO111MODULE="on" +``` + +Finally, let’s change directory to the newly created lightning-terminal folder inside `$GOPATH`: + +```shell +⛰ cd $GOPATH/src/github.com/lightninglabs/lightning-terminal +``` + +It is not recommended building from the master branch for mainnet. To checkout +the latest tagged release of Lightning Terminal, run + +```shell +⛰ git checkout $(git describe --match "v[0-9]*" --abbrev=0) +``` + +### iOS + +```shell +⛰ make ios +``` + +The Xcode framework file will be found in `mobile/build/ios/Litdmobile.xcframework`. + +### Android + +```shell +⛰ make android +``` + +The AAR library file will be found in `mobile/build/android/Litdmobile.aar`. + +--- +Tip: `make mobile` will build both iOS and Android libraries. + +--- + +## Generating proto definitions + +In order to call the methods in the generated library, the serialized proto for +the given RPC call must be provided. Similarly, the response will be a +serialized proto. + +### iOS + +In order to generate protobuf definitions for iOS, add `--swift_out=.` to the +first `protoc` invocation found in [ `gen_protos.sh` ](../lnrpc/gen_protos.sh). + +Then, some changes to [Dockerfile](../lnrpc/Dockerfile) need to be done in +order to use the [Swift protobuf](https://github.com/apple/swift-protobuf) +plugin with protoc: + +1. Replace the base image with `FROM swift:focal` so that Swift can be used. +2. `clang-format='1:7.0*'` is unavailable in Ubuntu Focal. Change that to +`clang-format='1:10.0*'`. +3. On the next line, install Go and set the environment variables by adding the +following commands: + +``` +RUN apt-get install -y wget \ + && wget -c https://golang.org/dl/go1.17.6.linux-amd64.tar.gz -O - \ + | tar -xz -C /usr/local +ENV GOPATH=/go +ENV PATH=$PATH:/usr/local/go/bin:/go/bin +``` + +4. At the end of the file, just above `CMD`, add the following `RUN` command. +This will download and compile the latest tagged release of Swift protobuf. + +``` +RUN git clone https://github.com/apple/swift-protobuf.git \ +&& cd swift-protobuf \ +&& git checkout $(git describe --tags --abbrev=0) \ +&& swift build -c release \ +&& mv .build/release/protoc-gen-swift /bin +``` + +Finally, run `make rpc`. + +Tip: The generated Swift files will be found in various folders. If you’d like +to move them to the same folder as the framework file, run + +```shell +⛰ find . -name "*.swift" -print0 | xargs -0 -I {} mv {} mobile/build/ios +``` + +`Litdmobile.xcframework` and all Swift files should now be added to your Xcode +project. You will also need to add [Swift Protobuf](https://github.com/apple/swift-protobuf) +to your project to support the generated code. + +### Android + +#### First option: + +In order to generate protobuf definitions for Android, add `--java_out=.` + +to the first `protoc` invocation found in +[ `gen_protos.sh` ](../lnrpc/gen_protos.sh). Then, run `make rpc`. + + +#### Second option (preferable): + +- You have to install the profobuf plugin to your Android application. +Please, follow this link https://github.com/google/protobuf-gradle-plugin. +- Add this line to your `app build.gradle` file. +```shell +classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.17" +``` +- Create a `proto` folder under the `main` folder. + +![proto_folder](docs/proto_folder.png) + +- Add `aar` file to libs folder. + +- After that add these lines to your `module's` `build.gradle` file: + +```shell +plugins { + id "com.google.protobuf" +} + +android { + sourceSets { + main { + proto { + + } + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "com.google.protobuf:protobuf-javalite:${rootProject.ext.javalite_version}" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${rootProject.ext.protoc_version}" + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option "lite" + } + } + } + } +} +``` +- Then, copy all the proto files from `lnd/lnrpc` to your `proto` folder, saving the structure. +- Build the project and wait until you see the generated Java proto files in the `build` folder. + + +--- +*Note:* + +If Android Studio tells you that the `aar` file cannot be included into the `app-bundle`, this is a workaround: + +1. Create a separate gradle module +2. Remove everything from there and leave only `aar` and `build.gradle`. + +![separate_gradle_module](docs/separate_gradle_module.png) + +3. Gradle file should contain only these lines: + +```shell +configurations.maybeCreate("default") +artifacts.add("default", file('Litdmobile.aar')) +``` + +4. In `dependencies` add this line instead of depending on `libs` folder: +```shell +implementation project(":litdmobile", { "default" }) +``` +--- + +## Calling the API + +In LND v0.15+ all API methods have prefixed the generated methods with the subserver name. This is required to support subservers with name conflicts. + +eg. `QueryScores` is now `AutopilotQueryScores`. `GetBlockHeader` is now `NeutrinoKitGetBlockHeader`. + +## API docs + +- [LND gRPC API Reference](https://api.lightning.community) +- [LND Builder’s Guide](https://docs.lightning.engineering) diff --git a/mobile/bindings.go b/mobile/bindings.go new file mode 100644 index 000000000..8620ff5d9 --- /dev/null +++ b/mobile/bindings.go @@ -0,0 +1,145 @@ +//go:build mobile +// +build mobile + +package litdmobile + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/jessevdk/go-flags" + terminal "github.com/lightninglabs/lightning-terminal" + "github.com/lightningnetwork/lnd" + "google.golang.org/grpc" +) + +// litdStarted will be used atomically to ensure only a single lnd instance is +// attempted to be started at once. +// var litdStarted int32 + +// This function will filter the args based on the prefix and also strip the prefix +// func filterArgs(args []string, prefix string) []string { +// var filteredArgs []string +// for _, arg := range args { +// if strings.HasPrefix(arg, prefix) { +// arg = strings.TrimPrefix(arg, prefix) +// filteredArgs = append(filteredArgs, "--"+arg) +// } +// } +// return filteredArgs +// } + +// Start starts lnd in a new goroutine. +// +// extraArgs can be used to pass command line arguments to lnd that will +// override what is found in the config file. Example: +// +// extraArgs = "--bitcoin.testnet --lnddir=\"/tmp/folder name/\" --profile=5050" +// +// The rpcReady is called lnd is ready to accept RPC calls. +// +// NOTE: On mobile platforms the '--lnddir` argument should be set to the +// current app directory in order to ensure lnd has the permissions needed to +// write to it. +func Start(extraArgs string, rpcReady Callback) { + // We only support a single lnd instance at a time (singleton) for now, + // so we make sure to return immediately if it has already been + // started. + // if !atomic.CompareAndSwapInt32(&litdStarted, 0, 1) { + // err := errors.New("litd already started") + // rpcReady.OnError(err) + // return + // } + // (Re-)initialize the in-mem gRPC listeners we're going to give to lnd. + // This is required each time lnd is started, because when lnd shuts + // down, the in-mem listeners are closed. + RecreateListeners() + + var splitArgs []string + for _, a := range strings.Split(extraArgs, "--") { + // Trim any whitespace space, and ignore empty params. + a := strings.TrimSpace(a) + if a == "" { + continue + } + + // Finally we prefix any non-empty string with -- to mimic the + // regular command line arguments. + splitArgs = append(splitArgs, "--"+a) + } + + // Add the extra arguments to os.Args, as that will be parsed in + // LoadConfig below. + os.Args = append(os.Args, splitArgs...) + + var ( + rpcListening = make(chan struct{}) + // quit = make(chan struct{}) + ) + go func() { + err := terminal.New(lightningLis, rpcListening).Run() + var flagErr *flags.Error + isFlagErr := errors.As(err, &flagErr) + if err != nil && (!isFlagErr || flagErr.Type != flags.ErrHelp) { + file, fileErr := os.OpenFile("/data/user/0/com.liminal/files/lit-start.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if fileErr != nil { + fmt.Println("Failed to open log file:", fileErr) + os.Exit(1) + } + // Write the error to the file + _, writeErr := file.WriteString(err.Error() + "\n") + if writeErr != nil { + fmt.Println("Failed to write to log file:", writeErr) + os.Exit(1) + } + // Close the file + file.Close() + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + }() + + dummyCfg := &lnd.Config{ + TLSCertPath: "/data/user/0/com.liminal/files/tls.cert", + NoMacaroons: false, + AdminMacPath: "/data/user/0/com.liminal/files/lnd/data/chain/bitcoin/mainnet/admin.macaroon", + } + proxyCfg := &lnd.Config{} + // By default we'll apply the admin auth options, which will include + // macaroons. + setDefaultDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(dummyCfg, false) + }, + ) + // For the WalletUnlocker and StateService, the macaroons might not be + // available yet when called, so we use a more restricted set of + // options that don't include them. + setWalletUnlockerDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(dummyCfg, true) + }, + ) + setProxyDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(proxyCfg, true) + }, + ) + setStateDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(dummyCfg, true) + }, + ) + + // By this point, we should be started, call rpcReady + go func() { + select { + case <-rpcListening: + // case <-quit: + // return + } + rpcReady.OnResponse([]byte{}) + }() +} diff --git a/mobile/docs/proto_folder.png b/mobile/docs/proto_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..43f0e15701bf238e9264a3c4676973c11257c6ad GIT binary patch literal 21726 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 T0t@huawl%;7+x>eazOkK>&{4~ literal 0 HcmV?d00001 diff --git a/mobile/gen_bindings.sh b/mobile/gen_bindings.sh new file mode 100755 index 000000000..9ae160225 --- /dev/null +++ b/mobile/gen_bindings.sh @@ -0,0 +1,282 @@ +#!/bin/sh + +mkdir -p build + +# Check falafel version. +falafelVersion=$1 +if [ -z $falafelVersion ] +then + echo "falafel version not set" + exit 1 +fi + +falafel=$(which falafel) +if [ $falafel ] +then + version="v$($falafel -v)" + if [ $version != $falafelVersion ] + then + echo "falafel version $falafelVersion required, had $version" + exit 1 + fi + echo "Using plugin $falafel $version" +else + echo "falafel not found" + exit 1 +fi + +# Name of the package for the generated APIs. +pkg="litdmobile" + +# The package where the protobuf definitions originally are found. +litd_target_pkg="github.com/lightninglabs/lightning-terminal/litrpc" + +lnd_target_pkg="github.com/lightningnetwork/lnd/lnrpc" +autopilot_target_pkg="github.com/lightningnetwork/lnd/lnrpc/autopilotrpc" +chain_target_pkg="github.com/lightningnetwork/lnd/lnrpc/chainrpc" +invoices_target_pkg="github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" +neutrino_target_pkg="github.com/lightningnetwork/lnd/lnrpc/neutrinorpc" +router_target_pkg="github.com/lightningnetwork/lnd/lnrpc/routerrpc" +signer_target_pkg="github.com/lightningnetwork/lnd/lnrpc/signrpc" +wallet_target_pkg="github.com/lightningnetwork/lnd/lnrpc/walletrpc" +watchtower_target_pkg="github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" +watchtower_client_target_pkg="github.com/lightningnetwork/lnd/lnrpc/wtclientrpc" + +loop_target_pkg="github.com/lightninglabs/loop/looprpc" +pool_target_pkg="github.com/lightninglabs/pool/poolrpc" + +tapd_target_pkg="github.com/lightninglabs/taproot-assets/taprpc" +assetwallet_target_pkg="github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + +# A mapping from grpc service to name of the custom listeners. The grpc server +# must be configured to listen on these. +listeners="accounts=lightningLis sessions=lightningLis proxy=lightningLis lightning=lightningLis walletunlocker=lightningLis state=lightningLis autopilot=lightningLis chainnotifier=lightningLis invoices=lightningLis neutrinokit=lightningLis peers=lightningLis router=lightningLis signer=lightningLis versioner=lightningLis walletkit=lightningLis watchtower=lightningLis watchtowerclient=lightningLis swapclient=lightningLis swapserver=lightningLis trader=lightningLis channelauctioneer=lightningLis taprootassets=lightningLis assetwallet=lightningLis" + +# Set to 1 to create boiler plate grpc client code and listeners. If more than +# one proto file is being parsed, it should only be done once. +mem_rpc=1 + +LITD_PROTOS="lit-accounts.proto lit-sessions.proto proxy.proto" + +LND_PROTOS="lnd.proto stateservice.proto walletunlocker.proto" +AUTOPILOT_PROTOS="autopilotrpc/autopilot.proto" +CHAIN_PROTOS="chainrpc/chainnotifier.proto" +INVOICES_PROTOS="invoicesrpc/invoices.proto" +NEUTRINO_PROTOS="neutrinorpc/neutrino.proto" +ROUTER_PROTOS="routerrpc/router.proto" +SIGNER_PROTOS="signrpc/signer.proto" +WALLET_PROTOS="walletrpc/walletkit.proto" +WATCHTOWER_PROTOS="watchtowerrpc/watchtower.proto" +WATCHTOWER_CLIENT_PROTOS="wtclientrpc/wtclient.proto" + +LOOP_PROTOS="loop.proto swapserverrpc/common.proto" + +POOL_PROTOS="trader.proto auctioneerrpc/auctioneer.proto" + +TAPD_PROTOS="taprootassets.proto assetwalletrpc/assetwallet.proto" + +# If prefix=1 is specified, prefix the generated methods with subserver name. +# This must be enabled to support subservers with name conflicts. +use_prefix="0" +if [ "$SUBSERVER_PREFIX" = "1" ] +then + echo "Prefixing methods with subserver name" + use_prefix="1" +fi + +litd_opts="package_name=$pkg,target_package=$litd_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" + +lnd_opts="package_name=$pkg,target_package=$lnd_target_pkg,api_prefix=0,listeners=$listeners,mem_rpc=$mem_rpc" +autopilot_opts="package_name=$pkg,target_package=$autopilot_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +chain_opts="package_name=$pkg,target_package=$chain_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +invoices_opts="package_name=$pkg,target_package=$invoices_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +neutrino_opts="package_name=$pkg,target_package=$neutrino_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +router_opts="package_name=$pkg,target_package=$router_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +signer_opts="package_name=$pkg,target_package=$signer_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +wallet_opts="package_name=$pkg,target_package=$wallet_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +watchtower_opts="package_name=$pkg,target_package=$watchtower_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +watchtower_client_opts="package_name=$pkg,target_package=$watchtower_client_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" + +loop_opts="package_name=$pkg,target_package=$loop_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +pool_opts="package_name=$pkg,target_package=$pool_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +auctioneer_opts="package_name=$pkg,target_package=$auctioneer_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +tapd_opts="package_name=$pkg,target_package=$tapd_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" +assetwallet_opts="package_name=$pkg,target_package=$assetwallet_target_pkg,api_prefix=$use_prefix,listeners=$listeners,mem_rpc=$mem_rpc" + +for file in $LITD_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$litd_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $LND_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$lnd_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $AUTOPILOT_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$autopilot_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $CHAIN_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$chain_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $INVOICES_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$invoices_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $NEUTRINO_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$neutrino_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $ROUTER_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$router_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $SIGNER_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$signer_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $WALLET_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$wallet_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $WATCHTOWER_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$watchtower_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $WATCHTOWER_CLIENT_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$watchtower_client_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $LOOP_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$loop_opts" \ + --proto_path=../proto \ + "${file}" +done + +for file in $POOL_PROTOS; do + echo "Generating mobile protos from ${file}" + + protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$pool_opts" \ + --proto_path=../proto \ + "${file}" +done + +# for file in $TAPD_PROTOS; do +echo "Generating mobile protos from taprootassets.proto" +protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$tapd_opts" \ + --proto_path=../proto \ + taprootassets.proto + +echo "Generating mobile protos from assetwalletrpc/assetwallet.proto" +protoc -I/usr/local/include -I. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=./build \ + --custom_opt="$assetwallet_opts" \ + --proto_path=../proto \ + assetwalletrpc/assetwallet.proto +# done + +# TODO update protoc command to avoid this replace hack +sed -i '12i "github.com/lightningnetwork/lnd/lnrpc/signrpc"' ./walletkit_api_generated.go +sed -i '12i "github.com/lightninglabs/pool/auctioneerrpc"' ./trader_api_generated.go +sed -i '14i "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"' ./assetwallet_api_generated.go + +trader_replacements="BatchSnapshotRequest BatchSnapshotsRequest" +for call in $trader_replacements; do + echo "Making replacements for trader_api_generated.go: ${call}" + sed -i "s/poolrpc.${call}/auctioneerrpc.${call}/g" ./trader_api_generated.go +done + +echo "Making replacements for channelauctioneer_api_generated.go" +sed -i "s/poolrpc/auctioneerrpc/g" ./channelauctioneer_api_generated.go + +# Run goimports to resolve any dependencies among the sub-servers. +goimports -w ./*_generated.go diff --git a/mobile/go.mod b/mobile/go.mod new file mode 100644 index 000000000..f3e4d6dd9 --- /dev/null +++ b/mobile/go.mod @@ -0,0 +1,3 @@ +module mobile + +go 1.18 diff --git a/mobile/sample_lnd.conf b/mobile/sample_lnd.conf new file mode 100644 index 000000000..3f4cc6c3e --- /dev/null +++ b/mobile/sample_lnd.conf @@ -0,0 +1,14 @@ +[Application Options] +debuglevel=info +maxbackoff=2s +nolisten=1 +norest=1 +tlsdisableautofill=1 + +[Routing] +routing.assumechanvalid=1 + +[Bitcoin] +bitcoin.active=1 +bitcoin.testnet=1 +bitcoin.node=neutrino diff --git a/proto/assetwalletrpc/assetwallet.proto b/proto/assetwalletrpc/assetwallet.proto new file mode 100644 index 000000000..5c6cbc0cf --- /dev/null +++ b/proto/assetwalletrpc/assetwallet.proto @@ -0,0 +1,223 @@ +syntax = "proto3"; + +import "taprootassets.proto"; + +package assetwalletrpc; + +option go_package = "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"; + +service AssetWallet { + /* + FundVirtualPsbt selects inputs from the available asset commitments to fund + a virtual transaction matching the template. + */ + rpc FundVirtualPsbt (FundVirtualPsbtRequest) + returns (FundVirtualPsbtResponse); + + /* + SignVirtualPsbt signs the inputs of a virtual transaction and prepares the + commitments of the inputs and outputs. + */ + rpc SignVirtualPsbt (SignVirtualPsbtRequest) + returns (SignVirtualPsbtResponse); + + /* + AnchorVirtualPsbts merges and then commits multiple virtual transactions in + a single BTC level anchor transaction. + + TODO(guggero): Actually implement accepting and merging multiple + transactions. + */ + rpc AnchorVirtualPsbts (AnchorVirtualPsbtsRequest) + returns (taprpc.SendAssetResponse); + + /* + NextInternalKey derives the next internal key for the given key family and + stores it as an internal key in the database to make sure it is identified + as a local key later on when importing proofs. While an internal key can + also be used as the internal key of a script key, it is recommended to use + the NextScriptKey RPC instead, to make sure the tweaked Taproot output key + is also recognized as a local key. + */ + rpc NextInternalKey (NextInternalKeyRequest) + returns (NextInternalKeyResponse); + + /* + NextScriptKey derives the next script key (and its corresponding internal + key) and stores them both in the database to make sure they are identified + as local keys later on when importing proofs. + */ + rpc NextScriptKey (NextScriptKeyRequest) returns (NextScriptKeyResponse); + + /* + ProveAssetOwnership creates an ownership proof embedded in an asset + transition proof. That ownership proof is a signed virtual transaction + spending the asset with a valid witness to prove the prover owns the keys + that can spend the asset. + */ + rpc ProveAssetOwnership (ProveAssetOwnershipRequest) + returns (ProveAssetOwnershipResponse); + + /* + VerifyAssetOwnership verifies the asset ownership proof embedded in the + given transition proof of an asset and returns true if the proof is valid. + */ + rpc VerifyAssetOwnership (VerifyAssetOwnershipRequest) + returns (VerifyAssetOwnershipResponse); + + /* + RemoveUTXOLease removes the lease/lock/reservation of the given managed + UTXO. + */ + rpc RemoveUTXOLease (RemoveUTXOLeaseRequest) + returns (RemoveUTXOLeaseResponse); +} + +message FundVirtualPsbtRequest { + oneof template { + /* + Use an existing PSBT packet as the template for the funded PSBT. + + TODO(guggero): Actually implement this. We can't use the "reserved" + keyword here because we're in a oneof, so we add the field but implement + it later. + */ + bytes psbt = 1; + + /* + Use the asset outputs and optional asset inputs from this raw template. + */ + TxTemplate raw = 2; + } +} + +message FundVirtualPsbtResponse { + /* + The funded but not yet signed PSBT packet. + */ + bytes funded_psbt = 1; + + /* + The index of the added change output or -1 if no change was left over. + */ + int32 change_output_index = 2; +} + +message TxTemplate { + /* + An optional list of inputs to use. Every input must be an asset UTXO known + to the wallet. The sum of all inputs must be greater than or equal to the + sum of all outputs. + + If no inputs are specified, asset coin selection will be performed instead + and inputs of sufficient value will be added to the resulting PSBT. + */ + repeated PrevId inputs = 1; + + /* + A map of all Taproot Asset addresses mapped to the anchor transaction's + output index that should be sent to. + */ + map recipients = 2; +} + +message PrevId { + /* + The bitcoin anchor output on chain that contains the input asset. + */ + OutPoint outpoint = 1; + + /* + The asset ID of the previous asset tree. + */ + bytes id = 2; + + /* + The tweaked Taproot output key committing to the possible spending + conditions of the asset. + */ + bytes script_key = 3; +} + +message OutPoint { + /* + Raw bytes representing the transaction id. + */ + bytes txid = 1; + + /* + The index of the output on the transaction. + */ + uint32 output_index = 2; +} + +message SignVirtualPsbtRequest { + /* + The PSBT of the virtual transaction that should be signed. The PSBT must + contain all required inputs, outputs, UTXO data and custom fields required + to identify the signing key. + */ + bytes funded_psbt = 1; +} + +message SignVirtualPsbtResponse { + /* + The signed virtual transaction in PSBT format. + */ + bytes signed_psbt = 1; + + /* + The indices of signed inputs. + */ + repeated uint32 signed_inputs = 2; +} + +message AnchorVirtualPsbtsRequest { + /* + The list of virtual transactions that should be merged and committed to in + the BTC level anchor transaction. + */ + repeated bytes virtual_psbts = 1; +} + +message NextInternalKeyRequest { + uint32 key_family = 1; +} + +message NextInternalKeyResponse { + taprpc.KeyDescriptor internal_key = 1; +} + +message NextScriptKeyRequest { + uint32 key_family = 1; +} + +message NextScriptKeyResponse { + taprpc.ScriptKey script_key = 1; +} + +message ProveAssetOwnershipRequest { + bytes asset_id = 1; + + bytes script_key = 2; +} + +message ProveAssetOwnershipResponse { + bytes proof_with_witness = 1; +} + +message VerifyAssetOwnershipRequest { + bytes proof_with_witness = 1; +} + +message VerifyAssetOwnershipResponse { + bool valid_proof = 1; +} + +message RemoveUTXOLeaseRequest { + // The outpoint of the UTXO to remove the lease for. + OutPoint outpoint = 1; +} + +message RemoveUTXOLeaseResponse { +} diff --git a/proto/autopilotrpc/autopilot.proto b/proto/autopilotrpc/autopilot.proto new file mode 100644 index 000000000..67d0f9e38 --- /dev/null +++ b/proto/autopilotrpc/autopilot.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package autopilotrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"; + +// Autopilot is a service that can be used to get information about the current +// state of the daemon's autopilot agent, and also supply it with information +// that can be used when deciding where to open channels. +service Autopilot { + /* + Status returns whether the daemon's autopilot agent is active. + */ + rpc Status (StatusRequest) returns (StatusResponse); + + /* + ModifyStatus is used to modify the status of the autopilot agent, like + enabling or disabling it. + */ + rpc ModifyStatus (ModifyStatusRequest) returns (ModifyStatusResponse); + + /* + QueryScores queries all available autopilot heuristics, in addition to any + active combination of these heruristics, for the scores they would give to + the given nodes. + */ + rpc QueryScores (QueryScoresRequest) returns (QueryScoresResponse); + + /* + SetScores attempts to set the scores used by the running autopilot agent, + if the external scoring heuristic is enabled. + */ + rpc SetScores (SetScoresRequest) returns (SetScoresResponse); +} + +message StatusRequest { +} + +message StatusResponse { + // Indicates whether the autopilot is active or not. + bool active = 1; +} + +message ModifyStatusRequest { + // Whether the autopilot agent should be enabled or not. + bool enable = 1; +} + +message ModifyStatusResponse { +} + +message QueryScoresRequest { + repeated string pubkeys = 1; + + // If set, we will ignore the local channel state when calculating scores. + bool ignore_local_state = 2; +} + +message QueryScoresResponse { + message HeuristicResult { + string heuristic = 1; + map scores = 2; + } + + repeated HeuristicResult results = 1; +} + +message SetScoresRequest { + // The name of the heuristic to provide scores to. + string heuristic = 1; + + /* + A map from hex-encoded public keys to scores. Scores must be in the range + [0.0, 1.0]. + */ + map scores = 2; +} + +message SetScoresResponse { +} diff --git a/proto/chainrpc/chainnotifier.proto b/proto/chainrpc/chainnotifier.proto new file mode 100644 index 000000000..36e66a279 --- /dev/null +++ b/proto/chainrpc/chainnotifier.proto @@ -0,0 +1,200 @@ +syntax = "proto3"; + +package chainrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/chainrpc"; + +// ChainNotifier is a service that can be used to get information about the +// chain backend by registering notifiers for chain events. +service ChainNotifier { + /* + RegisterConfirmationsNtfn is a synchronous response-streaming RPC that + registers an intent for a client to be notified once a confirmation request + has reached its required number of confirmations on-chain. + + A confirmation request must have a valid output script. It is also possible + to give a transaction ID. If the transaction ID is not set, a notification + is sent once the output script confirms. If the transaction ID is also set, + a notification is sent once the output script confirms in the given + transaction. + */ + rpc RegisterConfirmationsNtfn (ConfRequest) returns (stream ConfEvent); + + /* + RegisterSpendNtfn is a synchronous response-streaming RPC that registers an + intent for a client to be notification once a spend request has been spent + by a transaction that has confirmed on-chain. + + A client can specify whether the spend request should be for a particular + outpoint or for an output script by specifying a zero outpoint. + */ + rpc RegisterSpendNtfn (SpendRequest) returns (stream SpendEvent); + + /* + RegisterBlockEpochNtfn is a synchronous response-streaming RPC that + registers an intent for a client to be notified of blocks in the chain. The + stream will return a hash and height tuple of a block for each new/stale + block in the chain. It is the client's responsibility to determine whether + the tuple returned is for a new or stale block in the chain. + + A client can also request a historical backlog of blocks from a particular + point. This allows clients to be idempotent by ensuring that they do not + missing processing a single block within the chain. + */ + rpc RegisterBlockEpochNtfn (BlockEpoch) returns (stream BlockEpoch); +} + +message ConfRequest { + /* + The transaction hash for which we should request a confirmation notification + for. If set to a hash of all zeros, then the confirmation notification will + be requested for the script instead. + */ + bytes txid = 1; + + /* + An output script within a transaction with the hash above which will be used + by light clients to match block filters. If the transaction hash is set to a + hash of all zeros, then a confirmation notification will be requested for + this script instead. + */ + bytes script = 2; + + /* + The number of desired confirmations the transaction/output script should + reach before dispatching a confirmation notification. + */ + uint32 num_confs = 3; + + /* + The earliest height in the chain for which the transaction/output script + could have been included in a block. This should in most cases be set to the + broadcast height of the transaction/output script. + */ + uint32 height_hint = 4; + + /* + If true, then the block that mines the specified txid/script will be + included in eventual the notification event. + */ + bool include_block = 5; +} + +message ConfDetails { + // The raw bytes of the confirmed transaction. + bytes raw_tx = 1; + + // The hash of the block in which the confirmed transaction was included in. + bytes block_hash = 2; + + // The height of the block in which the confirmed transaction was included + // in. + uint32 block_height = 3; + + // The index of the confirmed transaction within the block. + uint32 tx_index = 4; + + /* + The raw bytes of the block that mined the transaction. Only included if + include_block was set in the request. + */ + bytes raw_block = 5; +} + +message Reorg { + // TODO(wilmer): need to know how the client will use this first. +} + +message ConfEvent { + oneof event { + /* + An event that includes the confirmation details of the request + (txid/ouput script). + */ + ConfDetails conf = 1; + + /* + An event send when the transaction of the request is reorged out of the + chain. + */ + Reorg reorg = 2; + } +} + +message Outpoint { + // The hash of the transaction. + bytes hash = 1; + + // The index of the output within the transaction. + uint32 index = 2; +} + +message SpendRequest { + /* + The outpoint for which we should request a spend notification for. If set to + a zero outpoint, then the spend notification will be requested for the + script instead. A zero or nil outpoint is not supported for Taproot spends + because the output script cannot reliably be computed from the witness alone + and the spent output script is not always available in the rescan context. + So an outpoint must _always_ be specified when registering a spend + notification for a Taproot output. + */ + Outpoint outpoint = 1; + + /* + The output script for the outpoint above. This will be used by light clients + to match block filters. If the outpoint is set to a zero outpoint, then a + spend notification will be requested for this script instead. + */ + bytes script = 2; + + /* + The earliest height in the chain for which the outpoint/output script could + have been spent. This should in most cases be set to the broadcast height of + the outpoint/output script. + */ + uint32 height_hint = 3; + + // TODO(wilmer): extend to support num confs on spending tx. +} + +message SpendDetails { + // The outpoint was that spent. + Outpoint spending_outpoint = 1; + + // The raw bytes of the spending transaction. + bytes raw_spending_tx = 2; + + // The hash of the spending transaction. + bytes spending_tx_hash = 3; + + // The input of the spending transaction that fulfilled the spend request. + uint32 spending_input_index = 4; + + // The height at which the spending transaction was included in a block. + uint32 spending_height = 5; +} + +message SpendEvent { + oneof event { + /* + An event that includes the details of the spending transaction of the + request (outpoint/output script). + */ + SpendDetails spend = 1; + + /* + An event sent when the spending transaction of the request was + reorged out of the chain. + */ + Reorg reorg = 2; + } +} + +message BlockEpoch { + // The hash of the block. + bytes hash = 1; + + // The height of the block. + uint32 height = 2; +} diff --git a/proto/gen_protos_docker.sh b/proto/gen_protos_docker.sh index 6f598c9ef..78fc48fdb 100755 --- a/proto/gen_protos_docker.sh +++ b/proto/gen_protos_docker.sh @@ -13,5 +13,6 @@ docker run \ --rm \ --user $UID:$UID \ -e UID=$UID \ + -e SUBSERVER_PREFIX \ -v "$DIR/../:/build" \ lit-protobuf-builder diff --git a/proto/invoicesrpc/invoices.proto b/proto/invoicesrpc/invoices.proto new file mode 100644 index 000000000..12317d02c --- /dev/null +++ b/proto/invoicesrpc/invoices.proto @@ -0,0 +1,175 @@ +syntax = "proto3"; + +import "lightning.proto"; + +package invoicesrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"; + +// Invoices is a service that can be used to create, accept, settle and cancel +// invoices. +service Invoices { + /* + SubscribeSingleInvoice returns a uni-directional stream (server -> client) + to notify the client of state transitions of the specified invoice. + Initially the current invoice state is always sent out. + */ + rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest) + returns (stream lnrpc.Invoice); + + /* + CancelInvoice cancels a currently open invoice. If the invoice is already + canceled, this call will succeed. If the invoice is already settled, it will + fail. + */ + rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp); + + /* + AddHoldInvoice creates a hold invoice. It ties the invoice to the hash + supplied in the request. + */ + rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp); + + /* + SettleInvoice settles an accepted invoice. If the invoice is already + settled, this call will succeed. + */ + rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp); + + /* + LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced + using either its payment hash, payment address, or set ID. + */ + rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice); +} + +message CancelInvoiceMsg { + // Hash corresponding to the (hold) invoice to cancel. When using + // REST, this field must be encoded as base64. + bytes payment_hash = 1; +} +message CancelInvoiceResp { +} + +message AddHoldInvoiceRequest { + /* + An optional memo to attach along with the invoice. Used for record keeping + purposes for the invoice's creator, and will also be set in the description + field of the encoded payment request if the description_hash field is not + being used. + */ + string memo = 1; + + // The hash of the preimage + bytes hash = 2; + + /* + The value of this invoice in satoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value = 3; + + /* + The value of this invoice in millisatoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value_msat = 10; + + /* + Hash (SHA-256) of a description of the payment. Used if the description of + payment (memo) is too long to naturally fit within the description field + of an encoded payment request. + */ + bytes description_hash = 4; + + // Payment request expiry time in seconds. Default is 86400 (24 hours). + int64 expiry = 5; + + // Fallback on-chain address. + string fallback_addr = 6; + + // Delta to use for the time-lock of the CLTV extended to the final hop. + uint64 cltv_expiry = 7; + + /* + Route hints that can each be individually used to assist in reaching the + invoice's destination. + */ + repeated lnrpc.RouteHint route_hints = 8; + + // Whether this invoice should include routing hints for private channels. + bool private = 9; +} + +message AddHoldInvoiceResp { + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 1; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 2; + + /* + The payment address of the generated invoice. This value should be used + in all payments for this invoice as we require it for end to end + security. + */ + bytes payment_addr = 3; +} + +message SettleInvoiceMsg { + // Externally discovered pre-image that should be used to settle the hold + // invoice. + bytes preimage = 1; +} + +message SettleInvoiceResp { +} + +message SubscribeSingleInvoiceRequest { + reserved 1; + + // Hash corresponding to the (hold) invoice to subscribe to. When using + // REST, this field must be encoded as base64url. + bytes r_hash = 2; +} + +enum LookupModifier { + // The default look up modifier, no look up behavior is changed. + DEFAULT = 0; + + /* + Indicates that when a look up is done based on a set_id, then only that set + of HTLCs related to that set ID should be returned. + */ + HTLC_SET_ONLY = 1; + + /* + Indicates that when a look up is done using a payment_addr, then no HTLCs + related to the payment_addr should be returned. This is useful when one + wants to be able to obtain the set of associated setIDs with a given + invoice, then look up the sub-invoices "projected" by that set ID. + */ + HTLC_SET_BLANK = 2; +} + +message LookupInvoiceMsg { + oneof invoice_ref { + // When using REST, this field must be encoded as base64. + bytes payment_hash = 1; + bytes payment_addr = 2; + bytes set_id = 3; + } + + LookupModifier lookup_modifier = 4; +} diff --git a/proto/lightning.proto b/proto/lightning.proto new file mode 100644 index 000000000..8a41a5a2e --- /dev/null +++ b/proto/lightning.proto @@ -0,0 +1,4957 @@ +syntax = "proto3"; + +package lnrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Lightning is the main RPC server of the daemon. +service Lightning { + /* lncli: `walletbalance` + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control + of the wallet. + */ + rpc WalletBalance (WalletBalanceRequest) returns (WalletBalanceResponse); + + /* lncli: `channelbalance` + ChannelBalance returns a report on the total funds across all open channels, + categorized in local/remote, pending local/remote and unsettled local/remote + balances. + */ + rpc ChannelBalance (ChannelBalanceRequest) returns (ChannelBalanceResponse); + + /* lncli: `listchaintxns` + GetTransactions returns a list describing all the known transactions + relevant to the wallet. + */ + rpc GetTransactions (GetTransactionsRequest) returns (TransactionDetails); + + /* lncli: `estimatefee` + EstimateFee asks the chain backend to estimate the fee rate and total fees + for a transaction that pays to multiple specified outputs. + + When using REST, the `AddrToAmount` map type can be set by appending + `&AddrToAmount[
]=` to the URL. Unfortunately this + map type doesn't appear in the REST API documentation because of a bug in + the grpc-gateway library. + */ + rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse); + + /* lncli: `sendcoins` + SendCoins executes a request to send coins to a particular address. Unlike + SendMany, this RPC call only allows creating a single output at a time. If + neither target_conf, or sat_per_vbyte are set, then the internal wallet will + consult its fee model to determine a fee for the default confirmation + target. + */ + rpc SendCoins (SendCoinsRequest) returns (SendCoinsResponse); + + /* lncli: `listunspent` + Deprecated, use walletrpc.ListUnspent instead. + + ListUnspent returns a list of all utxos spendable by the wallet with a + number of confirmations between the specified minimum and maximum. + */ + rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse); + + /* + SubscribeTransactions creates a uni-directional stream from the server to + the client in which any newly discovered transactions relevant to the + wallet are sent over. + */ + rpc SubscribeTransactions (GetTransactionsRequest) + returns (stream Transaction); + + /* lncli: `sendmany` + SendMany handles a request for a transaction that creates multiple specified + outputs in parallel. If neither target_conf, or sat_per_vbyte are set, then + the internal wallet will consult its fee model to determine a fee for the + default confirmation target. + */ + rpc SendMany (SendManyRequest) returns (SendManyResponse); + + /* lncli: `newaddress` + NewAddress creates a new address under control of the local wallet. + */ + rpc NewAddress (NewAddressRequest) returns (NewAddressResponse); + + /* lncli: `signmessage` + SignMessage signs a message with this node's private key. The returned + signature string is `zbase32` encoded and pubkey recoverable, meaning that + only the message digest and signature are needed for verification. + */ + rpc SignMessage (SignMessageRequest) returns (SignMessageResponse); + + /* lncli: `verifymessage` + VerifyMessage verifies a signature over a message and recovers the signer's + public key. The signature is only deemed valid if the recovered public key + corresponds to a node key in the public Lightning network. The signature + must be zbase32 encoded and signed by an active node in the resident node's + channel database. In addition to returning the validity of the signature, + VerifyMessage also returns the recovered pubkey from the signature. + */ + rpc VerifyMessage (VerifyMessageRequest) returns (VerifyMessageResponse); + + /* lncli: `connect` + ConnectPeer attempts to establish a connection to a remote peer. This is at + the networking level, and is used for communication between nodes. This is + distinct from establishing a channel with a peer. + */ + rpc ConnectPeer (ConnectPeerRequest) returns (ConnectPeerResponse); + + /* lncli: `disconnect` + DisconnectPeer attempts to disconnect one peer from another identified by a + given pubKey. In the case that we currently have a pending or active channel + with the target peer, then this action will be not be allowed. + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse); + + /* lncli: `listpeers` + ListPeers returns a verbose listing of all currently active peers. + */ + rpc ListPeers (ListPeersRequest) returns (ListPeersResponse); + + /* + SubscribePeerEvents creates a uni-directional stream from the server to + the client in which any events relevant to the state of peers are sent + over. Events include peers going online and offline. + */ + rpc SubscribePeerEvents (PeerEventSubscription) returns (stream PeerEvent); + + /* lncli: `getinfo` + GetInfo returns general information concerning the lightning node including + it's identity pubkey, alias, the chains it is connected to, and information + concerning the number of open+pending channels. + */ + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); + + /** lncli: `getrecoveryinfo` + GetRecoveryInfo returns information concerning the recovery mode including + whether it's in a recovery mode, whether the recovery is finished, and the + progress made so far. + */ + rpc GetRecoveryInfo (GetRecoveryInfoRequest) + returns (GetRecoveryInfoResponse); + + // TODO(roasbeef): merge with below with bool? + /* lncli: `pendingchannels` + PendingChannels returns a list of all the channels that are currently + considered "pending". A channel is pending if it has finished the funding + workflow and is waiting for confirmations for the funding txn, or is in the + process of closure, either initiated cooperatively or non-cooperatively. + */ + rpc PendingChannels (PendingChannelsRequest) + returns (PendingChannelsResponse); + + /* lncli: `listchannels` + ListChannels returns a description of all the open channels that this node + is a participant in. + */ + rpc ListChannels (ListChannelsRequest) returns (ListChannelsResponse); + + /* + SubscribeChannelEvents creates a uni-directional stream from the server to + the client in which any updates relevant to the state of the channels are + sent over. Events include new active channels, inactive channels, and closed + channels. + */ + rpc SubscribeChannelEvents (ChannelEventSubscription) + returns (stream ChannelEventUpdate); + + /* lncli: `closedchannels` + ClosedChannels returns a description of all the closed channels that + this node was a participant in. + */ + rpc ClosedChannels (ClosedChannelsRequest) returns (ClosedChannelsResponse); + + /* + OpenChannelSync is a synchronous version of the OpenChannel RPC call. This + call is meant to be consumed by clients to the REST proxy. As with all + other sync calls, all byte slices are intended to be populated as hex + encoded strings. + */ + rpc OpenChannelSync (OpenChannelRequest) returns (ChannelPoint); + + /* lncli: `openchannel` + OpenChannel attempts to open a singly funded channel specified in the + request to a remote peer. Users are able to specify a target number of + blocks that the funding transaction should be confirmed in, or a manual fee + rate to us for the funding transaction. If neither are specified, then a + lax block confirmation target is used. Each OpenStatusUpdate will return + the pending channel ID of the in-progress channel. Depending on the + arguments specified in the OpenChannelRequest, this pending channel ID can + then be used to manually progress the channel funding flow. + */ + rpc OpenChannel (OpenChannelRequest) returns (stream OpenStatusUpdate); + + /* lncli: `batchopenchannel` + BatchOpenChannel attempts to open multiple single-funded channels in a + single transaction in an atomic way. This means either all channel open + requests succeed at once or all attempts are aborted if any of them fail. + This is the safer variant of using PSBTs to manually fund a batch of + channels through the OpenChannel RPC. + */ + rpc BatchOpenChannel (BatchOpenChannelRequest) + returns (BatchOpenChannelResponse); + + /* + FundingStateStep is an advanced funding related call that allows the caller + to either execute some preparatory steps for a funding workflow, or + manually progress a funding workflow. The primary way a funding flow is + identified is via its pending channel ID. As an example, this method can be + used to specify that we're expecting a funding flow for a particular + pending channel ID, for which we need to use specific parameters. + Alternatively, this can be used to interactively drive PSBT signing for + funding for partially complete funding transactions. + */ + rpc FundingStateStep (FundingTransitionMsg) returns (FundingStateStepResp); + + /* + ChannelAcceptor dispatches a bi-directional streaming RPC in which + OpenChannel requests are sent to the client and the client responds with + a boolean that tells LND whether or not to accept the channel. This allows + node operators to specify their own criteria for accepting inbound channels + through a single persistent connection. + */ + rpc ChannelAcceptor (stream ChannelAcceptResponse) + returns (stream ChannelAcceptRequest); + + /* lncli: `closechannel` + CloseChannel attempts to close an active channel identified by its channel + outpoint (ChannelPoint). The actions of this method can additionally be + augmented to attempt a force close after a timeout period in the case of an + inactive peer. If a non-force close (cooperative closure) is requested, + then the user can specify either a target number of blocks until the + closure transaction is confirmed, or a manual fee rate. If neither are + specified, then a default lax, block confirmation target is used. + */ + rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate); + + /* lncli: `abandonchannel` + AbandonChannel removes all channel state from the database except for a + close summary. This method can be used to get rid of permanently unusable + channels due to bugs fixed in newer versions of lnd. This method can also be + used to remove externally funded channels where the funding transaction was + never broadcast. Only available for non-externally funded channels in dev + build. + */ + rpc AbandonChannel (AbandonChannelRequest) returns (AbandonChannelResponse); + + /* lncli: `sendpayment` + Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a + bi-directional streaming RPC for sending payments through the Lightning + Network. A single RPC invocation creates a persistent bi-directional + stream allowing clients to rapidly send payments through the Lightning + Network with a single persistent connection. + */ + rpc SendPayment (stream SendRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + SendPaymentSync is the synchronous non-streaming version of SendPayment. + This RPC is intended to be consumed by clients of the REST proxy. + Additionally, this RPC expects the destination's public key and the payment + hash (if any) to be encoded as hex strings. + */ + rpc SendPaymentSync (SendRequest) returns (SendResponse); + + /* lncli: `sendtoroute` + Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional + streaming RPC for sending payment through the Lightning Network. This + method differs from SendPayment in that it allows users to specify a full + route manually. This can be used for things like rebalancing, and atomic + swaps. + */ + rpc SendToRoute (stream SendToRouteRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + SendToRouteSync is a synchronous version of SendToRoute. It Will block + until the payment either fails or succeeds. + */ + rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse); + + /* lncli: `addinvoice` + AddInvoice attempts to add a new invoice to the invoice database. Any + duplicated invoices are rejected, therefore all invoices *must* have a + unique payment preimage. + */ + rpc AddInvoice (Invoice) returns (AddInvoiceResponse); + + /* lncli: `listinvoices` + ListInvoices returns a list of all the invoices currently stored within the + database. Any active debug invoices are ignored. It has full support for + paginated responses, allowing users to query for specific invoices through + their add_index. This can be done by using either the first_index_offset or + last_index_offset fields included in the response as the index_offset of the + next request. By default, the first 100 invoices created will be returned. + Backwards pagination is also supported through the Reversed flag. + */ + rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse); + + /* lncli: `lookupinvoice` + LookupInvoice attempts to look up an invoice according to its payment hash. + The passed payment hash *must* be exactly 32 bytes, if not, an error is + returned. + */ + rpc LookupInvoice (PaymentHash) returns (Invoice); + + /* + SubscribeInvoices returns a uni-directional stream (server -> client) for + notifying the client of newly added/settled invoices. The caller can + optionally specify the add_index and/or the settle_index. If the add_index + is specified, then we'll first start by sending add invoice events for all + invoices with an add_index greater than the specified value. If the + settle_index is specified, the next, we'll send out all settle events for + invoices with a settle_index greater than the specified value. One or both + of these fields can be set. If no fields are set, then we'll only send out + the latest add/settle events. + */ + rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice); + + /* lncli: `decodepayreq` + DecodePayReq takes an encoded payment request string and attempts to decode + it, returning a full description of the conditions encoded within the + payment request. + */ + rpc DecodePayReq (PayReqString) returns (PayReq); + + /* lncli: `listpayments` + ListPayments returns a list of all outgoing payments. + */ + rpc ListPayments (ListPaymentsRequest) returns (ListPaymentsResponse); + + /* + DeletePayment deletes an outgoing payment from DB. Note that it will not + attempt to delete an In-Flight payment, since that would be unsafe. + */ + rpc DeletePayment (DeletePaymentRequest) returns (DeletePaymentResponse); + + /* + DeleteAllPayments deletes all outgoing payments from DB. Note that it will + not attempt to delete In-Flight payments, since that would be unsafe. + */ + rpc DeleteAllPayments (DeleteAllPaymentsRequest) + returns (DeleteAllPaymentsResponse); + + /* lncli: `describegraph` + DescribeGraph returns a description of the latest graph state from the + point of view of the node. The graph information is partitioned into two + components: all the nodes/vertexes, and all the edges that connect the + vertexes themselves. As this is a directed graph, the edges also contain + the node directional specific routing policy which includes: the time lock + delta, fee information, etc. + */ + rpc DescribeGraph (ChannelGraphRequest) returns (ChannelGraph); + + /* lncli: `getnodemetrics` + GetNodeMetrics returns node metrics calculated from the graph. Currently + the only supported metric is betweenness centrality of individual nodes. + */ + rpc GetNodeMetrics (NodeMetricsRequest) returns (NodeMetricsResponse); + + /* lncli: `getchaninfo` + GetChanInfo returns the latest authenticated network announcement for the + given channel identified by its channel ID: an 8-byte integer which + uniquely identifies the location of transaction's funding output within the + blockchain. + */ + rpc GetChanInfo (ChanInfoRequest) returns (ChannelEdge); + + /* lncli: `getnodeinfo` + GetNodeInfo returns the latest advertised, aggregated, and authenticated + channel information for the specified node identified by its public key. + */ + rpc GetNodeInfo (NodeInfoRequest) returns (NodeInfo); + + /* lncli: `queryroutes` + QueryRoutes attempts to query the daemon's Channel Router for a possible + route to a target destination capable of carrying a specific amount of + satoshis. The returned route contains the full details required to craft and + send an HTLC, also including the necessary information that should be + present within the Sphinx packet encapsulated within the HTLC. + + When using REST, the `dest_custom_records` map type can be set by appending + `&dest_custom_records[]=` + to the URL. Unfortunately this map type doesn't appear in the REST API + documentation because of a bug in the grpc-gateway library. + */ + rpc QueryRoutes (QueryRoutesRequest) returns (QueryRoutesResponse); + + /* lncli: `getnetworkinfo` + GetNetworkInfo returns some basic stats about the known channel graph from + the point of view of the node. + */ + rpc GetNetworkInfo (NetworkInfoRequest) returns (NetworkInfo); + + /* lncli: `stop` + StopDaemon will send a shutdown request to the interrupt handler, triggering + a graceful shutdown of the daemon. + */ + rpc StopDaemon (StopRequest) returns (StopResponse); + + /* + SubscribeChannelGraph launches a streaming RPC that allows the caller to + receive notifications upon any changes to the channel graph topology from + the point of view of the responding node. Events notified include: new + nodes coming online, nodes updating their authenticated attributes, new + channels being advertised, updates in the routing policy for a directional + channel edge, and when channels are closed on-chain. + */ + rpc SubscribeChannelGraph (GraphTopologySubscription) + returns (stream GraphTopologyUpdate); + + /* lncli: `debuglevel` + DebugLevel allows a caller to programmatically set the logging verbosity of + lnd. The logging can be targeted according to a coarse daemon-wide logging + level, or in a granular fashion to specify the logging for a target + sub-system. + */ + rpc DebugLevel (DebugLevelRequest) returns (DebugLevelResponse); + + /* lncli: `feereport` + FeeReport allows the caller to obtain a report detailing the current fee + schedule enforced by the node globally for each channel. + */ + rpc FeeReport (FeeReportRequest) returns (FeeReportResponse); + + /* lncli: `updatechanpolicy` + UpdateChannelPolicy allows the caller to update the fee schedule and + channel policies for all channels globally, or a particular channel. + */ + rpc UpdateChannelPolicy (PolicyUpdateRequest) + returns (PolicyUpdateResponse); + + /* lncli: `fwdinghistory` + ForwardingHistory allows the caller to query the htlcswitch for a record of + all HTLCs forwarded within the target time range, and integer offset + within that time range, for a maximum number of events. If no maximum number + of events is specified, up to 100 events will be returned. If no time-range + is specified, then events will be returned in the order that they occured. + + A list of forwarding events are returned. The size of each forwarding event + is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. + As a result each message can only contain 50k entries. Each response has + the index offset of the last entry. The index offset can be provided to the + request to allow the caller to skip a series of records. + */ + rpc ForwardingHistory (ForwardingHistoryRequest) + returns (ForwardingHistoryResponse); + + /* lncli: `exportchanbackup` + ExportChannelBackup attempts to return an encrypted static channel backup + for the target channel identified by it channel point. The backup is + encrypted with a key generated from the aezeed seed of the user. The + returned backup can either be restored using the RestoreChannelBackup + method once lnd is running, or via the InitWallet and UnlockWallet methods + from the WalletUnlocker service. + */ + rpc ExportChannelBackup (ExportChannelBackupRequest) + returns (ChannelBackup); + + /* + ExportAllChannelBackups returns static channel backups for all existing + channels known to lnd. A set of regular singular static channel backups for + each channel are returned. Additionally, a multi-channel backup is returned + as well, which contains a single encrypted blob containing the backups of + each channel. + */ + rpc ExportAllChannelBackups (ChanBackupExportRequest) + returns (ChanBackupSnapshot); + + /* + VerifyChanBackup allows a caller to verify the integrity of a channel backup + snapshot. This method will accept either a packed Single or a packed Multi. + Specifying both will result in an error. + */ + rpc VerifyChanBackup (ChanBackupSnapshot) + returns (VerifyChanBackupResponse); + + /* lncli: `restorechanbackup` + RestoreChannelBackups accepts a set of singular channel backups, or a + single encrypted multi-chan backup and attempts to recover any funds + remaining within the channel. If we are able to unpack the backup, then the + new channel will be shown under listchannels, as well as pending channels. + */ + rpc RestoreChannelBackups (RestoreChanBackupRequest) + returns (RestoreBackupResponse); + + /* + SubscribeChannelBackups allows a client to sub-subscribe to the most up to + date information concerning the state of all channel backups. Each time a + new channel is added, we return the new set of channels, along with a + multi-chan backup containing the backup info for all channels. Each time a + channel is closed, we send a new update, which contains new new chan back + ups, but the updated set of encrypted multi-chan backups with the closed + channel(s) removed. + */ + rpc SubscribeChannelBackups (ChannelBackupSubscription) + returns (stream ChanBackupSnapshot); + + /* lncli: `bakemacaroon` + BakeMacaroon allows the creation of a new macaroon with custom read and + write permissions. No first-party caveats are added since this can be done + offline. + */ + rpc BakeMacaroon (BakeMacaroonRequest) returns (BakeMacaroonResponse); + + /* lncli: `listmacaroonids` + ListMacaroonIDs returns all root key IDs that are in use. + */ + rpc ListMacaroonIDs (ListMacaroonIDsRequest) + returns (ListMacaroonIDsResponse); + + /* lncli: `deletemacaroonid` + DeleteMacaroonID deletes the specified macaroon ID and invalidates all + macaroons derived from that ID. + */ + rpc DeleteMacaroonID (DeleteMacaroonIDRequest) + returns (DeleteMacaroonIDResponse); + + /* lncli: `listpermissions` + ListPermissions lists all RPC method URIs and their required macaroon + permissions to access them. + */ + rpc ListPermissions (ListPermissionsRequest) + returns (ListPermissionsResponse); + + /* + CheckMacaroonPermissions checks whether a request follows the constraints + imposed on the macaroon and that the macaroon is authorized to follow the + provided permissions. + */ + rpc CheckMacaroonPermissions (CheckMacPermRequest) + returns (CheckMacPermResponse); + + /* + RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A + gRPC middleware is software component external to lnd that aims to add + additional business logic to lnd by observing/intercepting/validating + incoming gRPC client requests and (if needed) replacing/overwriting outgoing + messages before they're sent to the client. When registering the middleware + must identify itself and indicate what custom macaroon caveats it wants to + be responsible for. Only requests that contain a macaroon with that specific + custom caveat are then sent to the middleware for inspection. The other + option is to register for the read-only mode in which all requests/responses + are forwarded for interception to the middleware but the middleware is not + allowed to modify any responses. As a security measure, _no_ middleware can + modify responses for requests made with _unencumbered_ macaroons! + */ + rpc RegisterRPCMiddleware (stream RPCMiddlewareResponse) + returns (stream RPCMiddlewareRequest); + + /* lncli: `sendcustom` + SendCustomMessage sends a custom peer message. + */ + rpc SendCustomMessage (SendCustomMessageRequest) + returns (SendCustomMessageResponse); + + /* lncli: `subscribecustom` + SubscribeCustomMessages subscribes to a stream of incoming custom peer + messages. + + To include messages with type outside of the custom range (>= 32768) lnd + needs to be compiled with the `dev` build tag, and the message type to + override should be specified in lnd's experimental protocol configuration. + */ + rpc SubscribeCustomMessages (SubscribeCustomMessagesRequest) + returns (stream CustomMessage); + + /* lncli: `listaliases` + ListAliases returns the set of all aliases that have ever existed with + their confirmed SCID (if it exists) and/or the base SCID (in the case of + zero conf). + */ + rpc ListAliases (ListAliasesRequest) returns (ListAliasesResponse); + + /* + LookupHtlcResolution retrieves a final htlc resolution from the database. + If the htlc has no final resolution yet, a NotFound grpc status code is + returned. + */ + rpc LookupHtlcResolution (LookupHtlcResolutionRequest) + returns (LookupHtlcResolutionResponse); +} + +message LookupHtlcResolutionRequest { + uint64 chan_id = 1; + + uint64 htlc_index = 2; +} + +message LookupHtlcResolutionResponse { + // Settled is true is the htlc was settled. If false, the htlc was failed. + bool settled = 1; + + // Offchain indicates whether the htlc was resolved off-chain or on-chain. + bool offchain = 2; +} + +message SubscribeCustomMessagesRequest { +} + +message CustomMessage { + // Peer from which the message originates + bytes peer = 1; + + // Message type. This value will be in the custom range (>= 32768). + uint32 type = 2; + + // Raw message data + bytes data = 3; +} + +message SendCustomMessageRequest { + // Peer to send the message to + bytes peer = 1; + + // Message type. This value needs to be in the custom range (>= 32768). + // To send a type < custom range, lnd needs to be compiled with the `dev` + // build tag, and the message type to override should be specified in lnd's + // experimental protocol configuration. + uint32 type = 2; + + // Raw message data. + bytes data = 3; +} + +message SendCustomMessageResponse { +} + +message Utxo { + // The type of address + AddressType address_type = 1; + + // The address + string address = 2; + + // The value of the unspent coin in satoshis + int64 amount_sat = 3; + + // The pkscript in hex + string pk_script = 4; + + // The outpoint in format txid:n + OutPoint outpoint = 5; + + // The number of confirmations for the Utxo + int64 confirmations = 6; +} + +enum OutputScriptType { + SCRIPT_TYPE_PUBKEY_HASH = 0; + SCRIPT_TYPE_SCRIPT_HASH = 1; + SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH = 2; + SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH = 3; + SCRIPT_TYPE_PUBKEY = 4; + SCRIPT_TYPE_MULTISIG = 5; + SCRIPT_TYPE_NULLDATA = 6; + SCRIPT_TYPE_NON_STANDARD = 7; + SCRIPT_TYPE_WITNESS_UNKNOWN = 8; + SCRIPT_TYPE_WITNESS_V1_TAPROOT = 9; +} + +message OutputDetail { + // The type of the output + OutputScriptType output_type = 1; + + // The address + string address = 2; + + // The pkscript in hex + string pk_script = 3; + + // The output index used in the raw transaction + int64 output_index = 4; + + // The value of the output coin in satoshis + int64 amount = 5; + + // Denotes if the output is controlled by the internal wallet + bool is_our_address = 6; +} + +message Transaction { + // The transaction hash + string tx_hash = 1; + + // The transaction amount, denominated in satoshis + int64 amount = 2; + + // The number of confirmations + int32 num_confirmations = 3; + + // The hash of the block this transaction was included in + string block_hash = 4; + + // The height of the block this transaction was included in + int32 block_height = 5; + + // Timestamp of this transaction + int64 time_stamp = 6; + + // Fees paid for this transaction + int64 total_fees = 7; + + // Addresses that received funds for this transaction. Deprecated as it is + // now incorporated in the output_details field. + repeated string dest_addresses = 8 [deprecated = true]; + + // Outputs that received funds for this transaction + repeated OutputDetail output_details = 11; + + // The raw transaction hex. + string raw_tx_hex = 9; + + // A label that was optionally set on transaction broadcast. + string label = 10; + + // PreviousOutpoints/Inputs of this transaction. + repeated PreviousOutPoint previous_outpoints = 12; +} + +message GetTransactionsRequest { + /* + The height from which to list transactions, inclusive. If this value is + greater than end_height, transactions will be read in reverse. + */ + int32 start_height = 1; + + /* + The height until which to list transactions, inclusive. To include + unconfirmed transactions, this value should be set to -1, which will + return transactions from start_height until the current chain tip and + unconfirmed transactions. If no end_height is provided, the call will + default to this option. + */ + int32 end_height = 2; + + // An optional filter to only include transactions relevant to an account. + string account = 3; +} + +message TransactionDetails { + // The list of transactions relevant to the wallet. + repeated Transaction transactions = 1; +} + +message FeeLimit { + oneof limit { + /* + The fee limit expressed as a fixed amount of satoshis. + + The fields fixed and fixed_msat are mutually exclusive. + */ + int64 fixed = 1; + + /* + The fee limit expressed as a fixed amount of millisatoshis. + + The fields fixed and fixed_msat are mutually exclusive. + */ + int64 fixed_msat = 3; + + // The fee limit expressed as a percentage of the payment amount. + int64 percent = 2; + } +} + +message SendRequest { + /* + The identity pubkey of the payment recipient. When using REST, this field + must be encoded as base64. + */ + bytes dest = 1; + + /* + The hex-encoded identity pubkey of the payment recipient. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string dest_string = 2 [deprecated = true]; + + /* + The amount to send expressed in satoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 3; + + /* + The amount to send expressed in millisatoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + /* + The hash to use within the payment's HTLC. When using REST, this field + must be encoded as base64. + */ + bytes payment_hash = 4; + + /* + The hex-encoded hash to use within the payment's HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 5 [deprecated = true]; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 6; + + /* + The CLTV delta from the current height that should be used to set the + timelock for the final hop. + */ + int32 final_cltv_delta = 7; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + This value can be represented either as a percentage of the amount being + sent, or as a fixed amount of the maximum fee the user is willing the pay to + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. + */ + FeeLimit fee_limit = 8; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 9 [jstype = JS_STRING]; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 13; + + /* + An optional maximum total time lock for the route. This should not exceed + lnd's `--max-cltv-expiry` setting. If zero, then the value of + `--max-cltv-expiry` is enforced. + */ + uint32 cltv_limit = 10; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. Record types are + required to be in the custom range >= 65536. When using REST, the values + must be encoded as base64. + */ + map dest_custom_records = 11; + + // If set, circular payments to self are permitted. + bool allow_self_payment = 14; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + */ + repeated FeatureBit dest_features = 15; + + /* + The payment address of the generated invoice. + */ + bytes payment_addr = 16; +} + +message SendResponse { + string payment_error = 1; + bytes payment_preimage = 2; + Route payment_route = 3; + bytes payment_hash = 4; +} + +message SendToRouteRequest { + /* + The payment hash to use for the HTLC. When using REST, this field must be + encoded as base64. + */ + bytes payment_hash = 1; + + /* + An optional hex-encoded payment hash to be used for the HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 2 [deprecated = true]; + + reserved 3; + + // Route that should be used to attempt to complete the payment. + Route route = 4; +} + +message ChannelAcceptRequest { + // The pubkey of the node that wishes to open an inbound channel. + bytes node_pubkey = 1; + + // The hash of the genesis block that the proposed channel resides in. + bytes chain_hash = 2; + + // The pending channel id. + bytes pending_chan_id = 3; + + // The funding amount in satoshis that initiator wishes to use in the + // channel. + uint64 funding_amt = 4; + + // The push amount of the proposed channel in millisatoshis. + uint64 push_amt = 5; + + // The dust limit of the initiator's commitment tx. + uint64 dust_limit = 6; + + // The maximum amount of coins in millisatoshis that can be pending in this + // channel. + uint64 max_value_in_flight = 7; + + // The minimum amount of satoshis the initiator requires us to have at all + // times. + uint64 channel_reserve = 8; + + // The smallest HTLC in millisatoshis that the initiator will accept. + uint64 min_htlc = 9; + + // The initial fee rate that the initiator suggests for both commitment + // transactions. + uint64 fee_per_kw = 10; + + /* + The number of blocks to use for the relative time lock in the pay-to-self + output of both commitment transactions. + */ + uint32 csv_delay = 11; + + // The total number of incoming HTLC's that the initiator will accept. + uint32 max_accepted_htlcs = 12; + + // A bit-field which the initiator uses to specify proposed channel + // behavior. + uint32 channel_flags = 13; + + // The commitment type the initiator wishes to use for the proposed channel. + CommitmentType commitment_type = 14; + + // Whether the initiator wants to open a zero-conf channel via the channel + // type. + bool wants_zero_conf = 15; + + // Whether the initiator wants to use the scid-alias channel type. This is + // separate from the feature bit. + bool wants_scid_alias = 16; +} + +message ChannelAcceptResponse { + // Whether or not the client accepts the channel. + bool accept = 1; + + // The pending channel id to which this response applies. + bytes pending_chan_id = 2; + + /* + An optional error to send the initiating party to indicate why the channel + was rejected. This field *should not* contain sensitive information, it will + be sent to the initiating party. This field should only be set if accept is + false, the channel will be rejected if an error is set with accept=true + because the meaning of this response is ambiguous. Limited to 500 + characters. + */ + string error = 3; + + /* + The upfront shutdown address to use if the initiating peer supports option + upfront shutdown script (see ListPeers for the features supported). Note + that the channel open will fail if this value is set for a peer that does + not support this feature bit. + */ + string upfront_shutdown = 4; + + /* + The csv delay (in blocks) that we require for the remote party. + */ + uint32 csv_delay = 5; + + /* + The reserve amount in satoshis that we require the remote peer to adhere to. + We require that the remote peer always have some reserve amount allocated to + them so that there is always a disincentive to broadcast old state (if they + hold 0 sats on their side of the channel, there is nothing to lose). + */ + uint64 reserve_sat = 6; + + /* + The maximum amount of funds in millisatoshis that we allow the remote peer + to have in outstanding htlcs. + */ + uint64 in_flight_max_msat = 7; + + /* + The maximum number of htlcs that the remote peer can offer us. + */ + uint32 max_htlc_count = 8; + + /* + The minimum value in millisatoshis for incoming htlcs on the channel. + */ + uint64 min_htlc_in = 9; + + /* + The number of confirmations we require before we consider the channel open. + */ + uint32 min_accept_depth = 10; + + /* + Whether the responder wants this to be a zero-conf channel. This will fail + if either side does not have the scid-alias feature bit set. The minimum + depth field must be zero if this is true. + */ + bool zero_conf = 11; +} + +message ChannelPoint { + oneof funding_txid { + /* + Txid of the funding transaction. When using REST, this field must be + encoded as base64. + */ + bytes funding_txid_bytes = 1; + + /* + Hex-encoded string representing the byte-reversed hash of the funding + transaction. + */ + string funding_txid_str = 2; + } + + // The index of the output of the funding transaction + uint32 output_index = 3; +} + +message OutPoint { + // Raw bytes representing the transaction id. + bytes txid_bytes = 1; + + // Reversed, hex-encoded string representing the transaction id. + string txid_str = 2; + + // The index of the output on the transaction. + uint32 output_index = 3; +} + +message PreviousOutPoint { + // The outpoint in format txid:n. + string outpoint = 1; + + // Denotes if the outpoint is controlled by the internal wallet. + // The flag will only detect p2wkh, np2wkh and p2tr inputs as its own. + bool is_our_output = 2; +} + +message LightningAddress { + // The identity pubkey of the Lightning node. + string pubkey = 1; + + // The network location of the lightning node, e.g. `69.69.69.69:1337` or + // `localhost:10011`. + string host = 2; +} + +message EstimateFeeRequest { + // The map from addresses to amounts for the transaction. + map AddrToAmount = 1; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 2; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 3; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 4; +} + +message EstimateFeeResponse { + // The total fee in satoshis. + int64 fee_sat = 1; + + // Deprecated, use sat_per_vbyte. + // The fee rate in satoshi/vbyte. + int64 feerate_sat_per_byte = 2 [deprecated = true]; + + // The fee rate in satoshi/vbyte. + uint64 sat_per_vbyte = 3; +} + +message SendManyRequest { + // The map from addresses to amounts + map AddrToAmount = 1; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 3; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + uint64 sat_per_vbyte = 4; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + int64 sat_per_byte = 5 [deprecated = true]; + + // An optional label for the transaction, limited to 500 characters. + string label = 6; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 7; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 8; +} +message SendManyResponse { + // The id of the transaction + string txid = 1; +} + +message SendCoinsRequest { + // The address to send coins to + string addr = 1; + + // The amount in satoshis to send + int64 amount = 2; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 3; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + uint64 sat_per_vbyte = 4; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + int64 sat_per_byte = 5 [deprecated = true]; + + /* + If set, then the amount field will be ignored, and lnd will attempt to + send all the coins under control of the internal wallet to the specified + address. + */ + bool send_all = 6; + + // An optional label for the transaction, limited to 500 characters. + string label = 7; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 8; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 9; +} +message SendCoinsResponse { + // The transaction ID of the transaction + string txid = 1; +} + +message ListUnspentRequest { + // The minimum number of confirmations to be included. + int32 min_confs = 1; + + // The maximum number of confirmations to be included. + int32 max_confs = 2; + + // An optional filter to only include outputs belonging to an account. + string account = 3; +} +message ListUnspentResponse { + // A list of utxos + repeated Utxo utxos = 1; +} + +/* +`AddressType` has to be one of: + +- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) +- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) +*/ +enum AddressType { + WITNESS_PUBKEY_HASH = 0; + NESTED_PUBKEY_HASH = 1; + UNUSED_WITNESS_PUBKEY_HASH = 2; + UNUSED_NESTED_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; + UNUSED_TAPROOT_PUBKEY = 5; +} + +message NewAddressRequest { + // The type of address to generate. + AddressType type = 1; + + /* + The name of the account to generate a new address for. If empty, the + default wallet account is used. + */ + string account = 2; +} +message NewAddressResponse { + // The newly generated wallet address + string address = 1; +} + +message SignMessageRequest { + /* + The message to be signed. When using REST, this field must be encoded as + base64. + */ + bytes msg = 1; + + /* + Instead of the default double-SHA256 hashing of the message before signing, + only use one round of hashing instead. + */ + bool single_hash = 2; +} +message SignMessageResponse { + // The signature for the given message + string signature = 1; +} + +message VerifyMessageRequest { + /* + The message over which the signature is to be verified. When using REST, + this field must be encoded as base64. + */ + bytes msg = 1; + + // The signature to be verified over the given message + string signature = 2; +} +message VerifyMessageResponse { + // Whether the signature was valid over the given message + bool valid = 1; + + // The pubkey recovered from the signature + string pubkey = 2; +} + +message ConnectPeerRequest { + /* + Lightning address of the peer to connect to. + */ + LightningAddress addr = 1; + + /* + If set, the daemon will attempt to persistently connect to the target + peer. Otherwise, the call will be synchronous. + */ + bool perm = 2; + + /* + The connection timeout value (in seconds) for this request. It won't affect + other requests. + */ + uint64 timeout = 3; +} +message ConnectPeerResponse { +} + +message DisconnectPeerRequest { + // The pubkey of the node to disconnect from + string pub_key = 1; +} +message DisconnectPeerResponse { +} + +message HTLC { + bool incoming = 1; + int64 amount = 2; + bytes hash_lock = 3; + uint32 expiration_height = 4; + + // Index identifying the htlc on the channel. + uint64 htlc_index = 5; + + // If this HTLC is involved in a forwarding operation, this field indicates + // the forwarding channel. For an outgoing htlc, it is the incoming channel. + // For an incoming htlc, it is the outgoing channel. When the htlc + // originates from this node or this node is the final destination, + // forwarding_channel will be zero. The forwarding channel will also be zero + // for htlcs that need to be forwarded but don't have a forwarding decision + // persisted yet. + uint64 forwarding_channel = 6; + + // Index identifying the htlc on the forwarding channel. + uint64 forwarding_htlc_index = 7; +} + +enum CommitmentType { + /* + Returned when the commitment type isn't known or unavailable. + */ + UNKNOWN_COMMITMENT_TYPE = 0; + + /* + A channel using the legacy commitment format having tweaked to_remote + keys. + */ + LEGACY = 1; + + /* + A channel that uses the modern commitment format where the key in the + output of the remote party does not change each state. This makes back + up and recovery easier as when the channel is closed, the funds go + directly to that key. + */ + STATIC_REMOTE_KEY = 2; + + /* + A channel that uses a commitment format that has anchor outputs on the + commitments, allowing fee bumping after a force close transaction has + been broadcast. + */ + ANCHORS = 3; + + /* + A channel that uses a commitment type that builds upon the anchors + commitment format, but in addition requires a CLTV clause to spend outputs + paying to the channel initiator. This is intended for use on leased channels + to guarantee that the channel initiator has no incentives to close a leased + channel before its maturity date. + */ + SCRIPT_ENFORCED_LEASE = 4; + + /* + A channel that uses musig2 for the funding output, and the new tapscript + features where relevant. + */ + // TODO(roasbeef): need script enforce mirror type for the above as well? + SIMPLE_TAPROOT = 5; +} + +message ChannelConstraints { + /* + The CSV delay expressed in relative blocks. If the channel is force closed, + we will need to wait for this many blocks before we can regain our funds. + */ + uint32 csv_delay = 1; + + // The minimum satoshis this node is required to reserve in its balance. + uint64 chan_reserve_sat = 2; + + // The dust limit (in satoshis) of the initiator's commitment tx. + uint64 dust_limit_sat = 3; + + // The maximum amount of coins in millisatoshis that can be pending in this + // channel. + uint64 max_pending_amt_msat = 4; + + // The smallest HTLC in millisatoshis that the initiator will accept. + uint64 min_htlc_msat = 5; + + // The total number of incoming HTLC's that the initiator will accept. + uint32 max_accepted_htlcs = 6; +} + +message Channel { + // Whether this channel is active or not + bool active = 1; + + // The identity pubkey of the remote node + string remote_pubkey = 2; + + /* + The outpoint (txid:index) of the funding transaction. With this value, Bob + will be able to generate a signature for Alice's version of the commitment + transaction. + */ + string channel_point = 3; + + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 4 [jstype = JS_STRING]; + + // The total amount of funds held in this channel + int64 capacity = 5; + + // This node's current balance in this channel + int64 local_balance = 6; + + // The counterparty's current balance in this channel + int64 remote_balance = 7; + + /* + The amount calculated to be paid in fees for the current set of commitment + transactions. The fee amount is persisted with the channel in order to + allow the fee amount to be removed and recalculated with each channel state + update, including updates that happen after a system restart. + */ + int64 commit_fee = 8; + + // The weight of the commitment transaction + int64 commit_weight = 9; + + /* + The required number of satoshis per kilo-weight that the requester will pay + at all times, for both the funding transaction and commitment transaction. + This value can later be updated once the channel is open. + */ + int64 fee_per_kw = 10; + + // The unsettled balance in this channel + int64 unsettled_balance = 11; + + /* + The total number of satoshis we've sent within this channel. + */ + int64 total_satoshis_sent = 12; + + /* + The total number of satoshis we've received within this channel. + */ + int64 total_satoshis_received = 13; + + /* + The total number of updates conducted within this channel. + */ + uint64 num_updates = 14; + + /* + The list of active, uncleared HTLCs currently pending within the channel. + */ + repeated HTLC pending_htlcs = 15; + + /* + Deprecated. The CSV delay expressed in relative blocks. If the channel is + force closed, we will need to wait for this many blocks before we can regain + our funds. + */ + uint32 csv_delay = 16 [deprecated = true]; + + // Whether this channel is advertised to the network or not. + bool private = 17; + + // True if we were the ones that created the channel. + bool initiator = 18; + + // A set of flags showing the current state of the channel. + string chan_status_flags = 19; + + // Deprecated. The minimum satoshis this node is required to reserve in its + // balance. + int64 local_chan_reserve_sat = 20 [deprecated = true]; + + /* + Deprecated. The minimum satoshis the other node is required to reserve in + its balance. + */ + int64 remote_chan_reserve_sat = 21 [deprecated = true]; + + // Deprecated. Use commitment_type. + bool static_remote_key = 22 [deprecated = true]; + + // The commitment type used by this channel. + CommitmentType commitment_type = 26; + + /* + The number of seconds that the channel has been monitored by the channel + scoring system. Scores are currently not persisted, so this value may be + less than the lifetime of the channel [EXPERIMENTAL]. + */ + int64 lifetime = 23; + + /* + The number of seconds that the remote peer has been observed as being online + by the channel scoring system over the lifetime of the channel + [EXPERIMENTAL]. + */ + int64 uptime = 24; + + /* + Close address is the address that we will enforce payout to on cooperative + close if the channel was opened utilizing option upfront shutdown. This + value can be set on channel open by setting close_address in an open channel + request. If this value is not set, you can still choose a payout address by + cooperatively closing with the delivery_address field set. + */ + string close_address = 25; + + /* + The amount that the initiator of the channel optionally pushed to the remote + party on channel open. This amount will be zero if the channel initiator did + not push any funds to the remote peer. If the initiator field is true, we + pushed this amount to our peer, if it is false, the remote peer pushed this + amount to us. + */ + uint64 push_amount_sat = 27; + + /* + This uint32 indicates if this channel is to be considered 'frozen'. A + frozen channel doest not allow a cooperative channel close by the + initiator. The thaw_height is the height that this restriction stops + applying to the channel. This field is optional, not setting it or using a + value of zero will mean the channel has no additional restrictions. The + height can be interpreted in two ways: as a relative height if the value is + less than 500,000, or as an absolute height otherwise. + */ + uint32 thaw_height = 28; + + // List constraints for the local node. + ChannelConstraints local_constraints = 29; + + // List constraints for the remote node. + ChannelConstraints remote_constraints = 30; + + /* + This lists out the set of alias short channel ids that exist for a channel. + This may be empty. + */ + repeated uint64 alias_scids = 31; + + // Whether or not this is a zero-conf channel. + bool zero_conf = 32; + + // This is the confirmed / on-chain zero-conf SCID. + uint64 zero_conf_confirmed_scid = 33; + + // The configured alias name of our peer. + string peer_alias = 34; + + // This is the peer SCID alias. + uint64 peer_scid_alias = 35 [jstype = JS_STRING]; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 36; +} + +message ListChannelsRequest { + bool active_only = 1; + bool inactive_only = 2; + bool public_only = 3; + bool private_only = 4; + + /* + Filters the response for channels with a target peer's pubkey. If peer is + empty, all channels will be returned. + */ + bytes peer = 5; + + // Informs the server if the peer alias lookup per channel should be + // enabled. It is turned off by default in order to avoid degradation of + // performance for existing clients. + bool peer_alias_lookup = 6; +} +message ListChannelsResponse { + // The list of active channels + repeated Channel channels = 11; +} + +message AliasMap { + /* + For non-zero-conf channels, this is the confirmed SCID. Otherwise, this is + the first assigned "base" alias. + */ + uint64 base_scid = 1; + + // The set of all aliases stored for the base SCID. + repeated uint64 aliases = 2; +} +message ListAliasesRequest { +} +message ListAliasesResponse { + repeated AliasMap alias_maps = 1; +} + +enum Initiator { + INITIATOR_UNKNOWN = 0; + INITIATOR_LOCAL = 1; + INITIATOR_REMOTE = 2; + INITIATOR_BOTH = 3; +} + +message ChannelCloseSummary { + // The outpoint (txid:index) of the funding transaction. + string channel_point = 1; + + // The unique channel ID for the channel. + uint64 chan_id = 2 [jstype = JS_STRING]; + + // The hash of the genesis block that this channel resides within. + string chain_hash = 3; + + // The txid of the transaction which ultimately closed this channel. + string closing_tx_hash = 4; + + // Public key of the remote peer that we formerly had a channel with. + string remote_pubkey = 5; + + // Total capacity of the channel. + int64 capacity = 6; + + // Height at which the funding transaction was spent. + uint32 close_height = 7; + + // Settled balance at the time of channel closure + int64 settled_balance = 8; + + // The sum of all the time-locked outputs at the time of channel closure + int64 time_locked_balance = 9; + + enum ClosureType { + COOPERATIVE_CLOSE = 0; + LOCAL_FORCE_CLOSE = 1; + REMOTE_FORCE_CLOSE = 2; + BREACH_CLOSE = 3; + FUNDING_CANCELED = 4; + ABANDONED = 5; + } + + // Details on how the channel was closed. + ClosureType close_type = 10; + + /* + Open initiator is the party that initiated opening the channel. Note that + this value may be unknown if the channel was closed before we migrated to + store open channel information after close. + */ + Initiator open_initiator = 11; + + /* + Close initiator indicates which party initiated the close. This value will + be unknown for channels that were cooperatively closed before we started + tracking cooperative close initiators. Note that this indicates which party + initiated a close, and it is possible for both to initiate cooperative or + force closes, although only one party's close will be confirmed on chain. + */ + Initiator close_initiator = 12; + + repeated Resolution resolutions = 13; + + /* + This lists out the set of alias short channel ids that existed for the + closed channel. This may be empty. + */ + repeated uint64 alias_scids = 14; + + // The confirmed SCID for a zero-conf channel. + uint64 zero_conf_confirmed_scid = 15 [jstype = JS_STRING]; +} + +enum ResolutionType { + TYPE_UNKNOWN = 0; + + // We resolved an anchor output. + ANCHOR = 1; + + /* + We are resolving an incoming htlc on chain. This if this htlc is + claimed, we swept the incoming htlc with the preimage. If it is timed + out, our peer swept the timeout path. + */ + INCOMING_HTLC = 2; + + /* + We are resolving an outgoing htlc on chain. If this htlc is claimed, + the remote party swept the htlc with the preimage. If it is timed out, + we swept it with the timeout path. + */ + OUTGOING_HTLC = 3; + + // We force closed and need to sweep our time locked commitment output. + COMMIT = 4; +} + +enum ResolutionOutcome { + // Outcome unknown. + OUTCOME_UNKNOWN = 0; + + // An output was claimed on chain. + CLAIMED = 1; + + // An output was left unclaimed on chain. + UNCLAIMED = 2; + + /* + ResolverOutcomeAbandoned indicates that an output that we did not + claim on chain, for example an anchor that we did not sweep and a + third party claimed on chain, or a htlc that we could not decode + so left unclaimed. + */ + ABANDONED = 3; + + /* + If we force closed our channel, our htlcs need to be claimed in two + stages. This outcome represents the broadcast of a timeout or success + transaction for this two stage htlc claim. + */ + FIRST_STAGE = 4; + + // A htlc was timed out on chain. + TIMEOUT = 5; +} + +message Resolution { + // The type of output we are resolving. + ResolutionType resolution_type = 1; + + // The outcome of our on chain action that resolved the outpoint. + ResolutionOutcome outcome = 2; + + // The outpoint that was spent by the resolution. + OutPoint outpoint = 3; + + // The amount that was claimed by the resolution. + uint64 amount_sat = 4; + + // The hex-encoded transaction ID of the sweep transaction that spent the + // output. + string sweep_txid = 5; +} + +message ClosedChannelsRequest { + bool cooperative = 1; + bool local_force = 2; + bool remote_force = 3; + bool breach = 4; + bool funding_canceled = 5; + bool abandoned = 6; +} + +message ClosedChannelsResponse { + repeated ChannelCloseSummary channels = 1; +} + +message Peer { + // The identity pubkey of the peer + string pub_key = 1; + + // Network address of the peer; eg `127.0.0.1:10011` + string address = 3; + + // Bytes of data transmitted to this peer + uint64 bytes_sent = 4; + + // Bytes of data transmitted from this peer + uint64 bytes_recv = 5; + + // Satoshis sent to this peer + int64 sat_sent = 6; + + // Satoshis received from this peer + int64 sat_recv = 7; + + // A channel is inbound if the counterparty initiated the channel + bool inbound = 8; + + // Ping time to this peer + int64 ping_time = 9; + + enum SyncType { + /* + Denotes that we cannot determine the peer's current sync type. + */ + UNKNOWN_SYNC = 0; + + /* + Denotes that we are actively receiving new graph updates from the peer. + */ + ACTIVE_SYNC = 1; + + /* + Denotes that we are not receiving new graph updates from the peer. + */ + PASSIVE_SYNC = 2; + + /* + Denotes that this peer is pinned into an active sync. + */ + PINNED_SYNC = 3; + } + + // The type of sync we are currently performing with this peer. + SyncType sync_type = 10; + + // Features advertised by the remote peer in their init message. + map features = 11; + + /* + The latest errors received from our peer with timestamps, limited to the 10 + most recent errors. These errors are tracked across peer connections, but + are not persisted across lnd restarts. Note that these errors are only + stored for peers that we have channels open with, to prevent peers from + spamming us with errors at no cost. + */ + repeated TimestampedError errors = 12; + + /* + The number of times we have recorded this peer going offline or coming + online, recorded across restarts. Note that this value is decreased over + time if the peer has not recently flapped, so that we can forgive peers + with historically high flap counts. + */ + int32 flap_count = 13; + + /* + The timestamp of the last flap we observed for this peer. If this value is + zero, we have not observed any flaps for this peer. + */ + int64 last_flap_ns = 14; + + /* + The last ping payload the peer has sent to us. + */ + bytes last_ping_payload = 15; +} + +message TimestampedError { + // The unix timestamp in seconds when the error occurred. + uint64 timestamp = 1; + + // The string representation of the error sent by our peer. + string error = 2; +} + +message ListPeersRequest { + /* + If true, only the last error that our peer sent us will be returned with + the peer's information, rather than the full set of historic errors we have + stored. + */ + bool latest_error = 1; +} +message ListPeersResponse { + // The list of currently connected peers + repeated Peer peers = 1; +} + +message PeerEventSubscription { +} + +message PeerEvent { + // The identity pubkey of the peer. + string pub_key = 1; + + enum EventType { + PEER_ONLINE = 0; + PEER_OFFLINE = 1; + } + + EventType type = 2; +} + +message GetInfoRequest { +} +message GetInfoResponse { + // The version of the LND software that the node is running. + string version = 14; + + // The SHA1 commit hash that the daemon is compiled with. + string commit_hash = 20; + + // The identity pubkey of the current node. + string identity_pubkey = 1; + + // If applicable, the alias of the current node, e.g. "bob" + string alias = 2; + + // The color of the current node in hex code format + string color = 17; + + // Number of pending channels + uint32 num_pending_channels = 3; + + // Number of active channels + uint32 num_active_channels = 4; + + // Number of inactive channels + uint32 num_inactive_channels = 15; + + // Number of peers + uint32 num_peers = 5; + + // The node's current view of the height of the best block + uint32 block_height = 6; + + // The node's current view of the hash of the best block + string block_hash = 8; + + // Timestamp of the block best known to the wallet + int64 best_header_timestamp = 13; + + // Whether the wallet's view is synced to the main chain + bool synced_to_chain = 9; + + // Whether we consider ourselves synced with the public channel graph. + bool synced_to_graph = 18; + + /* + Whether the current node is connected to testnet. This field is + deprecated and the network field should be used instead + */ + bool testnet = 10 [deprecated = true]; + + reserved 11; + + /* + Deprecated. The only active chain is bitcoin. + A list of active chains the node is connected to + */ + repeated Chain chains = 16 [deprecated = true]; + + // The URIs of the current node. + repeated string uris = 12; + + /* + Features that our node has advertised in our init message, node + announcements and invoices. + */ + map features = 19; + + /* + Indicates whether the HTLC interceptor API is in always-on mode. + */ + bool require_htlc_interceptor = 21; + + // Indicates whether final htlc resolutions are stored on disk. + bool store_final_htlc_resolutions = 22; +} + +message GetRecoveryInfoRequest { +} +message GetRecoveryInfoResponse { + // Whether the wallet is in recovery mode + bool recovery_mode = 1; + + // Whether the wallet recovery progress is finished + bool recovery_finished = 2; + + // The recovery progress, ranging from 0 to 1. + double progress = 3; +} + +message Chain { + // Deprecated. The chain is now always assumed to be bitcoin. + // The blockchain the node is on (must be bitcoin) + string chain = 1 [deprecated = true]; + + // The network the node is on (eg regtest, testnet, mainnet) + string network = 2; +} + +message ConfirmationUpdate { + bytes block_sha = 1; + int32 block_height = 2; + + uint32 num_confs_left = 3; +} + +message ChannelOpenUpdate { + ChannelPoint channel_point = 1; +} + +message ChannelCloseUpdate { + bytes closing_txid = 1; + + bool success = 2; +} + +message CloseChannelRequest { + /* + The outpoint (txid:index) of the funding transaction. With this value, Bob + will be able to generate a signature for Alice's version of the commitment + transaction. + */ + ChannelPoint channel_point = 1; + + // If true, then the channel will be closed forcibly. This means the + // current commitment transaction will be signed and broadcast. + bool force = 2; + + // The target number of blocks that the closure transaction should be + // confirmed by. + int32 target_conf = 3; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + int64 sat_per_byte = 4 [deprecated = true]; + + /* + An optional address to send funds to in the case of a cooperative close. + If the channel was opened with an upfront shutdown script and this field + is set, the request to close will fail because the channel must pay out + to the upfront shutdown addresss. + */ + string delivery_address = 5; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + uint64 sat_per_vbyte = 6; + + // The maximum fee rate the closer is willing to pay. + // + // NOTE: This field is only respected if we're the initiator of the channel. + uint64 max_fee_per_vbyte = 7; +} + +message CloseStatusUpdate { + oneof update { + PendingUpdate close_pending = 1; + ChannelCloseUpdate chan_close = 3; + } +} + +message PendingUpdate { + bytes txid = 1; + uint32 output_index = 2; +} + +message ReadyForPsbtFunding { + /* + The P2WSH address of the channel funding multisig address that the below + specified amount in satoshis needs to be sent to. + */ + string funding_address = 1; + + /* + The exact amount in satoshis that needs to be sent to the above address to + fund the pending channel. + */ + int64 funding_amount = 2; + + /* + A raw PSBT that contains the pending channel output. If a base PSBT was + provided in the PsbtShim, this is the base PSBT with one additional output. + If no base PSBT was specified, this is an otherwise empty PSBT with exactly + one output. + */ + bytes psbt = 3; +} + +message BatchOpenChannelRequest { + // The list of channels to open. + repeated BatchOpenChannel channels = 1; + + // The target number of blocks that the funding transaction should be + // confirmed by. + int32 target_conf = 2; + + // A manual fee rate set in sat/vByte that should be used when crafting the + // funding transaction. + int64 sat_per_vbyte = 3; + + // The minimum number of confirmations each one of your outputs used for + // the funding transaction must satisfy. + int32 min_confs = 4; + + // Whether unconfirmed outputs should be used as inputs for the funding + // transaction. + bool spend_unconfirmed = 5; + + // An optional label for the batch transaction, limited to 500 characters. + string label = 6; +} + +message BatchOpenChannel { + // The pubkey of the node to open a channel with. When using REST, this + // field must be encoded as base64. + bytes node_pubkey = 1; + + // The number of satoshis the wallet should commit to the channel. + int64 local_funding_amount = 2; + + // The number of satoshis to push to the remote side as part of the initial + // commitment state. + int64 push_sat = 3; + + // Whether this channel should be private, not announced to the greater + // network. + bool private = 4; + + // The minimum value in millisatoshi we will require for incoming HTLCs on + // the channel. + int64 min_htlc_msat = 5; + + // The delay we require on the remote's commitment transaction. If this is + // not set, it will be scaled automatically with the channel size. + uint32 remote_csv_delay = 6; + + /* + Close address is an optional address which specifies the address to which + funds should be paid out to upon cooperative close. This field may only be + set if the peer supports the option upfront feature bit (call listpeers + to check). The remote peer will only accept cooperative closes to this + address if it is set. + + Note: If this value is set on channel creation, you will *not* be able to + cooperatively close out to a different address. + */ + string close_address = 7; + + /* + An optional, unique identifier of 32 random bytes that will be used as the + pending channel ID to identify the channel while it is in the pre-pending + state. + */ + bytes pending_chan_id = 8; + + /* + The explicit commitment type to use. Note this field will only be used if + the remote peer supports explicit channel negotiation. + */ + CommitmentType commitment_type = 9; + + /* + The maximum amount of coins in millisatoshi that can be pending within + the channel. It only applies to the remote party. + */ + uint64 remote_max_value_in_flight_msat = 10; + + /* + The maximum number of concurrent HTLCs we will allow the remote party to add + to the commitment transaction. + */ + uint32 remote_max_htlcs = 11; + + /* + Max local csv is the maximum csv delay we will allow for our own commitment + transaction. + */ + uint32 max_local_csv = 12; + + /* + If this is true, then a zero-conf channel open will be attempted. + */ + bool zero_conf = 13; + + /* + If this is true, then an option-scid-alias channel-type open will be + attempted. + */ + bool scid_alias = 14; + + /* + The base fee charged regardless of the number of milli-satoshis sent. + */ + uint64 base_fee = 15; + + /* + The fee rate in ppm (parts per million) that will be charged in + proportion of the value of each forwarded HTLC. + */ + uint64 fee_rate = 16; + + /* + If use_base_fee is true the open channel announcement will update the + channel base fee with the value specified in base_fee. In the case of + a base_fee of 0 use_base_fee is needed downstream to distinguish whether + to use the default base fee value specified in the config or 0. + */ + bool use_base_fee = 17; + + /* + If use_fee_rate is true the open channel announcement will update the + channel fee rate with the value specified in fee_rate. In the case of + a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether + to use the default fee rate value specified in the config or 0. + */ + bool use_fee_rate = 18; + + /* + The number of satoshis we require the remote peer to reserve. This value, + if specified, must be above the dust limit and below 20% of the channel + capacity. + */ + uint64 remote_chan_reserve_sat = 19; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 20; +} + +message BatchOpenChannelResponse { + repeated PendingUpdate pending_channels = 1; +} + +message OpenChannelRequest { + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + uint64 sat_per_vbyte = 1; + + /* + The pubkey of the node to open a channel with. When using REST, this field + must be encoded as base64. + */ + bytes node_pubkey = 2; + + /* + The hex encoded pubkey of the node to open a channel with. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string node_pubkey_string = 3 [deprecated = true]; + + // The number of satoshis the wallet should commit to the channel + int64 local_funding_amount = 4; + + // The number of satoshis to push to the remote side as part of the initial + // commitment state + int64 push_sat = 5; + + // The target number of blocks that the funding transaction should be + // confirmed by. + int32 target_conf = 6; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + int64 sat_per_byte = 7 [deprecated = true]; + + // Whether this channel should be private, not announced to the greater + // network. + bool private = 8; + + // The minimum value in millisatoshi we will require for incoming HTLCs on + // the channel. + int64 min_htlc_msat = 9; + + // The delay we require on the remote's commitment transaction. If this is + // not set, it will be scaled automatically with the channel size. + uint32 remote_csv_delay = 10; + + // The minimum number of confirmations each one of your outputs used for + // the funding transaction must satisfy. + int32 min_confs = 11; + + // Whether unconfirmed outputs should be used as inputs for the funding + // transaction. + bool spend_unconfirmed = 12; + + /* + Close address is an optional address which specifies the address to which + funds should be paid out to upon cooperative close. This field may only be + set if the peer supports the option upfront feature bit (call listpeers + to check). The remote peer will only accept cooperative closes to this + address if it is set. + + Note: If this value is set on channel creation, you will *not* be able to + cooperatively close out to a different address. + */ + string close_address = 13; + + /* + Funding shims are an optional argument that allow the caller to intercept + certain funding functionality. For example, a shim can be provided to use a + particular key for the commitment key (ideally cold) rather than use one + that is generated by the wallet as normal, or signal that signing will be + carried out in an interactive manner (PSBT based). + */ + FundingShim funding_shim = 14; + + /* + The maximum amount of coins in millisatoshi that can be pending within + the channel. It only applies to the remote party. + */ + uint64 remote_max_value_in_flight_msat = 15; + + /* + The maximum number of concurrent HTLCs we will allow the remote party to add + to the commitment transaction. + */ + uint32 remote_max_htlcs = 16; + + /* + Max local csv is the maximum csv delay we will allow for our own commitment + transaction. + */ + uint32 max_local_csv = 17; + + /* + The explicit commitment type to use. Note this field will only be used if + the remote peer supports explicit channel negotiation. + */ + CommitmentType commitment_type = 18; + + /* + If this is true, then a zero-conf channel open will be attempted. + */ + bool zero_conf = 19; + + /* + If this is true, then an option-scid-alias channel-type open will be + attempted. + */ + bool scid_alias = 20; + + /* + The base fee charged regardless of the number of milli-satoshis sent. + */ + uint64 base_fee = 21; + + /* + The fee rate in ppm (parts per million) that will be charged in + proportion of the value of each forwarded HTLC. + */ + uint64 fee_rate = 22; + + /* + If use_base_fee is true the open channel announcement will update the + channel base fee with the value specified in base_fee. In the case of + a base_fee of 0 use_base_fee is needed downstream to distinguish whether + to use the default base fee value specified in the config or 0. + */ + bool use_base_fee = 23; + + /* + If use_fee_rate is true the open channel announcement will update the + channel fee rate with the value specified in fee_rate. In the case of + a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether + to use the default fee rate value specified in the config or 0. + */ + bool use_fee_rate = 24; + + /* + The number of satoshis we require the remote peer to reserve. This value, + if specified, must be above the dust limit and below 20% of the channel + capacity. + */ + uint64 remote_chan_reserve_sat = 25; + + /* + If set, then lnd will attempt to commit all the coins under control of the + internal wallet to open the channel, and the LocalFundingAmount field must + be zero and is ignored. + */ + bool fund_max = 26; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 27; + + /* + A list of selected outpoints that are allocated for channel funding. + */ + repeated OutPoint outpoints = 28; +} +message OpenStatusUpdate { + oneof update { + /* + Signals that the channel is now fully negotiated and the funding + transaction published. + */ + PendingUpdate chan_pending = 1; + + /* + Signals that the channel's funding transaction has now reached the + required number of confirmations on chain and can be used. + */ + ChannelOpenUpdate chan_open = 3; + + /* + Signals that the funding process has been suspended and the construction + of a PSBT that funds the channel PK script is now required. + */ + ReadyForPsbtFunding psbt_fund = 5; + } + + /* + The pending channel ID of the created channel. This value may be used to + further the funding flow manually via the FundingStateStep method. + */ + bytes pending_chan_id = 4; +} + +message KeyLocator { + // The family of key being identified. + int32 key_family = 1; + + // The precise index of the key being identified. + int32 key_index = 2; +} + +message KeyDescriptor { + /* + The raw bytes of the key being identified. + */ + bytes raw_key_bytes = 1; + + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 2; +} + +message ChanPointShim { + /* + The size of the pre-crafted output to be used as the channel point for this + channel funding. + */ + int64 amt = 1; + + // The target channel point to refrence in created commitment transactions. + ChannelPoint chan_point = 2; + + // Our local key to use when creating the multi-sig output. + KeyDescriptor local_key = 3; + + // The key of the remote party to use when creating the multi-sig output. + bytes remote_key = 4; + + /* + If non-zero, then this will be used as the pending channel ID on the wire + protocol to initate the funding request. This is an optional field, and + should only be set if the responder is already expecting a specific pending + channel ID. + */ + bytes pending_chan_id = 5; + + /* + This uint32 indicates if this channel is to be considered 'frozen'. A frozen + channel does not allow a cooperative channel close by the initiator. The + thaw_height is the height that this restriction stops applying to the + channel. The height can be interpreted in two ways: as a relative height if + the value is less than 500,000, or as an absolute height otherwise. + */ + uint32 thaw_height = 6; + + /* + Indicates that the funding output is using a MuSig2 multi-sig output. + */ + bool musig2 = 7; +} + +message PsbtShim { + /* + A unique identifier of 32 random bytes that will be used as the pending + channel ID to identify the PSBT state machine when interacting with it and + on the wire protocol to initiate the funding request. + */ + bytes pending_chan_id = 1; + + /* + An optional base PSBT the new channel output will be added to. If this is + non-empty, it must be a binary serialized PSBT. + */ + bytes base_psbt = 2; + + /* + If a channel should be part of a batch (multiple channel openings in one + transaction), it can be dangerous if the whole batch transaction is + published too early before all channel opening negotiations are completed. + This flag prevents this particular channel from broadcasting the transaction + after the negotiation with the remote peer. In a batch of channel openings + this flag should be set to true for every channel but the very last. + */ + bool no_publish = 3; +} + +message FundingShim { + oneof shim { + /* + A channel shim where the channel point was fully constructed outside + of lnd's wallet and the transaction might already be published. + */ + ChanPointShim chan_point_shim = 1; + + /* + A channel shim that uses a PSBT to fund and sign the channel funding + transaction. + */ + PsbtShim psbt_shim = 2; + } +} + +message FundingShimCancel { + // The pending channel ID of the channel to cancel the funding shim for. + bytes pending_chan_id = 1; +} + +message FundingPsbtVerify { + /* + The funded but not yet signed PSBT that sends the exact channel capacity + amount to the PK script returned in the open channel message in a previous + step. + */ + bytes funded_psbt = 1; + + // The pending channel ID of the channel to get the PSBT for. + bytes pending_chan_id = 2; + + /* + Can only be used if the no_publish flag was set to true in the OpenChannel + call meaning that the caller is solely responsible for publishing the final + funding transaction. If skip_finalize is set to true then lnd will not wait + for a FundingPsbtFinalize state step and instead assumes that a transaction + with the same TXID as the passed in PSBT will eventually confirm. + IT IS ABSOLUTELY IMPERATIVE that the TXID of the transaction that is + eventually published does have the _same TXID_ as the verified PSBT. That + means no inputs or outputs can change, only signatures can be added. If the + TXID changes between this call and the publish step then the channel will + never be created and the funds will be in limbo. + */ + bool skip_finalize = 3; +} + +message FundingPsbtFinalize { + /* + The funded PSBT that contains all witness data to send the exact channel + capacity amount to the PK script returned in the open channel message in a + previous step. Cannot be set at the same time as final_raw_tx. + */ + bytes signed_psbt = 1; + + // The pending channel ID of the channel to get the PSBT for. + bytes pending_chan_id = 2; + + /* + As an alternative to the signed PSBT with all witness data, the final raw + wire format transaction can also be specified directly. Cannot be set at the + same time as signed_psbt. + */ + bytes final_raw_tx = 3; +} + +message FundingTransitionMsg { + oneof trigger { + /* + The funding shim to register. This should be used before any + channel funding has began by the remote party, as it is intended as a + preparatory step for the full channel funding. + */ + FundingShim shim_register = 1; + + // Used to cancel an existing registered funding shim. + FundingShimCancel shim_cancel = 2; + + /* + Used to continue a funding flow that was initiated to be executed + through a PSBT. This step verifies that the PSBT contains the correct + outputs to fund the channel. + */ + FundingPsbtVerify psbt_verify = 3; + + /* + Used to continue a funding flow that was initiated to be executed + through a PSBT. This step finalizes the funded and signed PSBT, finishes + negotiation with the peer and finally publishes the resulting funding + transaction. + */ + FundingPsbtFinalize psbt_finalize = 4; + } +} + +message FundingStateStepResp { +} + +message PendingHTLC { + // The direction within the channel that the htlc was sent + bool incoming = 1; + + // The total value of the htlc + int64 amount = 2; + + // The final output to be swept back to the user's wallet + string outpoint = 3; + + // The next block height at which we can spend the current stage + uint32 maturity_height = 4; + + /* + The number of blocks remaining until the current stage can be swept. + Negative values indicate how many blocks have passed since becoming + mature. + */ + int32 blocks_til_maturity = 5; + + // Indicates whether the htlc is in its first or second stage of recovery + uint32 stage = 6; +} + +message PendingChannelsRequest { +} +message PendingChannelsResponse { + message PendingChannel { + string remote_node_pub = 1; + string channel_point = 2; + + int64 capacity = 3; + + int64 local_balance = 4; + int64 remote_balance = 5; + + // The minimum satoshis this node is required to reserve in its + // balance. + int64 local_chan_reserve_sat = 6; + + /* + The minimum satoshis the other node is required to reserve in its + balance. + */ + int64 remote_chan_reserve_sat = 7; + + // The party that initiated opening the channel. + Initiator initiator = 8; + + // The commitment type used by this channel. + CommitmentType commitment_type = 9; + + // Total number of forwarding packages created in this channel. + int64 num_forwarding_packages = 10; + + // A set of flags showing the current state of the channel. + string chan_status_flags = 11; + + // Whether this channel is advertised to the network or not. + bool private = 12; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way + impacts the channel's operation. + */ + string memo = 13; + } + + message PendingOpenChannel { + // The pending channel + PendingChannel channel = 1; + + /* + The amount calculated to be paid in fees for the current set of + commitment transactions. The fee amount is persisted with the channel + in order to allow the fee amount to be removed and recalculated with + each channel state update, including updates that happen after a system + restart. + */ + int64 commit_fee = 4; + + // The weight of the commitment transaction + int64 commit_weight = 5; + + /* + The required number of satoshis per kilo-weight that the requester will + pay at all times, for both the funding transaction and commitment + transaction. This value can later be updated once the channel is open. + */ + int64 fee_per_kw = 6; + + // Previously used for confirmation_height. Do not reuse. + reserved 2; + + // The number of blocks until the funding transaction is considered + // expired. If this value gets close to zero, there is a risk that the + // channel funding will be canceled by the channel responder. The + // channel should be fee bumped using CPFP (see walletrpc.BumpFee) to + // ensure that the channel confirms in time. Otherwise a force-close + // will be necessary if the channel confirms after the funding + // transaction expires. A negative value means the channel responder has + // very likely canceled the funding and the channel will never become + // fully operational. + int32 funding_expiry_blocks = 3; + } + + message WaitingCloseChannel { + // The pending channel waiting for closing tx to confirm + PendingChannel channel = 1; + + // The balance in satoshis encumbered in this channel + int64 limbo_balance = 2; + + /* + A list of valid commitment transactions. Any of these can confirm at + this point. + */ + Commitments commitments = 3; + + // The transaction id of the closing transaction + string closing_txid = 4; + } + + message Commitments { + // Hash of the local version of the commitment tx. + string local_txid = 1; + + // Hash of the remote version of the commitment tx. + string remote_txid = 2; + + // Hash of the remote pending version of the commitment tx. + string remote_pending_txid = 3; + + /* + The amount in satoshis calculated to be paid in fees for the local + commitment. + */ + uint64 local_commit_fee_sat = 4; + + /* + The amount in satoshis calculated to be paid in fees for the remote + commitment. + */ + uint64 remote_commit_fee_sat = 5; + + /* + The amount in satoshis calculated to be paid in fees for the remote + pending commitment. + */ + uint64 remote_pending_commit_fee_sat = 6; + } + + message ClosedChannel { + // The pending channel to be closed + PendingChannel channel = 1; + + // The transaction id of the closing transaction + string closing_txid = 2; + } + + message ForceClosedChannel { + // The pending channel to be force closed + PendingChannel channel = 1; + + // The transaction id of the closing transaction + string closing_txid = 2; + + // The balance in satoshis encumbered in this pending channel + int64 limbo_balance = 3; + + // The height at which funds can be swept into the wallet + uint32 maturity_height = 4; + + /* + Remaining # of blocks until the commitment output can be swept. + Negative values indicate how many blocks have passed since becoming + mature. + */ + int32 blocks_til_maturity = 5; + + // The total value of funds successfully recovered from this channel + int64 recovered_balance = 6; + + repeated PendingHTLC pending_htlcs = 8; + + /* + There are three resolution states for the anchor: + limbo, lost and recovered. Derive the current state + from the limbo and recovered balances. + */ + enum AnchorState { + // The recovered_balance is zero and limbo_balance is non-zero. + LIMBO = 0; + // The recovered_balance is non-zero. + RECOVERED = 1; + // A state that is neither LIMBO nor RECOVERED. + LOST = 2; + } + + AnchorState anchor = 9; + } + + // The balance in satoshis encumbered in pending channels + int64 total_limbo_balance = 1; + + // Channels pending opening + repeated PendingOpenChannel pending_open_channels = 2; + + /* + Deprecated: Channels pending closing previously contained cooperatively + closed channels with a single confirmation. These channels are now + considered closed from the time we see them on chain. + */ + repeated ClosedChannel pending_closing_channels = 3 [deprecated = true]; + + // Channels pending force closing + repeated ForceClosedChannel pending_force_closing_channels = 4; + + // Channels waiting for closing tx to confirm + repeated WaitingCloseChannel waiting_close_channels = 5; +} + +message ChannelEventSubscription { +} + +message ChannelEventUpdate { + oneof channel { + Channel open_channel = 1; + ChannelCloseSummary closed_channel = 2; + ChannelPoint active_channel = 3; + ChannelPoint inactive_channel = 4; + PendingUpdate pending_open_channel = 6; + ChannelPoint fully_resolved_channel = 7; + } + + enum UpdateType { + OPEN_CHANNEL = 0; + CLOSED_CHANNEL = 1; + ACTIVE_CHANNEL = 2; + INACTIVE_CHANNEL = 3; + PENDING_OPEN_CHANNEL = 4; + FULLY_RESOLVED_CHANNEL = 5; + } + + UpdateType type = 5; +} + +message WalletAccountBalance { + // The confirmed balance of the account (with >= 1 confirmations). + int64 confirmed_balance = 1; + + // The unconfirmed balance of the account (with 0 confirmations). + int64 unconfirmed_balance = 2; +} + +message WalletBalanceRequest { + // The wallet account the balance is shown for. + // If this is not specified, the balance of the "default" account is shown. + string account = 1; +} + +message WalletBalanceResponse { + // The balance of the wallet + int64 total_balance = 1; + + // The confirmed balance of a wallet(with >= 1 confirmations) + int64 confirmed_balance = 2; + + // The unconfirmed balance of a wallet(with 0 confirmations) + int64 unconfirmed_balance = 3; + + // The total amount of wallet UTXOs held in outputs that are locked for + // other usage. + int64 locked_balance = 5; + + // The amount of reserve required. + int64 reserved_balance_anchor_chan = 6; + + // A mapping of each wallet account's name to its balance. + map account_balance = 4; +} + +message Amount { + // Value denominated in satoshis. + uint64 sat = 1; + + // Value denominated in milli-satoshis. + uint64 msat = 2; +} + +message ChannelBalanceRequest { +} +message ChannelBalanceResponse { + // Deprecated. Sum of channels balances denominated in satoshis + int64 balance = 1 [deprecated = true]; + + // Deprecated. Sum of channels pending balances denominated in satoshis + int64 pending_open_balance = 2 [deprecated = true]; + + // Sum of channels local balances. + Amount local_balance = 3; + + // Sum of channels remote balances. + Amount remote_balance = 4; + + // Sum of channels local unsettled balances. + Amount unsettled_local_balance = 5; + + // Sum of channels remote unsettled balances. + Amount unsettled_remote_balance = 6; + + // Sum of channels pending local balances. + Amount pending_open_local_balance = 7; + + // Sum of channels pending remote balances. + Amount pending_open_remote_balance = 8; +} + +message QueryRoutesRequest { + // The 33-byte hex-encoded public key for the payment destination + string pub_key = 1; + + /* + The amount to send expressed in satoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 2; + + /* + The amount to send expressed in millisatoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + reserved 3; + + /* + An optional CLTV delta from the current height that should be used for the + timelock of the final hop. Note that unlike SendPayment, QueryRoutes does + not add any additional block padding on top of final_ctlv_delta. This + padding of a few blocks needs to be added manually or otherwise failures may + happen when a block comes in while the payment is in flight. + + Note: must not be set if making a payment to a blinded path (delta is + set by the aggregate parameters provided by blinded_payment_paths) + */ + int32 final_cltv_delta = 4; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + This value can be represented either as a percentage of the amount being + sent, or as a fixed amount of the maximum fee the user is willing the pay to + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. + */ + FeeLimit fee_limit = 5; + + /* + A list of nodes to ignore during path finding. When using REST, these fields + must be encoded as base64. + */ + repeated bytes ignored_nodes = 6; + + /* + Deprecated. A list of edges to ignore during path finding. + */ + repeated EdgeLocator ignored_edges = 7 [deprecated = true]; + + /* + The source node where the request route should originated from. If empty, + self is assumed. + */ + string source_pub_key = 8; + + /* + If set to true, edge probabilities from mission control will be used to get + the optimal route. + */ + bool use_mission_control = 9; + + /* + A list of directed node pairs that will be ignored during path finding. + */ + repeated NodePair ignored_pairs = 10; + + /* + An optional maximum total time lock for the route. If the source is empty or + ourselves, this should not exceed lnd's `--max-cltv-expiry` setting. If + zero, then the value of `--max-cltv-expiry` is used as the limit. + */ + uint32 cltv_limit = 11; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. If the destination + does not support the specified records, an error will be returned. + Record types are required to be in the custom range >= 65536. When using + REST, the values must be encoded as base64. + */ + map dest_custom_records = 13; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 14 [jstype = JS_STRING]; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 15; + + /* + Optional route hints to reach the destination through private channels. + */ + repeated lnrpc.RouteHint route_hints = 16; + + /* + An optional blinded path(s) to reach the destination. Note that the + introduction node must be provided as the first hop in the route. + */ + repeated BlindedPaymentPath blinded_payment_paths = 19; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + + Note: must not be set if making a payment to a blinded route (features + are provided in blinded_payment_paths). + */ + repeated lnrpc.FeatureBit dest_features = 17; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 18; +} + +message NodePair { + /* + The sending node of the pair. When using REST, this field must be encoded as + base64. + */ + bytes from = 1; + + /* + The receiving node of the pair. When using REST, this field must be encoded + as base64. + */ + bytes to = 2; +} + +message EdgeLocator { + // The short channel id of this edge. + uint64 channel_id = 1 [jstype = JS_STRING]; + + /* + The direction of this edge. If direction_reverse is false, the direction + of this edge is from the channel endpoint with the lexicographically smaller + pub key to the endpoint with the larger pub key. If direction_reverse is + is true, the edge goes the other way. + */ + bool direction_reverse = 2; +} + +message QueryRoutesResponse { + /* + The route that results from the path finding operation. This is still a + repeated field to retain backwards compatibility. + */ + repeated Route routes = 1; + + /* + The success probability of the returned route based on the current mission + control state. [EXPERIMENTAL] + */ + double success_prob = 2; +} + +message Hop { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + int64 chan_capacity = 2 [deprecated = true]; + int64 amt_to_forward = 3 [deprecated = true]; + int64 fee = 4 [deprecated = true]; + uint32 expiry = 5; + int64 amt_to_forward_msat = 6; + int64 fee_msat = 7; + + /* + An optional public key of the hop. If the public key is given, the payment + can be executed without relying on a copy of the channel graph. + */ + string pub_key = 8; + + /* + If set to true, then this hop will be encoded using the new variable length + TLV format. Note that if any custom tlv_records below are specified, then + this field MUST be set to true for them to be encoded properly. + */ + bool tlv_payload = 9 [deprecated = true]; + + /* + An optional TLV record that signals the use of an MPP payment. If present, + the receiver will enforce that the same mpp_record is included in the final + hop payload of all non-zero payments in the HTLC set. If empty, a regular + single-shot payment is or was attempted. + */ + MPPRecord mpp_record = 10; + + /* + An optional TLV record that signals the use of an AMP payment. If present, + the receiver will treat all received payments including the same + (payment_addr, set_id) pair as being part of one logical payment. The + payment will be settled by XORing the root_share's together and deriving the + child hashes and preimages according to BOLT XX. Must be used in conjunction + with mpp_record. + */ + AMPRecord amp_record = 12; + + /* + An optional set of key-value TLV records. This is useful within the context + of the SendToRoute call as it allows callers to specify arbitrary K-V pairs + to drop off at each hop within the onion. + */ + map custom_records = 11; + + // The payment metadata to send along with the payment to the payee. + bytes metadata = 13; + + /* + Blinding point is an optional blinding point included for introduction + nodes in blinded paths. This field is mandatory for hops that represents + the introduction point in a blinded path. + */ + bytes blinding_point = 14; + + /* + Encrypted data is a receiver-produced blob of data that provides hops + in a blinded route with forwarding data. As this data is encrypted by + the recipient, we will not be able to parse it - it is essentially an + arbitrary blob of data from our node's perspective. This field is + mandatory for all hops in a blinded path, including the introduction + node. + */ + bytes encrypted_data = 15; + + /* + The total amount that is sent to the recipient (possibly across multiple + HTLCs), as specified by the sender when making a payment to a blinded path. + This value is only set in the final hop payload of a blinded payment. This + value is analogous to the MPPRecord that is used for regular (non-blinded) + MPP payments. + */ + uint64 total_amt_msat = 16; +} + +message MPPRecord { + /* + A unique, random identifier used to authenticate the sender as the intended + payer of a multi-path payment. The payment_addr must be the same for all + subpayments, and match the payment_addr provided in the receiver's invoice. + The same payment_addr must be used on all subpayments. + */ + bytes payment_addr = 11; + + /* + The total amount in milli-satoshis being sent as part of a larger multi-path + payment. The caller is responsible for ensuring subpayments to the same node + and payment_hash sum exactly to total_amt_msat. The same + total_amt_msat must be used on all subpayments. + */ + int64 total_amt_msat = 10; +} + +message AMPRecord { + bytes root_share = 1; + + bytes set_id = 2; + + uint32 child_index = 3; +} + +/* +A path through the channel graph which runs over one or more channels in +succession. This struct carries all the information required to craft the +Sphinx onion packet, and send the payment along the first hop in the path. A +route is only selected as valid if all the channels have sufficient capacity to +carry the initial payment amount after fees are accounted for. +*/ +message Route { + /* + The cumulative (final) time lock across the entire route. This is the CLTV + value that should be extended to the first hop in the route. All other hops + will decrement the time-lock as advertised, leaving enough time for all + hops to wait for or present the payment preimage to complete the payment. + */ + uint32 total_time_lock = 1; + + /* + The sum of the fees paid at each hop within the final route. In the case + of a one-hop payment, this value will be zero as we don't need to pay a fee + to ourselves. + */ + int64 total_fees = 2 [deprecated = true]; + + /* + The total amount of funds required to complete a payment over this route. + This value includes the cumulative fees at each hop. As a result, the HTLC + extended to the first-hop in the route will need to have at least this many + satoshis, otherwise the route will fail at an intermediate node due to an + insufficient amount of fees. + */ + int64 total_amt = 3 [deprecated = true]; + + /* + Contains details concerning the specific forwarding details at each hop. + */ + repeated Hop hops = 4; + + /* + The total fees in millisatoshis. + */ + int64 total_fees_msat = 5; + + /* + The total amount in millisatoshis. + */ + int64 total_amt_msat = 6; +} + +message NodeInfoRequest { + // The 33-byte hex-encoded compressed public of the target node + string pub_key = 1; + + // If true, will include all known channels associated with the node. + bool include_channels = 2; +} + +message NodeInfo { + /* + An individual vertex/node within the channel graph. A node is + connected to other nodes by one or more channel edges emanating from it. As + the graph is directed, a node will also have an incoming edge attached to + it for each outgoing edge. + */ + LightningNode node = 1; + + // The total number of channels for the node. + uint32 num_channels = 2; + + // The sum of all channels capacity for the node, denominated in satoshis. + int64 total_capacity = 3; + + // A list of all public channels for the node. + repeated ChannelEdge channels = 4; +} + +/* +An individual vertex/node within the channel graph. A node is +connected to other nodes by one or more channel edges emanating from it. As the +graph is directed, a node will also have an incoming edge attached to it for +each outgoing edge. +*/ +message LightningNode { + uint32 last_update = 1; + string pub_key = 2; + string alias = 3; + repeated NodeAddress addresses = 4; + string color = 5; + map features = 6; + + // Custom node announcement tlv records. + map custom_records = 7; +} + +message NodeAddress { + string network = 1; + string addr = 2; +} + +message RoutingPolicy { + uint32 time_lock_delta = 1; + int64 min_htlc = 2; + int64 fee_base_msat = 3; + int64 fee_rate_milli_msat = 4; + bool disabled = 5; + uint64 max_htlc_msat = 6; + uint32 last_update = 7; + + // Custom channel update tlv records. + map custom_records = 8; +} + +/* +A fully authenticated channel along with all its unique attributes. +Once an authenticated channel announcement has been processed on the network, +then an instance of ChannelEdgeInfo encapsulating the channels attributes is +stored. The other portions relevant to routing policy of a channel are stored +within a ChannelEdgePolicy for each direction of the channel. +*/ +message ChannelEdge { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 channel_id = 1 [jstype = JS_STRING]; + string chan_point = 2; + + uint32 last_update = 3 [deprecated = true]; + + string node1_pub = 4; + string node2_pub = 5; + + int64 capacity = 6; + + RoutingPolicy node1_policy = 7; + RoutingPolicy node2_policy = 8; + + // Custom channel announcement tlv records. + map custom_records = 9; +} + +message ChannelGraphRequest { + /* + Whether unannounced channels are included in the response or not. If set, + unannounced channels are included. Unannounced channels are both private + channels, and public channels that are not yet announced to the network. + */ + bool include_unannounced = 1; +} + +// Returns a new instance of the directed channel graph. +message ChannelGraph { + // The list of `LightningNode`s in this channel graph + repeated LightningNode nodes = 1; + + // The list of `ChannelEdge`s in this channel graph + repeated ChannelEdge edges = 2; +} + +enum NodeMetricType { + UNKNOWN = 0; + BETWEENNESS_CENTRALITY = 1; +} + +message NodeMetricsRequest { + // The requested node metrics. + repeated NodeMetricType types = 1; +} + +message NodeMetricsResponse { + /* + Betweenness centrality is the sum of the ratio of shortest paths that pass + through the node for each pair of nodes in the graph (not counting paths + starting or ending at this node). + Map of node pubkey to betweenness centrality of the node. Normalized + values are in the [0,1] closed interval. + */ + map betweenness_centrality = 1; +} + +message FloatMetric { + // Arbitrary float value. + double value = 1; + + // The value normalized to [0,1] or [-1,1]. + double normalized_value = 2; +} + +message ChanInfoRequest { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; +} + +message NetworkInfoRequest { +} +message NetworkInfo { + uint32 graph_diameter = 1; + double avg_out_degree = 2; + uint32 max_out_degree = 3; + + uint32 num_nodes = 4; + uint32 num_channels = 5; + + int64 total_network_capacity = 6; + + double avg_channel_size = 7; + int64 min_channel_size = 8; + int64 max_channel_size = 9; + int64 median_channel_size_sat = 10; + + // The number of edges marked as zombies. + uint64 num_zombie_chans = 11; + + // TODO(roasbeef): fee rate info, expiry + // * also additional RPC for tracking fee info once in +} + +message StopRequest { +} +message StopResponse { +} + +message GraphTopologySubscription { +} +message GraphTopologyUpdate { + repeated NodeUpdate node_updates = 1; + repeated ChannelEdgeUpdate channel_updates = 2; + repeated ClosedChannelUpdate closed_chans = 3; +} +message NodeUpdate { + /* + Deprecated, use node_addresses. + */ + repeated string addresses = 1 [deprecated = true]; + + string identity_key = 2; + + /* + Deprecated, use features. + */ + bytes global_features = 3 [deprecated = true]; + + string alias = 4; + string color = 5; + repeated NodeAddress node_addresses = 7; + + /* + Features that the node has advertised in the init message, node + announcements and invoices. + */ + map features = 6; +} +message ChannelEdgeUpdate { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + + ChannelPoint chan_point = 2; + + int64 capacity = 3; + + RoutingPolicy routing_policy = 4; + + string advertising_node = 5; + string connecting_node = 6; +} +message ClosedChannelUpdate { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + int64 capacity = 2; + uint32 closed_height = 3; + ChannelPoint chan_point = 4; +} + +message HopHint { + // The public key of the node at the start of the channel. + string node_id = 1; + + // The unique identifier of the channel. + uint64 chan_id = 2 [jstype = JS_STRING]; + + // The base fee of the channel denominated in millisatoshis. + uint32 fee_base_msat = 3; + + /* + The fee rate of the channel for sending one satoshi across it denominated in + millionths of a satoshi. + */ + uint32 fee_proportional_millionths = 4; + + // The time-lock delta of the channel. + uint32 cltv_expiry_delta = 5; +} + +message SetID { + bytes set_id = 1; +} + +message RouteHint { + /* + A list of hop hints that when chained together can assist in reaching a + specific destination. + */ + repeated HopHint hop_hints = 1; +} + +message BlindedPaymentPath { + // The blinded path to send the payment to. + BlindedPath blinded_path = 1; + + // The base fee for the blinded path provided, expressed in msat. + uint64 base_fee_msat = 2; + + // The proportional fee for the blinded path provided, expressed in msat. + uint64 proportional_fee_msat = 3; + + /* + The total CLTV delta for the blinded path provided, including the + final CLTV delta for the receiving node. + */ + uint32 total_cltv_delta = 4; + + /* + The minimum hltc size that may be sent over the blinded path, expressed + in msat. + */ + uint64 htlc_min_msat = 5; + + /* + The maximum htlc size that may be sent over the blinded path, expressed + in msat. + */ + uint64 htlc_max_msat = 6; + + // The feature bits for the route. + repeated FeatureBit features = 7; +} + +message BlindedPath { + // The unblinded pubkey of the introduction node for the route. + bytes introduction_node = 1; + + // The ephemeral pubkey used by nodes in the blinded route. + bytes blinding_point = 2; + + /* + A set of blinded node keys and data blobs for the blinded portion of the + route. Note that the first hop is expected to be the introduction node, + so the route is always expected to have at least one hop. + */ + repeated BlindedHop blinded_hops = 3; +} + +message BlindedHop { + // The blinded public key of the node. + bytes blinded_node = 1; + + // An encrypted blob of data provided to the blinded node. + bytes encrypted_data = 2; +} + +message AMPInvoiceState { + // The state the HTLCs associated with this setID are in. + InvoiceHTLCState state = 1; + + // The settle index of this HTLC set, if the invoice state is settled. + uint64 settle_index = 2; + + // The time this HTLC set was settled expressed in unix epoch. + int64 settle_time = 3; + + // The total amount paid for the sub-invoice expressed in milli satoshis. + int64 amt_paid_msat = 5; +} + +message Invoice { + /* + An optional memo to attach along with the invoice. Used for record keeping + purposes for the invoice's creator, and will also be set in the description + field of the encoded payment request if the description_hash field is not + being used. + */ + string memo = 1; + + reserved 2; + + /* + The hex-encoded preimage (32 byte) which will allow settling an incoming + HTLC payable to this preimage. When using REST, this field must be encoded + as base64. + */ + bytes r_preimage = 3; + + /* + The hash of the preimage. When using REST, this field must be encoded as + base64. + Note: Output only, don't specify for creating an invoice. + */ + bytes r_hash = 4; + + /* + The value of this invoice in satoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value = 5; + + /* + The value of this invoice in millisatoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value_msat = 23; + + /* + Whether this invoice has been fulfilled. + + The field is deprecated. Use the state field instead (compare to SETTLED). + */ + bool settled = 6 [deprecated = true]; + + /* + When this invoice was created. + Measured in seconds since the unix epoch. + Note: Output only, don't specify for creating an invoice. + */ + int64 creation_date = 7; + + /* + When this invoice was settled. + Measured in seconds since the unix epoch. + Note: Output only, don't specify for creating an invoice. + */ + int64 settle_date = 8; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + Note: Output only, don't specify for creating an invoice. + */ + string payment_request = 9; + + /* + Hash (SHA-256) of a description of the payment. Used if the description of + payment (memo) is too long to naturally fit within the description field + of an encoded payment request. When using REST, this field must be encoded + as base64. + */ + bytes description_hash = 10; + + // Payment request expiry time in seconds. Default is 86400 (24 hours). + int64 expiry = 11; + + // Fallback on-chain address. + string fallback_addr = 12; + + // Delta to use for the time-lock of the CLTV extended to the final hop. + uint64 cltv_expiry = 13; + + /* + Route hints that can each be individually used to assist in reaching the + invoice's destination. + */ + repeated RouteHint route_hints = 14; + + // Whether this invoice should include routing hints for private channels. + // Note: When enabled, if value and value_msat are zero, a large number of + // hints with these channels can be included, which might not be desirable. + bool private = 15; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + Note: Output only, don't specify for creating an invoice. + */ + uint64 add_index = 16; + + /* + The "settle" index of this invoice. Each newly settled invoice will + increment this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all + settled invoices with an settle_index greater than this one. + Note: Output only, don't specify for creating an invoice. + */ + uint64 settle_index = 17; + + // Deprecated, use amt_paid_sat or amt_paid_msat. + int64 amt_paid = 18 [deprecated = true]; + + /* + The amount that was accepted for this invoice, in satoshis. This will ONLY + be set if this invoice has been settled or accepted. We provide this field + as if the invoice was created with a zero value, then we need to record what + amount was ultimately accepted. Additionally, it's possible that the sender + paid MORE that was specified in the original invoice. So we'll record that + here as well. + Note: Output only, don't specify for creating an invoice. + */ + int64 amt_paid_sat = 19; + + /* + The amount that was accepted for this invoice, in millisatoshis. This will + ONLY be set if this invoice has been settled or accepted. We provide this + field as if the invoice was created with a zero value, then we need to + record what amount was ultimately accepted. Additionally, it's possible that + the sender paid MORE that was specified in the original invoice. So we'll + record that here as well. + Note: Output only, don't specify for creating an invoice. + */ + int64 amt_paid_msat = 20; + + enum InvoiceState { + OPEN = 0; + SETTLED = 1; + CANCELED = 2; + ACCEPTED = 3; + } + + /* + The state the invoice is in. + Note: Output only, don't specify for creating an invoice. + */ + InvoiceState state = 21; + + /* + List of HTLCs paying to this invoice [EXPERIMENTAL]. + Note: Output only, don't specify for creating an invoice. + */ + repeated InvoiceHTLC htlcs = 22; + + /* + List of features advertised on the invoice. + Note: Output only, don't specify for creating an invoice. + */ + map features = 24; + + /* + Indicates if this invoice was a spontaneous payment that arrived via keysend + [EXPERIMENTAL]. + Note: Output only, don't specify for creating an invoice. + */ + bool is_keysend = 25; + + /* + The payment address of this invoice. This value will be used in MPP + payments, and also for newer invoices that always require the MPP payload + for added end-to-end security. + Note: Output only, don't specify for creating an invoice. + */ + bytes payment_addr = 26; + + /* + Signals whether or not this is an AMP invoice. + */ + bool is_amp = 27; + + /* + [EXPERIMENTAL]: + + Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the + given set ID. This field is always populated for AMP invoices, and can be + used along side LookupInvoice to obtain the HTLC information related to a + given sub-invoice. + Note: Output only, don't specify for creating an invoice. + */ + map amp_invoice_state = 28; +} + +enum InvoiceHTLCState { + ACCEPTED = 0; + SETTLED = 1; + CANCELED = 2; +} + +// Details of an HTLC that paid to an invoice +message InvoiceHTLC { + // Short channel id over which the htlc was received. + uint64 chan_id = 1 [jstype = JS_STRING]; + + // Index identifying the htlc on the channel. + uint64 htlc_index = 2; + + // The amount of the htlc in msat. + uint64 amt_msat = 3; + + // Block height at which this htlc was accepted. + int32 accept_height = 4; + + // Time at which this htlc was accepted. + int64 accept_time = 5; + + // Time at which this htlc was settled or canceled. + int64 resolve_time = 6; + + // Block height at which this htlc expires. + int32 expiry_height = 7; + + // Current state the htlc is in. + InvoiceHTLCState state = 8; + + // Custom tlv records. + map custom_records = 9; + + // The total amount of the mpp payment in msat. + uint64 mpp_total_amt_msat = 10; + + // Details relevant to AMP HTLCs, only populated if this is an AMP HTLC. + AMP amp = 11; +} + +// Details specific to AMP HTLCs. +message AMP { + // An n-of-n secret share of the root seed from which child payment hashes + // and preimages are derived. + bytes root_share = 1; + + // An identifier for the HTLC set that this HTLC belongs to. + bytes set_id = 2; + + // A nonce used to randomize the child preimage and child hash from a given + // root_share. + uint32 child_index = 3; + + // The payment hash of the AMP HTLC. + bytes hash = 4; + + // The preimage used to settle this AMP htlc. This field will only be + // populated if the invoice is in InvoiceState_ACCEPTED or + // InvoiceState_SETTLED. + bytes preimage = 5; +} + +message AddInvoiceResponse { + bytes r_hash = 1; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 2; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 16; + + /* + The payment address of the generated invoice. This value should be used + in all payments for this invoice as we require it for end to end + security. + */ + bytes payment_addr = 17; +} +message PaymentHash { + /* + The hex-encoded payment hash of the invoice to be looked up. The passed + payment hash must be exactly 32 bytes, otherwise an error is returned. + Deprecated now that the REST gateway supports base64 encoding of bytes + fields. + */ + string r_hash_str = 1 [deprecated = true]; + + /* + The payment hash of the invoice to be looked up. When using REST, this field + must be encoded as base64. + */ + bytes r_hash = 2; +} + +message ListInvoiceRequest { + /* + If set, only invoices that are not settled and not canceled will be returned + in the response. + */ + bool pending_only = 1; + + /* + The index of an invoice that will be used as either the start or end of a + query to determine which invoices should be returned in the response. + */ + uint64 index_offset = 4; + + // The max number of invoices to return in the response to this query. + uint64 num_max_invoices = 5; + + /* + If set, the invoices returned will result from seeking backwards from the + specified index offset. This can be used to paginate backwards. + */ + bool reversed = 6; + + // If set, returns all invoices with a creation date greater than or equal + // to it. Measured in seconds since the unix epoch. + uint64 creation_date_start = 7; + + // If set, returns all invoices with a creation date less than or equal to + // it. Measured in seconds since the unix epoch. + uint64 creation_date_end = 8; +} + +message ListInvoiceResponse { + /* + A list of invoices from the time slice of the time series specified in the + request. + */ + repeated Invoice invoices = 1; + + /* + The index of the last item in the set of returned invoices. This can be used + to seek further, pagination style. + */ + uint64 last_index_offset = 2; + + /* + The index of the last item in the set of returned invoices. This can be used + to seek backwards, pagination style. + */ + uint64 first_index_offset = 3; +} + +message InvoiceSubscription { + /* + If specified (non-zero), then we'll first start by sending out + notifications for all added indexes with an add_index greater than this + value. This allows callers to catch up on any events they missed while they + weren't connected to the streaming RPC. + */ + uint64 add_index = 1; + + /* + If specified (non-zero), then we'll first start by sending out + notifications for all settled indexes with an settle_index greater than + this value. This allows callers to catch up on any events they missed while + they weren't connected to the streaming RPC. + */ + uint64 settle_index = 2; +} + +enum PaymentFailureReason { + /* + Payment isn't failed (yet). + */ + FAILURE_REASON_NONE = 0; + + /* + There are more routes to try, but the payment timeout was exceeded. + */ + FAILURE_REASON_TIMEOUT = 1; + + /* + All possible routes were tried and failed permanently. Or were no + routes to the destination at all. + */ + FAILURE_REASON_NO_ROUTE = 2; + + /* + A non-recoverable error has occured. + */ + FAILURE_REASON_ERROR = 3; + + /* + Payment details incorrect (unknown hash, invalid amt or + invalid final cltv delta) + */ + FAILURE_REASON_INCORRECT_PAYMENT_DETAILS = 4; + + /* + Insufficient local balance. + */ + FAILURE_REASON_INSUFFICIENT_BALANCE = 5; +} + +message Payment { + // The payment hash + string payment_hash = 1; + + // Deprecated, use value_sat or value_msat. + int64 value = 2 [deprecated = true]; + + // Deprecated, use creation_time_ns + int64 creation_date = 3 [deprecated = true]; + + reserved 4; + + // Deprecated, use fee_sat or fee_msat. + int64 fee = 5 [deprecated = true]; + + // The payment preimage + string payment_preimage = 6; + + // The value of the payment in satoshis + int64 value_sat = 7; + + // The value of the payment in milli-satoshis + int64 value_msat = 8; + + // The optional payment request being fulfilled. + string payment_request = 9; + + enum PaymentStatus { + // Deprecated. This status will never be returned. + UNKNOWN = 0 [deprecated = true]; + + // Payment has inflight HTLCs. + IN_FLIGHT = 1; + + // Payment is settled. + SUCCEEDED = 2; + + // Payment is failed. + FAILED = 3; + + // Payment is created and has not attempted any HTLCs. + INITIATED = 4; + } + + // The status of the payment. + PaymentStatus status = 10; + + // The fee paid for this payment in satoshis + int64 fee_sat = 11; + + // The fee paid for this payment in milli-satoshis + int64 fee_msat = 12; + + // The time in UNIX nanoseconds at which the payment was created. + int64 creation_time_ns = 13; + + // The HTLCs made in attempt to settle the payment. + repeated HTLCAttempt htlcs = 14; + + /* + The creation index of this payment. Each payment can be uniquely identified + by this index, which may not strictly increment by 1 for payments made in + older versions of lnd. + */ + uint64 payment_index = 15; + + PaymentFailureReason failure_reason = 16; +} + +message HTLCAttempt { + // The unique ID that is used for this attempt. + uint64 attempt_id = 7; + + enum HTLCStatus { + IN_FLIGHT = 0; + SUCCEEDED = 1; + FAILED = 2; + } + + // The status of the HTLC. + HTLCStatus status = 1; + + // The route taken by this HTLC. + Route route = 2; + + // The time in UNIX nanoseconds at which this HTLC was sent. + int64 attempt_time_ns = 3; + + /* + The time in UNIX nanoseconds at which this HTLC was settled or failed. + This value will not be set if the HTLC is still IN_FLIGHT. + */ + int64 resolve_time_ns = 4; + + // Detailed htlc failure info. + Failure failure = 5; + + // The preimage that was used to settle the HTLC. + bytes preimage = 6; +} + +message ListPaymentsRequest { + /* + If true, then return payments that have not yet fully completed. This means + that pending payments, as well as failed payments will show up if this + field is set to true. This flag doesn't change the meaning of the indices, + which are tied to individual payments. + */ + bool include_incomplete = 1; + + /* + The index of a payment that will be used as either the start or end of a + query to determine which payments should be returned in the response. The + index_offset is exclusive. In the case of a zero index_offset, the query + will start with the oldest payment when paginating forwards, or will end + with the most recent payment when paginating backwards. + */ + uint64 index_offset = 2; + + // The maximal number of payments returned in the response to this query. + uint64 max_payments = 3; + + /* + If set, the payments returned will result from seeking backwards from the + specified index offset. This can be used to paginate backwards. The order + of the returned payments is always oldest first (ascending index order). + */ + bool reversed = 4; + + /* + If set, all payments (complete and incomplete, independent of the + max_payments parameter) will be counted. Note that setting this to true will + increase the run time of the call significantly on systems that have a lot + of payments, as all of them have to be iterated through to be counted. + */ + bool count_total_payments = 5; + + // If set, returns all invoices with a creation date greater than or equal + // to it. Measured in seconds since the unix epoch. + uint64 creation_date_start = 6; + + // If set, returns all invoices with a creation date less than or equal to + // it. Measured in seconds since the unix epoch. + uint64 creation_date_end = 7; +} + +message ListPaymentsResponse { + // The list of payments + repeated Payment payments = 1; + + /* + The index of the first item in the set of returned payments. This can be + used as the index_offset to continue seeking backwards in the next request. + */ + uint64 first_index_offset = 2; + + /* + The index of the last item in the set of returned payments. This can be used + as the index_offset to continue seeking forwards in the next request. + */ + uint64 last_index_offset = 3; + + /* + Will only be set if count_total_payments in the request was set. Represents + the total number of payments (complete and incomplete, independent of the + number of payments requested in the query) currently present in the payments + database. + */ + uint64 total_num_payments = 4; +} + +message DeletePaymentRequest { + // Payment hash to delete. + bytes payment_hash = 1; + + /* + Only delete failed HTLCs from the payment, not the payment itself. + */ + bool failed_htlcs_only = 2; +} + +message DeleteAllPaymentsRequest { + // Only delete failed payments. + bool failed_payments_only = 1; + + /* + Only delete failed HTLCs from payments, not the payment itself. + */ + bool failed_htlcs_only = 2; +} + +message DeletePaymentResponse { +} + +message DeleteAllPaymentsResponse { +} + +message AbandonChannelRequest { + ChannelPoint channel_point = 1; + + bool pending_funding_shim_only = 2; + + /* + Override the requirement for being in dev mode by setting this to true and + confirming the user knows what they are doing and this is a potential foot + gun to lose funds if used on active channels. + */ + bool i_know_what_i_am_doing = 3; +} + +message AbandonChannelResponse { +} + +message DebugLevelRequest { + bool show = 1; + string level_spec = 2; +} +message DebugLevelResponse { + string sub_systems = 1; +} + +message PayReqString { + // The payment request string to be decoded + string pay_req = 1; +} +message PayReq { + string destination = 1; + string payment_hash = 2; + int64 num_satoshis = 3; + int64 timestamp = 4; + int64 expiry = 5; + string description = 6; + string description_hash = 7; + string fallback_addr = 8; + int64 cltv_expiry = 9; + repeated RouteHint route_hints = 10; + bytes payment_addr = 11; + int64 num_msat = 12; + map features = 13; +} + +enum FeatureBit { + DATALOSS_PROTECT_REQ = 0; + DATALOSS_PROTECT_OPT = 1; + INITIAL_ROUING_SYNC = 3; + UPFRONT_SHUTDOWN_SCRIPT_REQ = 4; + UPFRONT_SHUTDOWN_SCRIPT_OPT = 5; + GOSSIP_QUERIES_REQ = 6; + GOSSIP_QUERIES_OPT = 7; + TLV_ONION_REQ = 8; + TLV_ONION_OPT = 9; + EXT_GOSSIP_QUERIES_REQ = 10; + EXT_GOSSIP_QUERIES_OPT = 11; + STATIC_REMOTE_KEY_REQ = 12; + STATIC_REMOTE_KEY_OPT = 13; + PAYMENT_ADDR_REQ = 14; + PAYMENT_ADDR_OPT = 15; + MPP_REQ = 16; + MPP_OPT = 17; + WUMBO_CHANNELS_REQ = 18; + WUMBO_CHANNELS_OPT = 19; + ANCHORS_REQ = 20; + ANCHORS_OPT = 21; + ANCHORS_ZERO_FEE_HTLC_REQ = 22; + ANCHORS_ZERO_FEE_HTLC_OPT = 23; + AMP_REQ = 30; + AMP_OPT = 31; +} + +message Feature { + string name = 2; + bool is_required = 3; + bool is_known = 4; +} + +message FeeReportRequest { +} +message ChannelFeeReport { + // The short channel id that this fee report belongs to. + uint64 chan_id = 5 [jstype = JS_STRING]; + + // The channel that this fee report belongs to. + string channel_point = 1; + + // The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 2; + + // The amount charged per milli-satoshis transferred expressed in + // millionths of a satoshi. + int64 fee_per_mil = 3; + + // The effective fee rate in milli-satoshis. Computed by dividing the + // fee_per_mil value by 1 million. + double fee_rate = 4; +} +message FeeReportResponse { + // An array of channel fee reports which describes the current fee schedule + // for each channel. + repeated ChannelFeeReport channel_fees = 1; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 24 hrs. + uint64 day_fee_sum = 2; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 1 week. + uint64 week_fee_sum = 3; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 1 month. + uint64 month_fee_sum = 4; +} + +message PolicyUpdateRequest { + oneof scope { + // If set, then this update applies to all currently active channels. + bool global = 1; + + // If set, this update will target a specific channel. + ChannelPoint chan_point = 2; + } + + // The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 3; + + // The effective fee rate in milli-satoshis. The precision of this value + // goes up to 6 decimal places, so 1e-6. + double fee_rate = 4; + + // The effective fee rate in micro-satoshis (parts per million). + uint32 fee_rate_ppm = 9; + + // The required timelock delta for HTLCs forwarded over the channel. + uint32 time_lock_delta = 5; + + // If set, the maximum HTLC size in milli-satoshis. If unset, the maximum + // HTLC will be unchanged. + uint64 max_htlc_msat = 6; + + // The minimum HTLC size in milli-satoshis. Only applied if + // min_htlc_msat_specified is true. + uint64 min_htlc_msat = 7; + + // If true, min_htlc_msat is applied. + bool min_htlc_msat_specified = 8; +} +enum UpdateFailure { + UPDATE_FAILURE_UNKNOWN = 0; + UPDATE_FAILURE_PENDING = 1; + UPDATE_FAILURE_NOT_FOUND = 2; + UPDATE_FAILURE_INTERNAL_ERR = 3; + UPDATE_FAILURE_INVALID_PARAMETER = 4; +} + +message FailedUpdate { + // The outpoint in format txid:n + OutPoint outpoint = 1; + + // Reason for the policy update failure. + UpdateFailure reason = 2; + + // A string representation of the policy update error. + string update_error = 3; +} + +message PolicyUpdateResponse { + // List of failed policy updates. + repeated FailedUpdate failed_updates = 1; +} + +message ForwardingHistoryRequest { + // Start time is the starting point of the forwarding history request. All + // records beyond this point will be included, respecting the end time, and + // the index offset. + uint64 start_time = 1; + + // End time is the end point of the forwarding history request. The + // response will carry at most 50k records between the start time and the + // end time. The index offset can be used to implement pagination. + uint64 end_time = 2; + + // Index offset is the offset in the time series to start at. As each + // response can only contain 50k records, callers can use this to skip + // around within a packed time series. + uint32 index_offset = 3; + + // The max number of events to return in the response to this query. + uint32 num_max_events = 4; + + // Informs the server if the peer alias should be looked up for each + // forwarding event. + bool peer_alias_lookup = 5; +} +message ForwardingEvent { + // Timestamp is the time (unix epoch offset) that this circuit was + // completed. Deprecated by timestamp_ns. + uint64 timestamp = 1 [deprecated = true]; + + // The incoming channel ID that carried the HTLC that created the circuit. + uint64 chan_id_in = 2 [jstype = JS_STRING]; + + // The outgoing channel ID that carried the preimage that completed the + // circuit. + uint64 chan_id_out = 4 [jstype = JS_STRING]; + + // The total amount (in satoshis) of the incoming HTLC that created half + // the circuit. + uint64 amt_in = 5; + + // The total amount (in satoshis) of the outgoing HTLC that created the + // second half of the circuit. + uint64 amt_out = 6; + + // The total fee (in satoshis) that this payment circuit carried. + uint64 fee = 7; + + // The total fee (in milli-satoshis) that this payment circuit carried. + uint64 fee_msat = 8; + + // The total amount (in milli-satoshis) of the incoming HTLC that created + // half the circuit. + uint64 amt_in_msat = 9; + + // The total amount (in milli-satoshis) of the outgoing HTLC that created + // the second half of the circuit. + uint64 amt_out_msat = 10; + + // The number of nanoseconds elapsed since January 1, 1970 UTC when this + // circuit was completed. + uint64 timestamp_ns = 11; + + // The peer alias of the incoming channel. + string peer_alias_in = 12; + + // The peer alias of the outgoing channel. + string peer_alias_out = 13; + + // TODO(roasbeef): add settlement latency? + // * use FPE on the chan id? + // * also list failures? +} +message ForwardingHistoryResponse { + // A list of forwarding events from the time slice of the time series + // specified in the request. + repeated ForwardingEvent forwarding_events = 1; + + // The index of the last time in the set of returned forwarding events. Can + // be used to seek further, pagination style. + uint32 last_offset_index = 2; +} + +message ExportChannelBackupRequest { + // The target channel point to obtain a back up for. + ChannelPoint chan_point = 1; +} + +message ChannelBackup { + /* + Identifies the channel that this backup belongs to. + */ + ChannelPoint chan_point = 1; + + /* + Is an encrypted single-chan backup. this can be passed to + RestoreChannelBackups, or the WalletUnlocker Init and Unlock methods in + order to trigger the recovery protocol. When using REST, this field must be + encoded as base64. + */ + bytes chan_backup = 2; +} + +message MultiChanBackup { + /* + Is the set of all channels that are included in this multi-channel backup. + */ + repeated ChannelPoint chan_points = 1; + + /* + A single encrypted blob containing all the static channel backups of the + channel listed above. This can be stored as a single file or blob, and + safely be replaced with any prior/future versions. When using REST, this + field must be encoded as base64. + */ + bytes multi_chan_backup = 2; +} + +message ChanBackupExportRequest { +} +message ChanBackupSnapshot { + /* + The set of new channels that have been added since the last channel backup + snapshot was requested. + */ + ChannelBackups single_chan_backups = 1; + + /* + A multi-channel backup that covers all open channels currently known to + lnd. + */ + MultiChanBackup multi_chan_backup = 2; +} + +message ChannelBackups { + /* + A set of single-chan static channel backups. + */ + repeated ChannelBackup chan_backups = 1; +} + +message RestoreChanBackupRequest { + oneof backup { + /* + The channels to restore as a list of channel/backup pairs. + */ + ChannelBackups chan_backups = 1; + + /* + The channels to restore in the packed multi backup format. When using + REST, this field must be encoded as base64. + */ + bytes multi_chan_backup = 2; + } +} +message RestoreBackupResponse { +} + +message ChannelBackupSubscription { +} + +message VerifyChanBackupResponse { +} + +message MacaroonPermission { + // The entity a permission grants access to. + string entity = 1; + + // The action that is granted. + string action = 2; +} +message BakeMacaroonRequest { + // The list of permissions the new macaroon should grant. + repeated MacaroonPermission permissions = 1; + + // The root key ID used to create the macaroon, must be a positive integer. + uint64 root_key_id = 2; + + /* + Informs the RPC on whether to allow external permissions that LND is not + aware of. + */ + bool allow_external_permissions = 3; +} +message BakeMacaroonResponse { + // The hex encoded macaroon, serialized in binary format. + string macaroon = 1; +} + +message ListMacaroonIDsRequest { +} +message ListMacaroonIDsResponse { + // The list of root key IDs that are in use. + repeated uint64 root_key_ids = 1; +} + +message DeleteMacaroonIDRequest { + // The root key ID to be removed. + uint64 root_key_id = 1; +} +message DeleteMacaroonIDResponse { + // A boolean indicates that the deletion is successful. + bool deleted = 1; +} + +message MacaroonPermissionList { + // A list of macaroon permissions. + repeated MacaroonPermission permissions = 1; +} + +message ListPermissionsRequest { +} +message ListPermissionsResponse { + /* + A map between all RPC method URIs and their required macaroon permissions to + access them. + */ + map method_permissions = 1; +} + +message Failure { + enum FailureCode { + /* + The numbers assigned in this enumeration match the failure codes as + defined in BOLT #4. Because protobuf 3 requires enums to start with 0, + a RESERVED value is added. + */ + RESERVED = 0; + + INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS = 1; + INCORRECT_PAYMENT_AMOUNT = 2; + FINAL_INCORRECT_CLTV_EXPIRY = 3; + FINAL_INCORRECT_HTLC_AMOUNT = 4; + FINAL_EXPIRY_TOO_SOON = 5; + INVALID_REALM = 6; + EXPIRY_TOO_SOON = 7; + INVALID_ONION_VERSION = 8; + INVALID_ONION_HMAC = 9; + INVALID_ONION_KEY = 10; + AMOUNT_BELOW_MINIMUM = 11; + FEE_INSUFFICIENT = 12; + INCORRECT_CLTV_EXPIRY = 13; + CHANNEL_DISABLED = 14; + TEMPORARY_CHANNEL_FAILURE = 15; + REQUIRED_NODE_FEATURE_MISSING = 16; + REQUIRED_CHANNEL_FEATURE_MISSING = 17; + UNKNOWN_NEXT_PEER = 18; + TEMPORARY_NODE_FAILURE = 19; + PERMANENT_NODE_FAILURE = 20; + PERMANENT_CHANNEL_FAILURE = 21; + EXPIRY_TOO_FAR = 22; + MPP_TIMEOUT = 23; + INVALID_ONION_PAYLOAD = 24; + + /* + An internal error occurred. + */ + INTERNAL_FAILURE = 997; + + /* + The error source is known, but the failure itself couldn't be decoded. + */ + UNKNOWN_FAILURE = 998; + + /* + An unreadable failure result is returned if the received failure message + cannot be decrypted. In that case the error source is unknown. + */ + UNREADABLE_FAILURE = 999; + } + + // Failure code as defined in the Lightning spec + FailureCode code = 1; + + reserved 2; + + // An optional channel update message. + ChannelUpdate channel_update = 3; + + // A failure type-dependent htlc value. + uint64 htlc_msat = 4; + + // The sha256 sum of the onion payload. + bytes onion_sha_256 = 5; + + // A failure type-dependent cltv expiry value. + uint32 cltv_expiry = 6; + + // A failure type-dependent flags value. + uint32 flags = 7; + + /* + The position in the path of the intermediate or final node that generated + the failure message. Position zero is the sender node. + **/ + uint32 failure_source_index = 8; + + // A failure type-dependent block height. + uint32 height = 9; +} + +message ChannelUpdate { + /* + The signature that validates the announced data and proves the ownership + of node id. + */ + bytes signature = 1; + + /* + The target chain that this channel was opened within. This value + should be the genesis hash of the target chain. Along with the short + channel ID, this uniquely identifies the channel globally in a + blockchain. + */ + bytes chain_hash = 2; + + /* + The unique description of the funding transaction. + */ + uint64 chan_id = 3 [jstype = JS_STRING]; + + /* + A timestamp that allows ordering in the case of multiple announcements. + We should ignore the message if timestamp is not greater than the + last-received. + */ + uint32 timestamp = 4; + + /* + The bitfield that describes whether optional fields are present in this + update. Currently, the least-significant bit must be set to 1 if the + optional field MaxHtlc is present. + */ + uint32 message_flags = 10; + + /* + The bitfield that describes additional meta-data concerning how the + update is to be interpreted. Currently, the least-significant bit must be + set to 0 if the creating node corresponds to the first node in the + previously sent channel announcement and 1 otherwise. If the second bit + is set, then the channel is set to be disabled. + */ + uint32 channel_flags = 5; + + /* + The minimum number of blocks this node requires to be added to the expiry + of HTLCs. This is a security parameter determined by the node operator. + This value represents the required gap between the time locks of the + incoming and outgoing HTLC's set to this node. + */ + uint32 time_lock_delta = 6; + + /* + The minimum HTLC value which will be accepted. + */ + uint64 htlc_minimum_msat = 7; + + /* + The base fee that must be used for incoming HTLC's to this particular + channel. This value will be tacked onto the required for a payment + independent of the size of the payment. + */ + uint32 base_fee = 8; + + /* + The fee rate that will be charged per millionth of a satoshi. + */ + uint32 fee_rate = 9; + + /* + The maximum HTLC value which will be accepted. + */ + uint64 htlc_maximum_msat = 11; + + /* + The set of data that was appended to this message, some of which we may + not actually know how to iterate or parse. By holding onto this data, we + ensure that we're able to properly validate the set of signatures that + cover these new fields, and ensure we're able to make upgrades to the + network in a forwards compatible manner. + */ + bytes extra_opaque_data = 12; +} + +message MacaroonId { + bytes nonce = 1; + bytes storageId = 2; + repeated Op ops = 3; +} + +message Op { + string entity = 1; + repeated string actions = 2; +} + +message CheckMacPermRequest { + bytes macaroon = 1; + repeated MacaroonPermission permissions = 2; + string fullMethod = 3; +} + +message CheckMacPermResponse { + bool valid = 1; +} + +message RPCMiddlewareRequest { + /* + The unique ID of the intercepted original gRPC request. Useful for mapping + request to response when implementing full duplex message interception. For + streaming requests, this will be the same ID for all incoming and outgoing + middleware intercept messages of the _same_ stream. + */ + uint64 request_id = 1; + + /* + The raw bytes of the complete macaroon as sent by the gRPC client in the + original request. This might be empty for a request that doesn't require + macaroons such as the wallet unlocker RPCs. + */ + bytes raw_macaroon = 2; + + /* + The parsed condition of the macaroon's custom caveat for convenient access. + This field only contains the value of the custom caveat that the handling + middleware has registered itself for. The condition _must_ be validated for + messages of intercept_type stream_auth and request! + */ + string custom_caveat_condition = 3; + + /* + There are three types of messages that will be sent to the middleware for + inspection and approval: Stream authentication, request and response + interception. The first two can only be accepted (=forward to main RPC + server) or denied (=return error to client). Intercepted responses can also + be replaced/overwritten. + */ + oneof intercept_type { + /* + Intercept stream authentication: each new streaming RPC call that is + initiated against lnd and contains the middleware's custom macaroon + caveat can be approved or denied based upon the macaroon in the stream + header. This message will only be sent for streaming RPCs, unary RPCs + must handle the macaroon authentication in the request interception to + avoid an additional message round trip between lnd and the middleware. + */ + StreamAuth stream_auth = 4; + + /* + Intercept incoming gRPC client request message: all incoming messages, + both on streaming and unary RPCs, are forwarded to the middleware for + inspection. For unary RPC messages the middleware is also expected to + validate the custom macaroon caveat of the request. + */ + RPCMessage request = 5; + + /* + Intercept outgoing gRPC response message: all outgoing messages, both on + streaming and unary RPCs, are forwarded to the middleware for inspection + and amendment. The response in this message is the original response as + it was generated by the main RPC server. It can either be accepted + (=forwarded to the client), replaced/overwritten with a new message of + the same type, or replaced by an error message. + */ + RPCMessage response = 6; + + /* + This is used to indicate to the client that the server has successfully + registered the interceptor. This is only used in the very first message + that the server sends to the client after the client sends the server + the middleware registration message. + */ + bool reg_complete = 8; + } + + /* + The unique message ID of this middleware intercept message. There can be + multiple middleware intercept messages per single gRPC request (one for the + incoming request and one for the outgoing response) or gRPC stream (one for + each incoming message and one for each outgoing response). This message ID + must be referenced when responding (accepting/rejecting/modifying) to an + intercept message. + */ + uint64 msg_id = 7; +} + +message StreamAuth { + /* + The full URI (in the format /./MethodName, for + example /lnrpc.Lightning/GetInfo) of the streaming RPC method that was just + established. + */ + string method_full_uri = 1; +} + +message RPCMessage { + /* + The full URI (in the format /./MethodName, for + example /lnrpc.Lightning/GetInfo) of the RPC method the message was sent + to/from. + */ + string method_full_uri = 1; + + /* + Indicates whether the message was sent over a streaming RPC method or not. + */ + bool stream_rpc = 2; + + /* + The full canonical gRPC name of the message type (in the format + .TypeName, for example lnrpc.GetInfoRequest). In case of an + error being returned from lnd, this simply contains the string "error". + */ + string type_name = 3; + + /* + The full content of the gRPC message, serialized in the binary protobuf + format. + */ + bytes serialized = 4; + + /* + Indicates that the response from lnd was an error, not a gRPC response. If + this is set to true then the type_name contains the string "error" and + serialized contains the error string. + */ + bool is_error = 5; +} + +message RPCMiddlewareResponse { + /* + The request message ID this response refers to. Must always be set when + giving feedback to an intercept but is ignored for the initial registration + message. + */ + uint64 ref_msg_id = 1; + + /* + The middleware can only send two types of messages to lnd: The initial + registration message that identifies the middleware and after that only + feedback messages to requests sent to the middleware. + */ + oneof middleware_message { + /* + The registration message identifies the middleware that's being + registered in lnd. The registration message must be sent immediately + after initiating the RegisterRpcMiddleware stream, otherwise lnd will + time out the attempt and terminate the request. NOTE: The middleware + will only receive interception messages for requests that contain a + macaroon with the custom caveat that the middleware declares it is + responsible for handling in the registration message! As a security + measure, _no_ middleware can intercept requests made with _unencumbered_ + macaroons! + */ + MiddlewareRegistration register = 2; + + /* + The middleware received an interception request and gives feedback to + it. The request_id indicates what message the feedback refers to. + */ + InterceptFeedback feedback = 3; + } +} + +message MiddlewareRegistration { + /* + The name of the middleware to register. The name should be as informative + as possible and is logged on registration. + */ + string middleware_name = 1; + + /* + The name of the custom macaroon caveat that this middleware is responsible + for. Only requests/responses that contain a macaroon with the registered + custom caveat are forwarded for interception to the middleware. The + exception being the read-only mode: All requests/responses are forwarded to + a middleware that requests read-only access but such a middleware won't be + allowed to _alter_ responses. As a security measure, _no_ middleware can + change responses to requests made with _unencumbered_ macaroons! + NOTE: Cannot be used at the same time as read_only_mode. + */ + string custom_macaroon_caveat_name = 2; + + /* + Instead of defining a custom macaroon caveat name a middleware can register + itself for read-only access only. In that mode all requests/responses are + forwarded to the middleware but the middleware isn't allowed to alter any of + the responses. + NOTE: Cannot be used at the same time as custom_macaroon_caveat_name. + */ + bool read_only_mode = 3; +} + +message InterceptFeedback { + /* + The error to return to the user. If this is non-empty, the incoming gRPC + stream/request is aborted and the error is returned to the gRPC client. If + this value is empty, it means the middleware accepts the stream/request/ + response and the processing of it can continue. + */ + string error = 1; + + /* + A boolean indicating that the gRPC message should be replaced/overwritten. + This boolean is needed because in protobuf an empty message is serialized as + a 0-length or nil byte slice and we wouldn't be able to distinguish between + an empty replacement message and the "don't replace anything" case. + */ + bool replace_response = 2; + + /* + If the replace_response field is set to true, this field must contain the + binary serialized gRPC message in the protobuf format. + */ + bytes replacement_serialized = 3; +} diff --git a/proto/neutrinorpc/neutrino.proto b/proto/neutrinorpc/neutrino.proto new file mode 100644 index 000000000..5a61feaad --- /dev/null +++ b/proto/neutrinorpc/neutrino.proto @@ -0,0 +1,228 @@ +syntax = "proto3"; + +package neutrinorpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"; + +// NeutrinoKit is a service that can be used to get information about the +// current state of the neutrino instance, fetch blocks and add/remove peers. +service NeutrinoKit { + /* + Status returns the status of the light client neutrino instance, + along with height and hash of the best block, and a list of connected + peers. + */ + rpc Status (StatusRequest) returns (StatusResponse); + + /* + AddPeer adds a new peer that has already been connected to the server. + */ + rpc AddPeer (AddPeerRequest) returns (AddPeerResponse); + + /* + DisconnectPeer disconnects a peer by target address. Both outbound and + inbound nodes will be searched for the target node. An error message will + be returned if the peer was not found. + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse); + + /* + IsBanned returns true if the peer is banned, otherwise false. + */ + rpc IsBanned (IsBannedRequest) returns (IsBannedResponse); + + /* + GetBlockHeader returns a block header with a particular block hash. + */ + rpc GetBlockHeader (GetBlockHeaderRequest) returns (GetBlockHeaderResponse); + + /* + GetBlock returns a block with a particular block hash. + */ + rpc GetBlock (GetBlockRequest) returns (GetBlockResponse); + + /* + GetCFilter returns a compact filter from a block. + */ + rpc GetCFilter (GetCFilterRequest) returns (GetCFilterResponse); + + /* + Deprecated, use chainrpc.GetBlockHash instead. + GetBlockHash returns the header hash of a block at a given height. + */ + rpc GetBlockHash (GetBlockHashRequest) returns (GetBlockHashResponse) { + option deprecated = true; + } +} + +message StatusRequest { +} + +message StatusResponse { + // Indicates whether the neutrino backend is active or not. + bool active = 1; + + // Is fully synced. + bool synced = 2; + + // Best block height. + int32 block_height = 3; + + // Best block hash. + string block_hash = 4; + + // Connected peers. + repeated string peers = 5; +} + +message AddPeerRequest { + // Peer to add. + string peer_addrs = 1; +} + +message AddPeerResponse { +} + +message DisconnectPeerRequest { + // Peer to disconnect. + string peer_addrs = 1; +} + +message DisconnectPeerResponse { +} + +message IsBannedRequest { + // Peer to lookup. + string peer_addrs = 1; +} + +message IsBannedResponse { + bool banned = 1; +} + +message GetBlockHeaderRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockHeaderResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 10; + + // The nonce. + uint32 nonce = 11; + + // The bits in hex notation. + string bits = 12; + + // The number of transactions in the block. + int32 ntx = 13; + + // The hash of the previous block. + string previous_block_hash = 14; + + // The raw hex of the block. + bytes raw_hex = 15; +} + +message GetBlockRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // List of transaction ids. + repeated string tx = 10; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 11; + + // The nonce. + uint32 nonce = 12; + + // The bits in hex notation. + string bits = 13; + + // The number of transactions in the block. + int32 ntx = 14; + + // The hash of the previous block. + string previous_block_hash = 15; + + // The raw hex of the block. + bytes raw_hex = 16; +} + +message GetCFilterRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetCFilterResponse { + // GCS filter. + bytes filter = 1; +} + +message GetBlockHashRequest { + // The block height or index. + int32 height = 1; +} + +message GetBlockHashResponse { + // The block hash. + string hash = 1; +} diff --git a/proto/routerrpc/router.proto b/proto/routerrpc/router.proto new file mode 100644 index 000000000..d591cc485 --- /dev/null +++ b/proto/routerrpc/router.proto @@ -0,0 +1,952 @@ +syntax = "proto3"; + +import "lightning.proto"; + +package routerrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc"; + +// Router is a service that offers advanced interaction with the router +// subsystem of the daemon. +service Router { + /* + SendPaymentV2 attempts to route a payment described by the passed + PaymentRequest to the final destination. The call returns a stream of + payment updates. + */ + rpc SendPaymentV2 (SendPaymentRequest) returns (stream lnrpc.Payment); + + /* + TrackPaymentV2 returns an update stream for the payment identified by the + payment hash. + */ + rpc TrackPaymentV2 (TrackPaymentRequest) returns (stream lnrpc.Payment); + + /* + TrackPayments returns an update stream for every payment that is not in a + terminal state. Note that if payments are in-flight while starting a new + subscription, the start of the payment stream could produce out-of-order + and/or duplicate events. In order to get updates for every in-flight + payment attempt make sure to subscribe to this method before initiating any + payments. + */ + rpc TrackPayments (TrackPaymentsRequest) returns (stream lnrpc.Payment); + + /* + EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it + may cost to send an HTLC to the target end destination. + */ + rpc EstimateRouteFee (RouteFeeRequest) returns (RouteFeeResponse); + + /* + Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via + the specified route. This method differs from SendPayment in that it + allows users to specify a full route manually. This can be used for + things like rebalancing, and atomic swaps. It differs from the newer + SendToRouteV2 in that it doesn't return the full HTLC information. + */ + rpc SendToRoute (SendToRouteRequest) returns (SendToRouteResponse) { + option deprecated = true; + } + + /* + SendToRouteV2 attempts to make a payment via the specified route. This + method differs from SendPayment in that it allows users to specify a full + route manually. This can be used for things like rebalancing, and atomic + swaps. + */ + rpc SendToRouteV2 (SendToRouteRequest) returns (lnrpc.HTLCAttempt); + + /* + ResetMissionControl clears all mission control state and starts with a clean + slate. + */ + rpc ResetMissionControl (ResetMissionControlRequest) + returns (ResetMissionControlResponse); + + /* + QueryMissionControl exposes the internal mission control state to callers. + It is a development feature. + */ + rpc QueryMissionControl (QueryMissionControlRequest) + returns (QueryMissionControlResponse); + + /* + XImportMissionControl is an experimental API that imports the state provided + to the internal mission control's state, using all results which are more + recent than our existing values. These values will only be imported + in-memory, and will not be persisted across restarts. + */ + rpc XImportMissionControl (XImportMissionControlRequest) + returns (XImportMissionControlResponse); + + /* + GetMissionControlConfig returns mission control's current config. + */ + rpc GetMissionControlConfig (GetMissionControlConfigRequest) + returns (GetMissionControlConfigResponse); + + /* + SetMissionControlConfig will set mission control's config, if the config + provided is valid. + */ + rpc SetMissionControlConfig (SetMissionControlConfigRequest) + returns (SetMissionControlConfigResponse); + + /* + Deprecated. QueryProbability returns the current success probability + estimate for a given node pair and amount. The call returns a zero success + probability if no channel is available or if the amount violates min/max + HTLC constraints. + */ + rpc QueryProbability (QueryProbabilityRequest) + returns (QueryProbabilityResponse); + + /* + BuildRoute builds a fully specified route based on a list of hop public + keys. It retrieves the relevant channel policies from the graph in order to + calculate the correct fees and time locks. + */ + rpc BuildRoute (BuildRouteRequest) returns (BuildRouteResponse); + + /* + SubscribeHtlcEvents creates a uni-directional stream from the server to + the client which delivers a stream of htlc events. + */ + rpc SubscribeHtlcEvents (SubscribeHtlcEventsRequest) + returns (stream HtlcEvent); + + /* + Deprecated, use SendPaymentV2. SendPayment attempts to route a payment + described by the passed PaymentRequest to the final destination. The call + returns a stream of payment status updates. + */ + rpc SendPayment (SendPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + + /* + Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for + the payment identified by the payment hash. + */ + rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + + /** + HtlcInterceptor dispatches a bi-directional streaming RPC in which + Forwarded HTLC requests are sent to the client and the client responds with + a boolean that tells LND if this htlc should be intercepted. + In case of interception, the htlc can be either settled, cancelled or + resumed later by using the ResolveHoldForward endpoint. + */ + rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) + returns (stream ForwardHtlcInterceptRequest); + + /* + UpdateChanStatus attempts to manually set the state of a channel + (enabled, disabled, or auto). A manual "disable" request will cause the + channel to stay disabled until a subsequent manual request of either + "enable" or "auto". + */ + rpc UpdateChanStatus (UpdateChanStatusRequest) + returns (UpdateChanStatusResponse); +} + +message SendPaymentRequest { + // The identity pubkey of the payment recipient + bytes dest = 1; + + /* + Number of satoshis to send. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 2; + + /* + Number of millisatoshis to send. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + // The hash to use within the payment's HTLC + bytes payment_hash = 3; + + /* + The CLTV delta from the current height that should be used to set the + timelock for the final hop. + */ + int32 final_cltv_delta = 4; + + // An optional payment addr to be included within the last hop of the route. + bytes payment_addr = 20; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. The amount in the payment request may be zero. In + that case it is required to set the amt field as well. If no payment request + is specified, the following fields are required: dest, amt and payment_hash. + */ + string payment_request = 5; + + /* + An upper limit on the amount of time we should spend when attempting to + fulfill the payment. This is expressed in seconds. If we cannot make a + successful payment within this time frame, an error will be returned. + This field must be non-zero. + */ + int32 timeout_seconds = 6; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + If this field is left to the default value of 0, only zero-fee routes will + be considered. This usually means single hop routes connecting directly to + the destination. To send the payment without a fee limit, use max int here. + + The fields fee_limit_sat and fee_limit_msat are mutually exclusive. + */ + int64 fee_limit_sat = 7; + + /* + The maximum number of millisatoshis that will be paid as a fee of the + payment. If this field is left to the default value of 0, only zero-fee + routes will be considered. This usually means single hop routes connecting + directly to the destination. To send the payment without a fee limit, use + max int here. + + The fields fee_limit_sat and fee_limit_msat are mutually exclusive. + */ + int64 fee_limit_msat = 13; + + /* + Deprecated, use outgoing_chan_ids. The channel id of the channel that must + be taken to the first hop. If zero, any channel may be used (unless + outgoing_chan_ids are set). + */ + uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true]; + + /* + The channel ids of the channels are allowed for the first hop. If empty, + any channel may be used. + */ + repeated uint64 outgoing_chan_ids = 19; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 14; + + /* + An optional maximum total time lock for the route. This should not exceed + lnd's `--max-cltv-expiry` setting. If zero, then the value of + `--max-cltv-expiry` is enforced. + */ + int32 cltv_limit = 9; + + /* + Optional route hints to reach the destination through private channels. + */ + repeated lnrpc.RouteHint route_hints = 10; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. Record types are + required to be in the custom range >= 65536. When using REST, the values + must be encoded as base64. + */ + map dest_custom_records = 11; + + // If set, circular payments to self are permitted. + bool allow_self_payment = 15; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + */ + repeated lnrpc.FeatureBit dest_features = 16; + + /* + The maximum number of partial payments that may be use to complete the full + amount. + */ + uint32 max_parts = 17; + + /* + If set, only the final payment update is streamed back. Intermediate updates + that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 18; + + /* + The largest payment split that should be attempted when making a payment if + splitting is necessary. Setting this value will effectively cause lnd to + split more aggressively, vs only when it thinks it needs to. Note that this + value is in milli-satoshis. + */ + uint64 max_shard_size_msat = 21; + + /* + If set, an AMP-payment will be attempted. + */ + bool amp = 22; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 23; +} + +message TrackPaymentRequest { + // The hash of the payment to look up. + bytes payment_hash = 1; + + /* + If set, only the final payment update is streamed back. Intermediate updates + that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 2; +} + +message TrackPaymentsRequest { + /* + If set, only the final payment updates are streamed back. Intermediate + updates that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 1; +} + +message RouteFeeRequest { + /* + The destination once wishes to obtain a routing fee quote to. + */ + bytes dest = 1; + + /* + The amount one wishes to send to the target destination. + */ + int64 amt_sat = 2; +} + +message RouteFeeResponse { + /* + A lower bound of the estimated fee to the target destination within the + network, expressed in milli-satoshis. + */ + int64 routing_fee_msat = 1; + + /* + An estimate of the worst case time delay that can occur. Note that callers + will still need to factor in the final CLTV delta of the last hop into this + value. + */ + int64 time_lock_delay = 2; +} + +message SendToRouteRequest { + // The payment hash to use for the HTLC. + bytes payment_hash = 1; + + // Route that should be used to attempt to complete the payment. + lnrpc.Route route = 2; + + /* + Whether the payment should be marked as failed when a temporary error is + returned from the given route. Set it to true so the payment won't be + failed unless a terminal error is occurred, such as payment timeout, no + routes, incorrect payment details, or insufficient funds. + */ + bool skip_temp_err = 3; +} + +message SendToRouteResponse { + // The preimage obtained by making the payment. + bytes preimage = 1; + + // The failure message in case the payment failed. + lnrpc.Failure failure = 2; +} + +message ResetMissionControlRequest { +} + +message ResetMissionControlResponse { +} + +message QueryMissionControlRequest { +} + +// QueryMissionControlResponse contains mission control state. +message QueryMissionControlResponse { + reserved 1; + + // Node pair-level mission control state. + repeated PairHistory pairs = 2; +} + +message XImportMissionControlRequest { + // Node pair-level mission control state to be imported. + repeated PairHistory pairs = 1; + + // Whether to force override MC pair history. Note that even with force + // override the failure pair is imported before the success pair and both + // still clamp existing failure/success amounts. + bool force = 2; +} + +message XImportMissionControlResponse { +} + +// PairHistory contains the mission control state for a particular node pair. +message PairHistory { + // The source node pubkey of the pair. + bytes node_from = 1; + + // The destination node pubkey of the pair. + bytes node_to = 2; + + reserved 3, 4, 5, 6; + + PairData history = 7; +} + +message PairData { + // Time of last failure. + int64 fail_time = 1; + + /* + Lowest amount that failed to forward rounded to whole sats. This may be + set to zero if the failure is independent of amount. + */ + int64 fail_amt_sat = 2; + + /* + Lowest amount that failed to forward in millisats. This may be + set to zero if the failure is independent of amount. + */ + int64 fail_amt_msat = 4; + + reserved 3; + + // Time of last success. + int64 success_time = 5; + + // Highest amount that we could successfully forward rounded to whole sats. + int64 success_amt_sat = 6; + + // Highest amount that we could successfully forward in millisats. + int64 success_amt_msat = 7; +} + +message GetMissionControlConfigRequest { +} + +message GetMissionControlConfigResponse { + /* + Mission control's currently active config. + */ + MissionControlConfig config = 1; +} + +message SetMissionControlConfigRequest { + /* + The config to set for mission control. Note that all values *must* be set, + because the full config will be applied. + */ + MissionControlConfig config = 1; +} + +message SetMissionControlConfigResponse { +} + +message MissionControlConfig { + /* + Deprecated, use AprioriParameters. The amount of time mission control will + take to restore a penalized node or channel back to 50% success probability, + expressed in seconds. Setting this value to a higher value will penalize + failures for longer, making mission control less likely to route through + nodes and channels that we have previously recorded failures for. + */ + uint64 half_life_seconds = 1 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The probability of success mission + control should assign to hop in a route where it has no other information + available. Higher values will make mission control more willing to try hops + that we have no information about, lower values will discourage trying these + hops. + */ + float hop_probability = 2 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The importance that mission control + should place on historical results, expressed as a value in [0;1]. Setting + this value to 1 will ignore all historical payments and just use the hop + probability to assess the probability of success for each hop. A zero value + ignores hop probability completely and relies entirely on historical + results, unless none are available. + */ + float weight = 3 [deprecated = true]; + + /* + The maximum number of payment results that mission control will store. + */ + uint32 maximum_payment_results = 4; + + /* + The minimum time that must have passed since the previously recorded failure + before we raise the failure amount. + */ + uint64 minimum_failure_relax_interval = 5; + + enum ProbabilityModel { + APRIORI = 0; + BIMODAL = 1; + } + + /* + ProbabilityModel defines which probability estimator should be used in + pathfinding. Note that the bimodal estimator is experimental. + */ + ProbabilityModel model = 6; + + /* + EstimatorConfig is populated dependent on the estimator type. + */ + oneof EstimatorConfig { + AprioriParameters apriori = 7; + BimodalParameters bimodal = 8; + } +} + +message BimodalParameters { + /* + NodeWeight defines how strongly other previous forwardings on channels of a + router should be taken into account when computing a channel's probability + to route. The allowed values are in the range [0, 1], where a value of 0 + means that only direct information about a channel is taken into account. + */ + double node_weight = 1; + + /* + ScaleMsat describes the scale over which channels statistically have some + liquidity left. The value determines how quickly the bimodal distribution + drops off from the edges of a channel. A larger value (compared to typical + channel capacities) means that the drop off is slow and that channel + balances are distributed more uniformly. A small value leads to the + assumption of very unbalanced channels. + */ + uint64 scale_msat = 2; + + /* + DecayTime describes the information decay of knowledge about previous + successes and failures in channels. The smaller the decay time, the quicker + we forget about past forwardings. + */ + uint64 decay_time = 3; +} + +message AprioriParameters { + /* + The amount of time mission control will take to restore a penalized node + or channel back to 50% success probability, expressed in seconds. Setting + this value to a higher value will penalize failures for longer, making + mission control less likely to route through nodes and channels that we + have previously recorded failures for. + */ + uint64 half_life_seconds = 1; + + /* + The probability of success mission control should assign to hop in a route + where it has no other information available. Higher values will make mission + control more willing to try hops that we have no information about, lower + values will discourage trying these hops. + */ + double hop_probability = 2; + + /* + The importance that mission control should place on historical results, + expressed as a value in [0;1]. Setting this value to 1 will ignore all + historical payments and just use the hop probability to assess the + probability of success for each hop. A zero value ignores hop probability + completely and relies entirely on historical results, unless none are + available. + */ + double weight = 3; + + /* + The fraction of a channel's capacity that we consider to have liquidity. For + amounts that come close to or exceed the fraction, an additional penalty is + applied. A value of 1.0 disables the capacity factor. Allowed values are in + [0.75, 1.0]. + */ + double capacity_fraction = 4; +} + +message QueryProbabilityRequest { + // The source node pubkey of the pair. + bytes from_node = 1; + + // The destination node pubkey of the pair. + bytes to_node = 2; + + // The amount for which to calculate a probability. + int64 amt_msat = 3; +} + +message QueryProbabilityResponse { + // The success probability for the requested pair. + double probability = 1; + + // The historical data for the requested pair. + PairData history = 2; +} + +message BuildRouteRequest { + /* + The amount to send expressed in msat. If set to zero, the minimum routable + amount is used. + */ + int64 amt_msat = 1; + + /* + CLTV delta from the current height that should be used for the timelock + of the final hop + */ + int32 final_cltv_delta = 2; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 3 [jstype = JS_STRING]; + + /* + A list of hops that defines the route. This does not include the source hop + pubkey. + */ + repeated bytes hop_pubkeys = 4; + + // An optional payment addr to be included within the last hop of the route. + bytes payment_addr = 5; +} + +message BuildRouteResponse { + /* + Fully specified route that can be used to execute the payment. + */ + lnrpc.Route route = 1; +} + +message SubscribeHtlcEventsRequest { +} + +/* +HtlcEvent contains the htlc event that was processed. These are served on a +best-effort basis; events are not persisted, delivery is not guaranteed +(in the event of a crash in the switch, forward events may be lost) and +some events may be replayed upon restart. Events consumed from this package +should be de-duplicated by the htlc's unique combination of incoming and +outgoing channel id and htlc id. [EXPERIMENTAL] +*/ +message HtlcEvent { + /* + The short channel id that the incoming htlc arrived at our node on. This + value is zero for sends. + */ + uint64 incoming_channel_id = 1; + + /* + The short channel id that the outgoing htlc left our node on. This value + is zero for receives. + */ + uint64 outgoing_channel_id = 2; + + /* + Incoming id is the index of the incoming htlc in the incoming channel. + This value is zero for sends. + */ + uint64 incoming_htlc_id = 3; + + /* + Outgoing id is the index of the outgoing htlc in the outgoing channel. + This value is zero for receives. + */ + uint64 outgoing_htlc_id = 4; + + /* + The time in unix nanoseconds that the event occurred. + */ + uint64 timestamp_ns = 5; + + enum EventType { + UNKNOWN = 0; + SEND = 1; + RECEIVE = 2; + FORWARD = 3; + } + + /* + The event type indicates whether the htlc was part of a send, receive or + forward. + */ + EventType event_type = 6; + + oneof event { + ForwardEvent forward_event = 7; + ForwardFailEvent forward_fail_event = 8; + SettleEvent settle_event = 9; + LinkFailEvent link_fail_event = 10; + SubscribedEvent subscribed_event = 11; + FinalHtlcEvent final_htlc_event = 12; + } +} + +message HtlcInfo { + // The timelock on the incoming htlc. + uint32 incoming_timelock = 1; + + // The timelock on the outgoing htlc. + uint32 outgoing_timelock = 2; + + // The amount of the incoming htlc. + uint64 incoming_amt_msat = 3; + + // The amount of the outgoing htlc. + uint64 outgoing_amt_msat = 4; +} + +message ForwardEvent { + // Info contains details about the htlc that was forwarded. + HtlcInfo info = 1; +} + +message ForwardFailEvent { +} + +message SettleEvent { + // The revealed preimage. + bytes preimage = 1; +} + +message FinalHtlcEvent { + bool settled = 1; + bool offchain = 2; +} + +message SubscribedEvent { +} + +message LinkFailEvent { + // Info contains details about the htlc that we failed. + HtlcInfo info = 1; + + // FailureCode is the BOLT error code for the failure. + lnrpc.Failure.FailureCode wire_failure = 2; + + /* + FailureDetail provides additional information about the reason for the + failure. This detail enriches the information provided by the wire message + and may be 'no detail' if the wire message requires no additional metadata. + */ + FailureDetail failure_detail = 3; + + // A string representation of the link failure. + string failure_string = 4; +} + +enum FailureDetail { + UNKNOWN = 0; + NO_DETAIL = 1; + ONION_DECODE = 2; + LINK_NOT_ELIGIBLE = 3; + ON_CHAIN_TIMEOUT = 4; + HTLC_EXCEEDS_MAX = 5; + INSUFFICIENT_BALANCE = 6; + INCOMPLETE_FORWARD = 7; + HTLC_ADD_FAILED = 8; + FORWARDS_DISABLED = 9; + INVOICE_CANCELED = 10; + INVOICE_UNDERPAID = 11; + INVOICE_EXPIRY_TOO_SOON = 12; + INVOICE_NOT_OPEN = 13; + MPP_INVOICE_TIMEOUT = 14; + ADDRESS_MISMATCH = 15; + SET_TOTAL_MISMATCH = 16; + SET_TOTAL_TOO_LOW = 17; + SET_OVERPAID = 18; + UNKNOWN_INVOICE = 19; + INVALID_KEYSEND = 20; + MPP_IN_PROGRESS = 21; + CIRCULAR_ROUTE = 22; +} + +enum PaymentState { + /* + Payment is still in flight. + */ + IN_FLIGHT = 0; + + /* + Payment completed successfully. + */ + SUCCEEDED = 1; + + /* + There are more routes to try, but the payment timeout was exceeded. + */ + FAILED_TIMEOUT = 2; + + /* + All possible routes were tried and failed permanently. Or were no + routes to the destination at all. + */ + FAILED_NO_ROUTE = 3; + + /* + A non-recoverable error has occurred. + */ + FAILED_ERROR = 4; + + /* + Payment details incorrect (unknown hash, invalid amt or + invalid final cltv delta) + */ + FAILED_INCORRECT_PAYMENT_DETAILS = 5; + + /* + Insufficient local balance. + */ + FAILED_INSUFFICIENT_BALANCE = 6; +} + +message PaymentStatus { + // Current state the payment is in. + PaymentState state = 1; + + /* + The pre-image of the payment when state is SUCCEEDED. + */ + bytes preimage = 2; + + reserved 3; + + /* + The HTLCs made in attempt to settle the payment [EXPERIMENTAL]. + */ + repeated lnrpc.HTLCAttempt htlcs = 4; +} + +message CircuitKey { + /// The id of the channel that the is part of this circuit. + uint64 chan_id = 1; + + /// The index of the incoming htlc in the incoming channel. + uint64 htlc_id = 2; +} + +message ForwardHtlcInterceptRequest { + /* + The key of this forwarded htlc. It defines the incoming channel id and + the index in this channel. + */ + CircuitKey incoming_circuit_key = 1; + + // The incoming htlc amount. + uint64 incoming_amount_msat = 5; + + // The incoming htlc expiry. + uint32 incoming_expiry = 6; + + /* + The htlc payment hash. This value is not guaranteed to be unique per + request. + */ + bytes payment_hash = 2; + + // The requested outgoing channel id for this forwarded htlc. Because of + // non-strict forwarding, this isn't necessarily the channel over which the + // packet will be forwarded eventually. A different channel to the same peer + // may be selected as well. + uint64 outgoing_requested_chan_id = 7; + + // The outgoing htlc amount. + uint64 outgoing_amount_msat = 3; + + // The outgoing htlc expiry. + uint32 outgoing_expiry = 4; + + // Any custom records that were present in the payload. + map custom_records = 8; + + // The onion blob for the next hop + bytes onion_blob = 9; + + // The block height at which this htlc will be auto-failed to prevent the + // channel from force-closing. + int32 auto_fail_height = 10; +} + +/** +ForwardHtlcInterceptResponse enables the caller to resolve a previously hold +forward. The caller can choose either to: +- `Resume`: Execute the default behavior (usually forward). +- `Reject`: Fail the htlc backwards. +- `Settle`: Settle this htlc with a given preimage. +*/ +message ForwardHtlcInterceptResponse { + /** + The key of this forwarded htlc. It defines the incoming channel id and + the index in this channel. + */ + CircuitKey incoming_circuit_key = 1; + + // The resolve action for this intercepted htlc. + ResolveHoldForwardAction action = 2; + + // The preimage in case the resolve action is Settle. + bytes preimage = 3; + + // Encrypted failure message in case the resolve action is Fail. + // + // If failure_message is specified, the failure_code field must be set + // to zero. + bytes failure_message = 4; + + // Return the specified failure code in case the resolve action is Fail. The + // message data fields are populated automatically. + // + // If a non-zero failure_code is specified, failure_message must not be set. + // + // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the + // default value for this field. + lnrpc.Failure.FailureCode failure_code = 5; +} + +enum ResolveHoldForwardAction { + SETTLE = 0; + FAIL = 1; + RESUME = 2; +} + +message UpdateChanStatusRequest { + lnrpc.ChannelPoint chan_point = 1; + + ChanStatusAction action = 2; +} + +enum ChanStatusAction { + ENABLE = 0; + DISABLE = 1; + AUTO = 2; +} + +message UpdateChanStatusResponse { +} diff --git a/proto/signrpc/signer.proto b/proto/signrpc/signer.proto new file mode 100644 index 000000000..f704000f9 --- /dev/null +++ b/proto/signrpc/signer.proto @@ -0,0 +1,697 @@ +syntax = "proto3"; + +package signrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/signrpc"; + +// Signer is a service that gives access to the signing functionality of the +// daemon's wallet. +service Signer { + /* + SignOutputRaw is a method that can be used to generated a signature for a + set of inputs/outputs to a transaction. Each request specifies details + concerning how the outputs should be signed, which keys they should be + signed with, and also any optional tweaks. The return value is a fixed + 64-byte signature (the same format as we use on the wire in Lightning). + + If we are unable to sign using the specified keys, then an error will be + returned. + */ + rpc SignOutputRaw (SignReq) returns (SignResp); + + /* + ComputeInputScript generates a complete InputIndex for the passed + transaction with the signature as defined within the passed SignDescriptor. + This method should be capable of generating the proper input script for both + regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh + output. + + Note that when using this method to sign inputs belonging to the wallet, + the only items of the SignDescriptor that need to be populated are pkScript + in the TxOut field, the value in that same field, and finally the input + index. + */ + rpc ComputeInputScript (SignReq) returns (InputScriptResp); + + /* + SignMessage signs a message with the key specified in the key locator. The + returned signature is fixed-size LN wire format encoded. + + The main difference to SignMessage in the main RPC is that a specific key is + used to sign the message instead of the node identity private key. + */ + rpc SignMessage (SignMessageReq) returns (SignMessageResp); + + /* + VerifyMessage verifies a signature over a message using the public key + provided. The signature must be fixed-size LN wire format encoded. + + The main difference to VerifyMessage in the main RPC is that the public key + used to sign the message does not have to be a node known to the network. + */ + rpc VerifyMessage (VerifyMessageReq) returns (VerifyMessageResp); + + /* + DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key + derivation between the ephemeral public key in the request and the node's + key specified in the key_desc parameter. Either a key locator or a raw + public key is expected in the key_desc, if neither is supplied, defaults to + the node's identity private key: + P_shared = privKeyNode * ephemeralPubkey + The resulting shared public key is serialized in the compressed format and + hashed with sha256, resulting in the final key length of 256bit. + */ + rpc DeriveSharedKey (SharedKeyRequest) returns (SharedKeyResponse); + + /* + MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used + to calculate the combined MuSig2 public key from a list of all participating + signers' public keys. This RPC is completely stateless and deterministic and + does not create any signing session. It can be used to determine the Taproot + public key that should be put in an on-chain output once all public keys are + known. A signing session is only needed later when that output should be + _spent_ again. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineKeys (MuSig2CombineKeysRequest) + returns (MuSig2CombineKeysResponse); + + /* + MuSig2CreateSession (experimental!) creates a new MuSig2 signing session + using the local key identified by the key locator. The complete list of all + public keys of all signing parties must be provided, including the public + key of the local signing key. If nonces of other parties are already known, + they can be submitted as well to reduce the number of RPC calls necessary + later on. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CreateSession (MuSig2SessionRequest) + returns (MuSig2SessionResponse); + + /* + MuSig2RegisterNonces (experimental!) registers one or more public nonces of + other signing participants for a session identified by its ID. This RPC can + be called multiple times until all nonces are registered. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2RegisterNonces (MuSig2RegisterNoncesRequest) + returns (MuSig2RegisterNoncesResponse); + + /* + MuSig2Sign (experimental!) creates a partial signature using the local + signing key that was specified when the session was created. This can only + be called when all public nonces of all participants are known and have been + registered with the session. If this node isn't responsible for combining + all the partial signatures, then the cleanup flag should be set, indicating + that the session can be removed from memory once the signature was produced. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Sign (MuSig2SignRequest) returns (MuSig2SignResponse); + + /* + MuSig2CombineSig (experimental!) combines the given partial signature(s) + with the local one, if it already exists. Once a partial signature of all + participants is registered, the final signature will be combined and + returned. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineSig (MuSig2CombineSigRequest) + returns (MuSig2CombineSigResponse); + + /* + MuSig2Cleanup (experimental!) allows a caller to clean up a session early in + cases where it's obvious that the signing session won't succeed and the + resources can be released. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Cleanup (MuSig2CleanupRequest) returns (MuSig2CleanupResponse); +} + +message KeyLocator { + // The family of key being identified. + int32 key_family = 1; + + // The precise index of the key being identified. + int32 key_index = 2; +} + +message KeyDescriptor { + /* + The raw bytes of the public key in the key pair being identified. Either + this or the KeyLocator must be specified. + */ + bytes raw_key_bytes = 1; + + /* + The key locator that identifies which private key to use for signing. + Either this or the raw bytes of the target public key must be specified. + */ + KeyLocator key_loc = 2; +} + +message TxOut { + // The value of the output being spent. + int64 value = 1; + + // The script of the output being spent. + bytes pk_script = 2; +} + +enum SignMethod { + /* + Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be + signed. + */ + SIGN_METHOD_WITNESS_V0 = 0; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using the + BIP0086 method (commit to internal key only). + */ + SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 = 1; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using a given + taproot hash to commit to in addition to the internal key. + */ + SIGN_METHOD_TAPROOT_KEY_SPEND = 2; + + /* + Specifies that a SegWit v1 (p2tr) input should be spent using the script + path and that a specific leaf script should be signed for. + */ + SIGN_METHOD_TAPROOT_SCRIPT_SPEND = 3; +} + +message SignDescriptor { + /* + A descriptor that precisely describes *which* key to use for signing. This + may provide the raw public key directly, or require the Signer to re-derive + the key according to the populated derivation path. + + Note that if the key descriptor was obtained through walletrpc.DeriveKey, + then the key locator MUST always be provided, since the derived keys are not + persisted unlike with DeriveNextKey. + */ + KeyDescriptor key_desc = 1; + + /* + A scalar value that will be added to the private key corresponding to the + above public key to obtain the private key to be used to sign this input. + This value is typically derived via the following computation: + + * derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N + */ + bytes single_tweak = 2; + + /* + A private key that will be used in combination with its corresponding + private key to derive the private key that is to be used to sign the target + input. Within the Lightning protocol, this value is typically the + commitment secret from a previously revoked commitment transaction. This + value is in combination with two hash values, and the original private key + to derive the private key to be used when signing. + + * k = (privKey*sha256(pubKey || tweakPub) + + tweakPriv*sha256(tweakPub || pubKey)) mod N + */ + bytes double_tweak = 3; + + /* + The 32 byte input to the taproot tweak derivation that is used to derive + the output key from an internal key: outputKey = internalKey + + tagged_hash("tapTweak", internalKey || tapTweak). + + When doing a BIP 86 spend, this field can be an empty byte slice. + + When doing a normal key path spend, with the output key committing to an + actual script root, then this field should be: the tapscript root hash. + */ + bytes tap_tweak = 10; + + /* + The full script required to properly redeem the output. This field will + only be populated if a p2tr, p2wsh or a p2sh output is being signed. If a + taproot script path spend is being attempted, then this should be the raw + leaf script. + */ + bytes witness_script = 4; + + /* + A description of the output being spent. The value and script MUST be + provided. + */ + TxOut output = 5; + + /* + The target sighash type that should be used when generating the final + sighash, and signature. + */ + uint32 sighash = 7; + + /* + The target input within the transaction that should be signed. + */ + int32 input_index = 8; + + /* + The sign method specifies how the input should be signed. Depending on the + method, either the tap_tweak, witness_script or both need to be specified. + Defaults to SegWit v0 signing to be backward compatible with older RPC + clients. + */ + SignMethod sign_method = 9; +} + +message SignReq { + // The raw bytes of the transaction to be signed. + bytes raw_tx_bytes = 1; + + // A set of sign descriptors, for each input to be signed. + repeated SignDescriptor sign_descs = 2; + + /* + The full list of UTXO information for each of the inputs being spent. This + is required when spending one or more taproot (SegWit v1) outputs. + */ + repeated TxOut prev_outputs = 3; +} + +message SignResp { + /* + A set of signatures realized in a fixed 64-byte format ordered in ascending + input order. + */ + repeated bytes raw_sigs = 1; +} + +message InputScript { + // The serializes witness stack for the specified input. + repeated bytes witness = 1; + + /* + The optional sig script for the specified witness that will only be set if + the input specified is a nested p2sh witness program. + */ + bytes sig_script = 2; +} + +message InputScriptResp { + // The set of fully valid input scripts requested. + repeated InputScript input_scripts = 1; +} + +message SignMessageReq { + /* + The message to be signed. When using REST, this field must be encoded as + base64. + */ + bytes msg = 1; + + // The key locator that identifies which key to use for signing. + KeyLocator key_loc = 2; + + // Double-SHA256 hash instead of just the default single round. + bool double_hash = 3; + + /* + Use the compact (pubkey recoverable) format instead of the raw lnwire + format. This option cannot be used with Schnorr signatures. + */ + bool compact_sig = 4; + + /* + Use Schnorr signature. This option cannot be used with compact format. + */ + bool schnorr_sig = 5; + + /* + The optional Taproot tweak bytes to apply to the private key before creating + a Schnorr signature. The private key is tweaked as described in BIP-341: + privKey + h_tapTweak(internalKey || tapTweak) + */ + bytes schnorr_sig_tap_tweak = 6; +} +message SignMessageResp { + /* + The signature for the given message in the fixed-size LN wire format. + */ + bytes signature = 1; +} + +message VerifyMessageReq { + // The message over which the signature is to be verified. When using + // REST, this field must be encoded as base64. + bytes msg = 1; + + /* + The fixed-size LN wire encoded signature to be verified over the given + message. When using REST, this field must be encoded as base64. + */ + bytes signature = 2; + + /* + The public key the signature has to be valid for. When using REST, this + field must be encoded as base64. If the is_schnorr_sig option is true, then + the public key is expected to be in the 32-byte x-only serialization + according to BIP-340. + */ + bytes pubkey = 3; + + /* + Specifies if the signature is a Schnorr signature. + */ + bool is_schnorr_sig = 4; +} + +message VerifyMessageResp { + // Whether the signature was valid over the given message. + bool valid = 1; +} + +message SharedKeyRequest { + // The ephemeral public key to use for the DH key derivation. + bytes ephemeral_pubkey = 1; + + /* + Deprecated. The optional key locator of the local key that should be used. + If this parameter is not set then the node's identity private key will be + used. + */ + KeyLocator key_loc = 2 [deprecated = true]; + + /* + A key descriptor describes the key used for performing ECDH. Either a key + locator or a raw public key is expected, if neither is supplied, defaults to + the node's identity private key. + */ + KeyDescriptor key_desc = 3; +} + +message SharedKeyResponse { + // The shared public key, hashed with sha256. + bytes shared_key = 1; +} + +message TweakDesc { + /* + Tweak is the 32-byte value that will modify the public key. + */ + bytes tweak = 1; + + /* + Specifies if the target key should be converted to an x-only public key + before tweaking. If true, then the public key will be mapped to an x-only + key before the tweaking operation is applied. + */ + bool is_x_only = 2; +} + +message TaprootTweakDesc { + /* + The root hash of the tapscript tree if a script path is committed to. If + the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086 + key spend only), then this needs to be empty and the key_spend_only field + below must be set to true. This is required because gRPC cannot + differentiate between a zero-size byte slice and a nil byte slice (both + would be serialized the same way). So the extra boolean is required. + */ + bytes script_root = 1; + + /* + Indicates that the above script_root is expected to be empty because this + is a BIP-0086 key spend only commitment where only the internal key is + committed to instead of also including a script root hash. + */ + bool key_spend_only = 2; +} + +enum MuSig2Version { + /* + The default value on the RPC is zero for enums so we need to represent an + invalid/undefined version by default to make sure clients upgrade their + software to set the version explicitly. + */ + MUSIG2_VERSION_UNDEFINED = 0; + + /* + The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the + version v0.4.0 of the MuSig2 BIP draft. + */ + MUSIG2_VERSION_V040 = 1; + + /* + The current version of MuSig2 which corresponds to the version v1.0.0rc2 of + the MuSig2 BIP draft. + */ + MUSIG2_VERSION_V100RC2 = 2; +} + +message MuSig2CombineKeysRequest { + /* + A list of all public keys (serialized in 32-byte x-only format for v0.4.0 + and 33-byte compressed format for v1.0.0rc2!) participating in the signing + session. The list will always be sorted lexicographically internally. This + must include the local key which is described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 1; + + /* + A series of optional generic tweaks to be applied to the the aggregated + public key. + */ + repeated TweakDesc tweaks = 2; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 3; + + /* + The mandatory version of the MuSig2 BIP draft to use. This is necessary to + differentiate between the changes that were made to the BIP while this + experimental RPC was already released. Some of those changes affect how the + combined key and nonces are created. + */ + MuSig2Version version = 4; +} + +message MuSig2CombineKeysResponse { + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 1; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 2; + + /* + The version of the MuSig2 BIP that was used to combine the keys. + */ + MuSig2Version version = 4; +} + +message MuSig2SessionRequest { + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 1; + + /* + A list of all public keys (serialized in 32-byte x-only format for v0.4.0 + and 33-byte compressed format for v1.0.0rc2!) participating in the signing + session. The list will always be sorted lexicographically internally. This + must include the local key which is described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 2; + + /* + An optional list of all public nonces of other signing participants that + might already be known. + */ + repeated bytes other_signer_public_nonces = 3; + + /* + A series of optional generic tweaks to be applied to the the aggregated + public key. + */ + repeated TweakDesc tweaks = 4; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 5; + + /* + The mandatory version of the MuSig2 BIP draft to use. This is necessary to + differentiate between the changes that were made to the BIP while this + experimental RPC was already released. Some of those changes affect how the + combined key and nonces are created. + */ + MuSig2Version version = 6; + + /* + A set of pre generated secret local nonces to use in the musig2 session. + This field is optional. This can be useful for protocols that need to send + nonces ahead of time before the set of signer keys are known. This value + MUST be 97 bytes and be the concatenation of two CSPRNG generated 32 byte + values and local public key used for signing as specified in the key_loc + field. + */ + bytes pregenerated_local_nonce = 7; +} + +message MuSig2SessionResponse { + /* + The unique ID that represents this signing session. A session can be used + for producing a signature a single time. If the signing fails for any + reason, a new session with the same participants needs to be created. + */ + bytes session_id = 1; + + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 2; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 3; + + /* + The two public nonces the local signer uses, combined into a single value + of 66 bytes. Can be split into the two 33-byte points to get the individual + nonces. + */ + bytes local_public_nonces = 4; + + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 5; + + /* + The version of the MuSig2 BIP that was used to create the session. + */ + MuSig2Version version = 6; +} + +message MuSig2RegisterNoncesRequest { + /* + The unique ID of the signing session those nonces should be registered with. + */ + bytes session_id = 1; + + /* + A list of all public nonces of other signing participants that should be + registered. + */ + repeated bytes other_signer_public_nonces = 3; +} + +message MuSig2RegisterNoncesResponse { + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 1; +} + +message MuSig2SignRequest { + /* + The unique ID of the signing session to use for signing. + */ + bytes session_id = 1; + + /* + The 32-byte SHA256 digest of the message to sign. + */ + bytes message_digest = 2; + + /* + Cleanup indicates that after signing, the session state can be cleaned up, + since another participant is going to be responsible for combining the + partial signatures. + */ + bool cleanup = 3; +} + +message MuSig2SignResponse { + /* + The partial signature created by the local signer. + */ + bytes local_partial_signature = 1; +} + +message MuSig2CombineSigRequest { + /* + The unique ID of the signing session to combine the signatures for. + */ + bytes session_id = 1; + + /* + The list of all other participants' partial signatures to add to the current + session. + */ + repeated bytes other_partial_signatures = 2; +} + +message MuSig2CombineSigResponse { + /* + Indicates whether all partial signatures required to create a final, full + signature are known yet. If this is true, then the final_signature field is + set, otherwise it is empty. + */ + bool have_all_signatures = 1; + + /* + The final, full signature that is valid for the combined public key. + */ + bytes final_signature = 2; +} + +message MuSig2CleanupRequest { + /* + The unique ID of the signing session that should be removed/cleaned up. + */ + bytes session_id = 1; +} + +message MuSig2CleanupResponse { +} diff --git a/proto/stateservice.proto b/proto/stateservice.proto new file mode 100644 index 000000000..97a78d31a --- /dev/null +++ b/proto/stateservice.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package lnrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// State service is a always running service that exposes the current state of +// the wallet and RPC server. +service State { + // SubscribeState subscribes to the state of the wallet. The current wallet + // state will always be delivered immediately. + rpc SubscribeState (SubscribeStateRequest) + returns (stream SubscribeStateResponse); + + // GetState returns the current wallet state without streaming further + // changes. + rpc GetState (GetStateRequest) returns (GetStateResponse); +} + +enum WalletState { + // NON_EXISTING means that the wallet has not yet been initialized. + NON_EXISTING = 0; + + // LOCKED means that the wallet is locked and requires a password to unlock. + LOCKED = 1; + + // UNLOCKED means that the wallet was unlocked successfully, but RPC server + // isn't ready. + UNLOCKED = 2; + + // RPC_ACTIVE means that the lnd server is active but not fully ready for + // calls. + RPC_ACTIVE = 3; + + // SERVER_ACTIVE means that the lnd server is ready to accept calls. + SERVER_ACTIVE = 4; + + // WAITING_TO_START means that node is waiting to become the leader in a + // cluster and is not started yet. + WAITING_TO_START = 255; +} + +message SubscribeStateRequest { +} + +message SubscribeStateResponse { + WalletState state = 1; +} + +message GetStateRequest { +} + +message GetStateResponse { + WalletState state = 1; +} diff --git a/proto/taprootassets.proto b/proto/taprootassets.proto new file mode 100644 index 000000000..042d5dcb3 --- /dev/null +++ b/proto/taprootassets.proto @@ -0,0 +1,1025 @@ +syntax = "proto3"; + +package taprpc; + +option go_package = "github.com/lightninglabs/taproot-assets/taprpc"; + +service TaprootAssets { + /* tapcli: `assets list` + ListAssets lists the set of assets owned by the target daemon. + */ + rpc ListAssets (ListAssetRequest) returns (ListAssetResponse); + + /* tapcli: `assets utxos` + ListUtxos lists the UTXOs managed by the target daemon, and the assets they + hold. + */ + rpc ListUtxos (ListUtxosRequest) returns (ListUtxosResponse); + + /* tapcli: `assets groups` + ListGroups lists the asset groups known to the target daemon, and the assets + held in each group. + */ + rpc ListGroups (ListGroupsRequest) returns (ListGroupsResponse); + + /* tapcli: `assets balance` + ListBalances lists asset balances + */ + rpc ListBalances (ListBalancesRequest) returns (ListBalancesResponse); + + /* tapcli: `assets transfers` + ListTransfers lists outbound asset transfers tracked by the target daemon. + */ + rpc ListTransfers (ListTransfersRequest) returns (ListTransfersResponse); + + /* tapcli: `stop` + StopDaemon will send a shutdown request to the interrupt handler, triggering + a graceful shutdown of the daemon. + */ + rpc StopDaemon (StopRequest) returns (StopResponse); + + /* tapcli: `debuglevel` + DebugLevel allows a caller to programmatically set the logging verbosity of + tapd. The logging can be targeted according to a coarse daemon-wide logging + level, or in a granular fashion to specify the logging for a target + sub-system. + */ + rpc DebugLevel (DebugLevelRequest) returns (DebugLevelResponse); + + /* tapcli: `addrs query` + QueryAddrs queries the set of Taproot Asset addresses stored in the + database. + */ + rpc QueryAddrs (QueryAddrRequest) returns (QueryAddrResponse); + + /* tapcli: `addrs new` + NewAddr makes a new address from the set of request params. + */ + rpc NewAddr (NewAddrRequest) returns (Addr); + + /* tapcli: `addrs decode` + DecodeAddr decode a Taproot Asset address into a partial asset message that + represents the asset it wants to receive. + */ + rpc DecodeAddr (DecodeAddrRequest) returns (Addr); + + /* tapcli: `addrs receives` + List all receives for incoming asset transfers for addresses that were + created previously. + */ + rpc AddrReceives (AddrReceivesRequest) returns (AddrReceivesResponse); + + /* tapcli: `proofs verify` + VerifyProof attempts to verify a given proof file that claims to be anchored + at the specified genesis point. + */ + rpc VerifyProof (ProofFile) returns (VerifyProofResponse); + + /* tapcli: `proofs decode` + DecodeProof attempts to decode a given proof file into human readable + format. + */ + rpc DecodeProof (DecodeProofRequest) returns (DecodeProofResponse); + + /* tapcli: `proofs export` + ExportProof exports the latest raw proof file anchored at the specified + script_key. + */ + rpc ExportProof (ExportProofRequest) returns (ProofFile); + + /* tapcli: `assets send` + SendAsset uses one or multiple passed Taproot Asset address(es) to attempt + to complete an asset send. The method returns information w.r.t the on chain + send, as well as the proof file information the receiver needs to fully + receive the asset. + */ + rpc SendAsset (SendAssetRequest) returns (SendAssetResponse); + + /* tapcli: `assets burn` + BurnAsset burns the given number of units of a given asset by sending them + to a provably un-spendable script key. Burning means irrevocably destroying + a certain number of assets, reducing the total supply of the asset. Because + burning is such a destructive and non-reversible operation, some specific + values need to be set in the request to avoid accidental burns. + */ + rpc BurnAsset (BurnAssetRequest) returns (BurnAssetResponse); + + /* tapcli: `getinfo` + GetInfo returns the information for the node. + */ + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); + + /* + SubscribeSendAssetEventNtfns registers a subscription to the event + notification stream which relates to the asset sending process. + */ + rpc SubscribeSendAssetEventNtfns (SubscribeSendAssetEventNtfnsRequest) + returns (stream SendAssetEvent); + + /* + FetchAssetMeta allows a caller to fetch the reveal meta data for an asset + either by the asset ID for that asset, or a meta hash. + */ + rpc FetchAssetMeta (FetchAssetMetaRequest) returns (AssetMeta); +} + +enum AssetType { + /* + Indicates that an asset is capable of being split/merged, with each of the + units being fungible, even across a key asset ID boundary (assuming the + key group is the same). + */ + NORMAL = 0; + + /* + Indicates that an asset is a collectible, meaning that each of the other + items under the same key group are not fully fungible with each other. + Collectibles also cannot be split or merged. + */ + COLLECTIBLE = 1; +} + +enum AssetMetaType { + /* + Opaque is used for asset meta blobs that have no true structure and instead + should be interpreted as opaque blobs. + */ + META_TYPE_OPAQUE = 0; +} + +message AssetMeta { + /* + The raw data of the asset meta data. Based on the type below, this may be + structured data such as a text file or PDF. The size of the data is limited + to 1MiB. + */ + bytes data = 1; + + // The type of the asset meta data. + AssetMetaType type = 2; + + /* + The hash of the meta. This is the hash of the TLV serialization of the meta + itself. + */ + bytes meta_hash = 3; +} + +message ListAssetRequest { + bool with_witness = 1; + bool include_spent = 2; + bool include_leased = 3; +} + +message AnchorInfo { + // The transaction that anchors the Taproot Asset commitment where the asset + // resides. + bytes anchor_tx = 1; + + // The txid of the above transaction. + string anchor_txid = 2; + + // The block hash the contains the anchor transaction above. + string anchor_block_hash = 3; + + // The outpoint (txid:vout) that stores the Taproot Asset commitment. + string anchor_outpoint = 4; + + /* + The raw internal key that was used to create the anchor Taproot output key. + */ + bytes internal_key = 5; + + /* + The Taproot merkle root hash of the anchor output the asset was committed + to. If there is no Tapscript sibling, this is equal to the Taproot Asset + root commitment hash. + */ + bytes merkle_root = 6; + + /* + The serialized preimage of a Tapscript sibling, if there was one. If this + is empty, then the merkle_root hash is equal to the Taproot root hash of the + anchor output. + */ + bytes tapscript_sibling = 7; + + // The height of the block which contains the anchor transaction. + uint32 block_height = 8; +} + +message GenesisInfo { + // The first outpoint of the transaction that created the asset (txid:vout). + string genesis_point = 1; + + // The name of the asset. + string name = 2; + + // The hash of the meta data for this genesis asset. + bytes meta_hash = 3; + + // The asset ID that uniquely identifies the asset. + bytes asset_id = 4; + + /* + The index of the output that carries the unique Taproot Asset commitment in + the genesis transaction. + */ + uint32 output_index = 5; + + // The version of the Taproot Asset commitment that created this asset. + int32 version = 6; +} + +message AssetGroup { + // The raw group key which is a normal public key. + bytes raw_group_key = 1; + + /* + The tweaked group key, which is derived based on the genesis point and also + asset type. + */ + bytes tweaked_group_key = 2; + + /* + A witness that authorizes a specific asset to be part of the asset group + specified by the above key. + */ + bytes asset_witness = 3; + + // TODO(jhb): update to include tapscript_root +} + +message GroupKeyReveal { + // The raw group key which is a normal public key. + bytes raw_group_key = 1; + + // The tapscript root included in the tweaked group key, which may be empty. + bytes tapscript_root = 2; +} + +message GenesisReveal { + // The base genesis information in the genesis reveal. + GenesisInfo genesis_base_reveal = 1; + + // The asset type, not included in the base genesis info. + AssetType asset_type = 2; +} + +enum AssetVersion { + // ASSET_VERSION_V0 is the default asset version. This version will include + // the witness vector in the leaf for a tap commitment. + ASSET_VERSION_V0 = 0; + + // ASSET_VERSION_V1 is the asset version that leaves out the witness vector + // from the MS-SMT leaf encoding. + ASSET_VERSION_V1 = 1; +} + +message Asset { + // The version of the Taproot Asset. + AssetVersion version = 1; + + // The base genesis information of an asset. This information never changes. + GenesisInfo asset_genesis = 2; + + // The type of the asset. + AssetType asset_type = 3; + + // The total amount of the asset stored in this Taproot Asset UTXO. + uint64 amount = 4; + + // An optional locktime, as with Bitcoin transactions. + int32 lock_time = 5; + + // An optional relative lock time, same as Bitcoin transactions. + int32 relative_lock_time = 6; + + // The version of the script, only version 0 is defined at present. + int32 script_version = 7; + + // The script key of the asset, which can be spent under Taproot semantics. + bytes script_key = 9; + + // Indicates whether the script key is known to the wallet of the lnd node + // connected to the Taproot Asset daemon. + bool script_key_is_local = 10; + + // The information related to the key group of an asset (if it exists). + AssetGroup asset_group = 11; + + // Describes where in the chain the asset is currently anchored. + AnchorInfo chain_anchor = 12; + + repeated PrevWitness prev_witnesses = 13; + + // Indicates whether the asset has been spent. + bool is_spent = 14; + + // If the asset has been leased, this is the owner (application ID) of the + // lease. + bytes lease_owner = 15; + + // If the asset has been leased, this is the expiry of the lease as a Unix + // timestamp in seconds. + int64 lease_expiry = 16; + + // Indicates whether this transfer was an asset burn. If true, the number of + // assets in this output are destroyed and can no longer be spent. + bool is_burn = 17; +} + +message PrevWitness { + PrevInputAsset prev_id = 1; + + repeated bytes tx_witness = 2; + + SplitCommitment split_commitment = 3; +} + +message SplitCommitment { + Asset root_asset = 1; +} + +message ListAssetResponse { + repeated Asset assets = 1; +} + +message ListUtxosRequest { + bool include_leased = 1; +} + +message ManagedUtxo { + // The outpoint of the UTXO. + string out_point = 1; + + // The UTXO amount in satoshis. + int64 amt_sat = 2; + + // The internal key used for the on-chain output. + bytes internal_key = 3; + + // The Taproot Asset root commitment hash. + bytes taproot_asset_root = 4; + + /* + The Taproot merkle root hash committed to by the outpoint of this UTXO. + If there is no Tapscript sibling, this is equal to the Taproot Asset root + commitment hash. + */ + bytes merkle_root = 5; + + // The assets held at this UTXO. + repeated Asset assets = 6; +} + +message ListUtxosResponse { + // The set of UTXOs managed by the daemon. + map managed_utxos = 1; +} + +message ListGroupsRequest { +} + +message AssetHumanReadable { + // The ID of the asset. + bytes id = 1; + + // The amount of the asset. + uint64 amount = 2; + + // An optional locktime, as with Bitcoin transactions. + int32 lock_time = 3; + + // An optional relative locktime, as with Bitcoin transactions. + int32 relative_lock_time = 4; + + // The name of the asset. + string tag = 5; + + // The metadata hash of the asset. + bytes meta_hash = 6; + + // The type of the asset. + AssetType type = 7; + + // The version of the asset. + AssetVersion version = 8; +} + +message GroupedAssets { + // A list of assets with the same group key. + repeated AssetHumanReadable assets = 1; +} + +message ListGroupsResponse { + // The set of assets with a group key. + map groups = 1; +} + +message ListBalancesRequest { + oneof group_by { + // Group results by asset IDs. + bool asset_id = 1; + + // Group results by group keys. + bool group_key = 2; + } + + // If the query results should grouped by asset ids, then an optional asset + // filter may be provided to query balance of a specific asset. + bytes asset_filter = 3; + + // If the query results should be grouped by group keys, then an optional + // group key filter may be provided to query the balance of a specific + // asset group. + bytes group_key_filter = 4; +} + +message AssetBalance { + // The base genesis information of an asset. This information never changes. + GenesisInfo asset_genesis = 1; + + // The type of the asset. + AssetType asset_type = 2; + + // The balance of the asset owned by the target daemon. + uint64 balance = 3; +} + +message AssetGroupBalance { + // The group key or nil aggregating assets that don't have a group. + bytes group_key = 1; + + // The total balance of the assets in the group. + uint64 balance = 2; +} + +message ListBalancesResponse { + map asset_balances = 1; + + map asset_group_balances = 2; +} + +message ListTransfersRequest { +} + +message ListTransfersResponse { + // The unordered list of outgoing asset transfers. + repeated AssetTransfer transfers = 1; +} + +message AssetTransfer { + int64 transfer_timestamp = 1; + + // The new transaction that commits to the set of Taproot Assets found + // at the above new anchor point. + bytes anchor_tx_hash = 2; + + uint32 anchor_tx_height_hint = 3; + + int64 anchor_tx_chain_fees = 4; + + // Describes the set of spent assets. + repeated TransferInput inputs = 5; + + // Describes the set of newly created asset outputs. + repeated TransferOutput outputs = 6; +} + +message TransferInput { + // The old/current location of the Taproot Asset commitment that was spent + // as an input. + string anchor_point = 1; + + // The ID of the asset that was spent. + bytes asset_id = 2; + + // The script key of the asset that was spent. + bytes script_key = 3; + + // The amount of the asset that was spent. + uint64 amount = 4; +} + +message TransferOutputAnchor { + // The new location of the Taproot Asset commitment that was created on + // chain. + string outpoint = 1; + + int64 value = 2; + + bytes internal_key = 3; + + bytes taproot_asset_root = 4; + + bytes merkle_root = 5; + + bytes tapscript_sibling = 6; + + uint32 num_passive_assets = 7; +} + +enum OutputType { + // OUTPUT_TYPE_SIMPLE is a plain full-value or split output that is not a + // split root and does not carry passive assets. In case of a split, the + // asset of this output has a split commitment. + OUTPUT_TYPE_SIMPLE = 0; + + // OUTPUT_TYPE_SPLIT_ROOT is a split root output that carries the change + // from a split or a tombstone from a non-interactive full value send + // output. In either case, the asset of this output has a tx witness. + OUTPUT_TYPE_SPLIT_ROOT = 1; + + // OUTPUT_TYPE_PASSIVE_ASSETS_ONLY indicates that this output only carries + // passive assets and therefore the asset in this output is nil. The passive + // assets themselves are signed in their own virtual transactions and + // are not present in this packet. + OUTPUT_TYPE_PASSIVE_ASSETS_ONLY = 2; + + // OUTPUT_TYPE_PASSIVE_SPLIT_ROOT is a split root output that carries the + // change from a split or a tombstone from a non-interactive full value send + // output, as well as passive assets. + OUTPUT_TYPE_PASSIVE_SPLIT_ROOT = 3; + + // OUTPUT_TYPE_SIMPLE_PASSIVE_ASSETS is a plain full-value interactive send + // output that also carries passive assets. This is a special case where we + // send the full value of a single asset in a commitment to a new script + // key, but also carry passive assets in the same output. This is useful for + // key rotation (send-to-self) scenarios or asset burns where we burn the + // full supply of a single asset within a commitment. + OUTPUT_TYPE_SIMPLE_PASSIVE_ASSETS = 4; +} + +message TransferOutput { + TransferOutputAnchor anchor = 1; + + bytes script_key = 2; + + bool script_key_is_local = 3; + + uint64 amount = 4; + + // The new individual transition proof (not a full proof file) that proves + // the inclusion of the new asset within the new AnchorTx. + bytes new_proof_blob = 5; + + bytes split_commit_root_hash = 6; + + OutputType output_type = 7; + + AssetVersion asset_version = 8; +} + +message StopRequest { +} + +message StopResponse { +} + +message DebugLevelRequest { + // If true, all the valid debug sub-systems will be returned. + bool show = 1; + + string level_spec = 2; +} +message DebugLevelResponse { + string sub_systems = 1; +} + +message Addr { + // The bech32 encoded Taproot Asset address. + string encoded = 1; + + // The asset ID that uniquely identifies the asset. + bytes asset_id = 2; + + // The type of the asset. + AssetType asset_type = 3; + + // The total amount of the asset stored in this Taproot Asset UTXO. + uint64 amount = 4; + + // The group key of the asset (if it exists) + bytes group_key = 5; + + /* + The specific script key the asset must commit to in order to transfer + ownership to the creator of the address. + */ + bytes script_key = 6; + + // The internal key used for the on-chain output. + bytes internal_key = 7; + + /* + The optional serialized tapscript sibling preimage to use for the receiving + asset. This is usually empty as it is only needed when there should be an + additional script path in the Taproot tree alongside the Taproot Asset + commitment of the asset. + */ + bytes tapscript_sibling = 8; + + /* + The tweaked internal key that commits to the asset and represents the + on-chain output key the Bitcoin transaction must send to in order to + transfer assets described in this address. + */ + bytes taproot_output_key = 9; + + // The address of the proof courier service used in proof transfer. + string proof_courier_addr = 10; + + // The asset version of the address. + AssetVersion asset_version = 11; +} + +message QueryAddrRequest { + /* + If set, then only addresses created after this Unix timestamp will be + returned. + */ + int64 created_after = 1; + + /* + If set, then only addresses created before this Unix timestamp will be + returned. + */ + int64 created_before = 2; + + // The max number of addresses that should be returned. + int32 limit = 3; + + // The offset from the addresses that should be returned. + int32 offset = 4; +} + +message QueryAddrResponse { + repeated Addr addrs = 1; +} + +message NewAddrRequest { + bytes asset_id = 1; + + uint64 amt = 2; + + /* + The optional script key that the receiving asset should be locked to. If no + script key is provided, a normal BIP-86 key will be derived from the + underlying wallet. + + NOTE: The script_key and internal_key fields should either both be set or + both be empty. + */ + ScriptKey script_key = 3; + + /* + The optional internal key of the receiving BTC level transaction output on + which the receiving asset transfers will be committed to. If no internal key + is provided, a key will be derived from the underlying wallet. + + NOTE: The script_key and internal_key fields should either both be set or + both be empty. + */ + KeyDescriptor internal_key = 4; + + /* + The optional serialized tapscript sibling preimage to use for the receiving + asset. This is usually empty as it is only needed when there should be an + additional script path in the Taproot tree alongside the Taproot Asset + commitment of the asset. + */ + bytes tapscript_sibling = 5; + + /* + An optional proof courier address for use in proof transfer. If unspecified, + the daemon configured default address will be used. + */ + string proof_courier_addr = 6; + + /* + The asset version to use when sending/receiving to/from this address. + */ + AssetVersion asset_version = 7; +} + +message ScriptKey { + /* + The full Taproot output key the asset is locked to. This is either a BIP-86 + key if the tap_tweak below is empty, or a key with the tap tweak applied to + it. + */ + bytes pub_key = 1; + + /* + The key descriptor describing the internal key of the above Taproot key. + */ + KeyDescriptor key_desc = 2; + + /* + The optional Taproot tweak to apply to the above internal key. If this is + empty then a BIP-86 style tweak is applied to the internal key. + */ + bytes tap_tweak = 3; +} + +message KeyLocator { + /* + The family of key being identified. + */ + int32 key_family = 1; + + /* + The precise index of the key being identified. + */ + int32 key_index = 2; +} + +message KeyDescriptor { + /* + The raw bytes of the key being identified. + */ + bytes raw_key_bytes = 1; + + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 2; +} + +message DecodeAddrRequest { + string addr = 1; +} + +message ProofFile { + // The raw proof file encoded as bytes. Must be a file and not just an + // individual mint/transfer proof. + bytes raw_proof_file = 1; + + string genesis_point = 2; +} + +message DecodedProof { + // The index depth of the decoded proof, with 0 being the latest proof. + uint32 proof_at_depth = 1; + + // The total number of proofs contained in the decoded proof file (this will + // always be 1 if a single mint/transition proof was given as the raw_proof + // instead of a file). + uint32 number_of_proofs = 2; + + // The asset referenced in the proof. + Asset asset = 3; + + // The reveal meta data associated with the proof, if available. + AssetMeta meta_reveal = 4; + + // The merkle proof for AnchorTx used to prove its + // inclusion within BlockHeader. + bytes tx_merkle_proof = 5; + + // The TaprootProof proving the new inclusion of the + // resulting asset within AnchorTx. + bytes inclusion_proof = 6; + + // The set of TaprootProofs proving the exclusion of + // the resulting asset from all other Taproot outputs within AnchorTx. + repeated bytes exclusion_proofs = 7; + + // An optional TaprootProof needed if this asset is + // the result of a split. SplitRootProof proves inclusion of the root + // asset of the split. + bytes split_root_proof = 8; + + // The number of additional nested full proofs for any inputs found within + // the resulting asset. + uint32 num_additional_inputs = 9; + + // ChallengeWitness is an optional virtual transaction witness that serves + // as an ownership proof for the asset. If this is non-nil, then it is a + // valid transfer witness for a 1-input, 1-output virtual transaction that + // spends the asset in this proof and sends it to the NUMS key, to prove + // that the creator of the proof is able to produce a valid signature to + // spend the asset. + repeated bytes challenge_witness = 10; + + // Indicates whether the state transition this proof represents is a burn, + // meaning that the assets were provably destroyed and can no longer be + // spent. + bool is_burn = 11; + + // GenesisReveal is an optional field that is the Genesis information for + // the asset. This is required for minting proofs. + GenesisReveal genesis_reveal = 12; + + // GroupKeyReveal is an optional field that includes the information needed + // to derive the tweaked group key. + GroupKeyReveal group_key_reveal = 13; +} + +message VerifyProofResponse { + bool valid = 1; + + // The decoded last proof in the file if the proof file was valid. + DecodedProof decoded_proof = 2; +} + +message DecodeProofRequest { + // The raw proof bytes to decode. This can be a full proof file or a single + // mint/transition proof. If it is a full proof file, the proof_at_depth + // field will be used to determine which individual proof within the file to + // decode. + bytes raw_proof = 1; + + // The index depth of the decoded proof, with 0 being the latest proof. This + // is ignored if the raw_proof is a single mint/transition proof and not a + // proof file. + uint32 proof_at_depth = 2; + + // An option to include previous witnesses in decoding. + bool with_prev_witnesses = 3; + + // An option to attempt to retrieve the meta data associated with the proof. + bool with_meta_reveal = 4; +} + +message DecodeProofResponse { + DecodedProof decoded_proof = 1; +} + +message ExportProofRequest { + bytes asset_id = 1; + bytes script_key = 2; + + // TODO(roasbeef): specify information to make new state transition in proof + // file? +} + +enum AddrEventStatus { + ADDR_EVENT_STATUS_UNKNOWN = 0; + ADDR_EVENT_STATUS_TRANSACTION_DETECTED = 1; + ADDR_EVENT_STATUS_TRANSACTION_CONFIRMED = 2; + ADDR_EVENT_STATUS_PROOF_RECEIVED = 3; + ADDR_EVENT_STATUS_COMPLETED = 4; +} + +message AddrEvent { + // The time the event was created in unix timestamp seconds. + uint64 creation_time_unix_seconds = 1; + + // The address the event was created for. + Addr addr = 2; + + // The current status of the event. + AddrEventStatus status = 3; + + // The outpoint that contains the inbound asset transfer. + string outpoint = 4; + + /* + The amount in satoshis that were transferred on chain along with the asset. + This amount is independent of the requested asset amount, which can be + looked up on the address. + */ + uint64 utxo_amt_sat = 5; + + /* + The taproot sibling hash that was used to send to the Taproot output. + */ + bytes taproot_sibling = 6; + + /* + The height at which the on-chain output was confirmed. If this is zero, it + means the output is unconfirmed. + */ + uint32 confirmation_height = 7; + + /* + Indicates whether a proof file can be found for the address' asset ID and + script key. + */ + bool has_proof = 8; +} + +message AddrReceivesRequest { + // Filter receives by a specific address. Leave empty to get all receives. + string filter_addr = 1; + + // Filter receives by a specific status. Leave empty to get all receives. + AddrEventStatus filter_status = 2; +} + +message AddrReceivesResponse { + // The events that match the filter criteria. + repeated AddrEvent events = 1; +} + +message SendAssetRequest { + repeated string tap_addrs = 1; + + // The optional fee rate to use for the minting transaction, in sat/kw. + uint32 fee_rate = 2; + // TODO(roasbeef): maybe in future add details re type of ProofCourier or + // w/e +} + +message PrevInputAsset { + string anchor_point = 1; + bytes asset_id = 2; + bytes script_key = 3; + uint64 amount = 4; +} + +message SendAssetResponse { + AssetTransfer transfer = 1; +} + +message GetInfoRequest { +} + +message GetInfoResponse { + string version = 1; + string lnd_version = 2; + string network = 3; + string lnd_identity_pubkey = 4; + string node_alias = 5; + uint32 block_height = 6; + string block_hash = 7; + bool sync_to_chain = 8; +} + +message SubscribeSendAssetEventNtfnsRequest { +} + +message SendAssetEvent { + oneof event { + // An event which indicates that a send state is about to be executed. + ExecuteSendStateEvent execute_send_state_event = 1; + + // An event which indicates that the proof send backoff wait period will + // start imminently. + ReceiverProofBackoffWaitEvent receiver_proof_backoff_wait_event = 2; + } +} + +message ExecuteSendStateEvent { + // Execute timestamp (microseconds). + int64 timestamp = 1; + + // The send state that is about to be executed. + string send_state = 2; +} + +message ReceiverProofBackoffWaitEvent { + // Transfer attempt timestamp (microseconds). + int64 timestamp = 1; + + // Backoff is the active backoff wait duration. + int64 backoff = 2; + + // Tries counter is the number of tries we've made so far during the + // course of the current backoff procedure to deliver the proof to the + // receiver. + int64 tries_counter = 3; +} + +message FetchAssetMetaRequest { + oneof asset { + // The asset ID of the asset to fetch the meta for. + bytes asset_id = 1; + + // The 32-byte meta hash of the asset meta. + bytes meta_hash = 2; + + // The hex encoded asset ID of the asset to fetch the meta for. + string asset_id_str = 3; + + // The hex encoded meta hash of the asset meta. + string meta_hash_str = 4; + } +} + +message BurnAssetRequest { + oneof asset { + // The asset ID of the asset to burn units of. + bytes asset_id = 1; + + // The hex encoded asset ID of the asset to burn units of. + string asset_id_str = 2; + } + + uint64 amount_to_burn = 3; + + // A safety check to ensure the user is aware of the destructive nature of + // the burn. This needs to be set to the value "assets will be destroyed" + // for the burn to succeed. + string confirmation_text = 4; +} + +message BurnAssetResponse { + // The asset transfer that contains the asset burn as an output. + AssetTransfer burn_transfer = 1; + + // The burn transition proof for the asset burn output. + DecodedProof burn_proof = 2; +} diff --git a/proto/walletrpc/walletkit.proto b/proto/walletrpc/walletkit.proto new file mode 100644 index 000000000..0d3fd9512 --- /dev/null +++ b/proto/walletrpc/walletkit.proto @@ -0,0 +1,1245 @@ +syntax = "proto3"; + +import "lightning.proto"; +import "signrpc/signer.proto"; + +package walletrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/walletrpc"; + +// WalletKit is a service that gives access to the core functionalities of the +// daemon's wallet. +service WalletKit { + /* + ListUnspent returns a list of all utxos spendable by the wallet with a + number of confirmations between the specified minimum and maximum. By + default, all utxos are listed. To list only the unconfirmed utxos, set + the unconfirmed_only to true. + */ + rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse); + + /* + LeaseOutput locks an output to the given ID, preventing it from being + available for any future coin selection attempts. The absolute time of the + lock's expiration is returned. The expiration of the lock can be extended by + successive invocations of this RPC. Outputs can be unlocked before their + expiration through `ReleaseOutput`. + */ + rpc LeaseOutput (LeaseOutputRequest) returns (LeaseOutputResponse); + + /* + ReleaseOutput unlocks an output, allowing it to be available for coin + selection if it remains unspent. The ID should match the one used to + originally lock the output. + */ + rpc ReleaseOutput (ReleaseOutputRequest) returns (ReleaseOutputResponse); + + /* + ListLeases lists all currently locked utxos. + */ + rpc ListLeases (ListLeasesRequest) returns (ListLeasesResponse); + + /* + DeriveNextKey attempts to derive the *next* key within the key family + (account in BIP43) specified. This method should return the next external + child within this branch. + */ + rpc DeriveNextKey (KeyReq) returns (signrpc.KeyDescriptor); + + /* + DeriveKey attempts to derive an arbitrary key specified by the passed + KeyLocator. + */ + rpc DeriveKey (signrpc.KeyLocator) returns (signrpc.KeyDescriptor); + + /* + NextAddr returns the next unused address within the wallet. + */ + rpc NextAddr (AddrRequest) returns (AddrResponse); + + /* + ListAccounts retrieves all accounts belonging to the wallet by default. A + name and key scope filter can be provided to filter through all of the + wallet accounts and return only those matching. + */ + rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse); + + /* + RequiredReserve returns the minimum amount of satoshis that should be kept + in the wallet in order to fee bump anchor channels if necessary. The value + scales with the number of public anchor channels but is capped at a maximum. + */ + rpc RequiredReserve (RequiredReserveRequest) + returns (RequiredReserveResponse); + + /* + ListAddresses retrieves all the addresses along with their balance. An + account name filter can be provided to filter through all of the + wallet accounts and return the addresses of only those matching. + */ + rpc ListAddresses (ListAddressesRequest) returns (ListAddressesResponse); + + /* + SignMessageWithAddr returns the compact signature (base64 encoded) created + with the private key of the provided address. This requires the address + to be solely based on a public key lock (no scripts). Obviously the internal + lnd wallet has to possess the private key of the address otherwise + an error is returned. + + This method aims to provide full compatibility with the bitcoin-core and + btcd implementation. Bitcoin-core's algorithm is not specified in a + BIP and only applicable for legacy addresses. This method enhances the + signing for additional address types: P2WKH, NP2WKH, P2TR. + For P2TR addresses this represents a special case. ECDSA is used to create + a compact signature which makes the public key of the signature recoverable. + */ + rpc SignMessageWithAddr (SignMessageWithAddrRequest) + returns (SignMessageWithAddrResponse); + + /* + VerifyMessageWithAddr returns the validity and the recovered public key of + the provided compact signature (base64 encoded). The verification is + twofold. First the validity of the signature itself is checked and then + it is verified that the recovered public key of the signature equals + the public key of the provided address. There is no dependence on the + private key of the address therefore also external addresses are allowed + to verify signatures. + Supported address types are P2PKH, P2WKH, NP2WKH, P2TR. + + This method is the counterpart of the related signing method + (SignMessageWithAddr) and aims to provide full compatibility to + bitcoin-core's implementation. Although bitcoin-core/btcd only provide + this functionality for legacy addresses this function enhances it to + the address types: P2PKH, P2WKH, NP2WKH, P2TR. + + The verification for P2TR addresses is a special case and requires the + ECDSA compact signature to compare the reovered public key to the internal + taproot key. The compact ECDSA signature format was used because there + are still no known compact signature schemes for schnorr signatures. + */ + rpc VerifyMessageWithAddr (VerifyMessageWithAddrRequest) + returns (VerifyMessageWithAddrResponse); + + /* + ImportAccount imports an account backed by an account extended public key. + The master key fingerprint denotes the fingerprint of the root key + corresponding to the account public key (also known as the key with + derivation path m/). This may be required by some hardware wallets for + proper identification and signing. + + The address type can usually be inferred from the key's version, but may be + required for certain keys to map them into the proper scope. + + For BIP-0044 keys, an address type must be specified as we intend to not + support importing BIP-0044 keys into the wallet using the legacy + pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force + the standard BIP-0049 derivation scheme, while a witness address type will + force the standard BIP-0084 derivation scheme. + + For BIP-0049 keys, an address type must also be specified to make a + distinction between the standard BIP-0049 address schema (nested witness + pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys + externally, witness pubkeys internally). + + NOTE: Events (deposits/spends) for keys derived from an account will only be + detected by lnd if they happen after the import. Rescans to detect past + events will be supported later on. + */ + rpc ImportAccount (ImportAccountRequest) returns (ImportAccountResponse); + + /* + ImportPublicKey imports a public key as watch-only into the wallet. The + public key is converted into a simple address of the given type and that + address script is watched on chain. For Taproot keys, this will only watch + the BIP-0086 style output script. Use ImportTapscript for more advanced key + spend or script spend outputs. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + */ + rpc ImportPublicKey (ImportPublicKeyRequest) + returns (ImportPublicKeyResponse); + + /* + ImportTapscript imports a Taproot script and internal key and adds the + resulting Taproot output key as a watch-only output script into the wallet. + For BIP-0086 style Taproot keys (no root hash commitment and no script spend + path) use ImportPublicKey. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + + NOTE: Taproot keys imported through this RPC currently _cannot_ be used for + funding PSBTs. Only tracking the balance and UTXOs is currently supported. + */ + rpc ImportTapscript (ImportTapscriptRequest) + returns (ImportTapscriptResponse); + + /* + PublishTransaction attempts to publish the passed transaction to the + network. Once this returns without an error, the wallet will continually + attempt to re-broadcast the transaction on start up, until it enters the + chain. + */ + rpc PublishTransaction (Transaction) returns (PublishResponse); + + /* + SendOutputs is similar to the existing sendmany call in Bitcoind, and + allows the caller to create a transaction that sends to several outputs at + once. This is ideal when wanting to batch create a set of transactions. + */ + rpc SendOutputs (SendOutputsRequest) returns (SendOutputsResponse); + + /* + EstimateFee attempts to query the internal fee estimator of the wallet to + determine the fee (in sat/kw) to attach to a transaction in order to + achieve the confirmation target. + */ + rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse); + + /* + PendingSweeps returns lists of on-chain outputs that lnd is currently + attempting to sweep within its central batching engine. Outputs with similar + fee rates are batched together in order to sweep them within a single + transaction. + + NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + remain supported. This is an advanced API that depends on the internals of + the UtxoSweeper, so things may change. + */ + rpc PendingSweeps (PendingSweepsRequest) returns (PendingSweepsResponse); + + /* + BumpFee bumps the fee of an arbitrary input within a transaction. This RPC + takes a different approach than bitcoind's bumpfee command. lnd has a + central batching engine in which inputs with similar fee rates are batched + together to save on transaction fees. Due to this, we cannot rely on + bumping the fee on a specific transaction, since transactions can change at + any point with the addition of new inputs. The list of inputs that + currently exist within lnd's central batching engine can be retrieved + through the PendingSweeps RPC. + + When bumping the fee of an input that currently exists within lnd's central + batching engine, a higher fee transaction will be created that replaces the + lower fee transaction through the Replace-By-Fee (RBF) policy. If it + + This RPC also serves useful when wanting to perform a Child-Pays-For-Parent + (CPFP), where the child transaction pays for its parent's fee. This can be + done by specifying an outpoint within the low fee transaction that is under + the control of the wallet. + + The fee preference can be expressed either as a specific fee rate or a delta + of blocks in which the output should be swept on-chain within. If a fee + preference is not explicitly specified, then an error is returned. + + Note that this RPC currently doesn't perform any validation checks on the + fee preference being provided. For now, the responsibility of ensuring that + the new fee preference is sufficient is delegated to the user. + */ + rpc BumpFee (BumpFeeRequest) returns (BumpFeeResponse); + + /* + ListSweeps returns a list of the sweep transactions our node has produced. + Note that these sweeps may not be confirmed yet, as we record sweeps on + broadcast, not confirmation. + */ + rpc ListSweeps (ListSweepsRequest) returns (ListSweepsResponse); + + /* + LabelTransaction adds a label to a transaction. If the transaction already + has a label the call will fail unless the overwrite bool is set. This will + overwrite the exiting transaction label. Labels must not be empty, and + cannot exceed 500 characters. + */ + rpc LabelTransaction (LabelTransactionRequest) + returns (LabelTransactionResponse); + + /* + FundPsbt creates a fully populated PSBT that contains enough inputs to fund + the outputs specified in the template. There are two ways of specifying a + template: Either by passing in a PSBT with at least one output declared or + by passing in a raw TxTemplate message. + + If there are no inputs specified in the template, coin selection is + performed automatically. If the template does contain any inputs, it is + assumed that full coin selection happened externally and no additional + inputs are added. If the specified inputs aren't enough to fund the outputs + with the given fee rate, an error is returned. + + After either selecting or verifying the inputs, all input UTXOs are locked + with an internal app ID. + + NOTE: If this method returns without an error, it is the caller's + responsibility to either spend the locked UTXOs (by finalizing and then + publishing the transaction) or to unlock/release the locked UTXOs in case of + an error on the caller's side. + */ + rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse); + + /* + SignPsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all unsigned inputs that have all required fields + (UTXO information, BIP32 derivation information, witness or sig scripts) + set. + If no error is returned, the PSBT is ready to be given to the next signer or + to be finalized if lnd was the last signer. + + NOTE: This RPC only signs inputs (and only those it can sign), it does not + perform any other tasks (such as coin selection, UTXO locking or + input/output/fee value validation, PSBT finalization). Any input that is + incomplete will be skipped. + */ + rpc SignPsbt (SignPsbtRequest) returns (SignPsbtResponse); + + /* + FinalizePsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all inputs that belong to the wallet. Lnd must be + the last signer of the transaction. That means, if there are any unsigned + non-witness inputs or inputs without UTXO information attached or inputs + without witness data that do not belong to lnd's wallet, this method will + fail. If no error is returned, the PSBT is ready to be extracted and the + final TX within to be broadcast. + + NOTE: This method does NOT publish the transaction once finalized. It is the + caller's responsibility to either publish the transaction on success or + unlock/release any locked UTXOs in case of an error in this method. + */ + rpc FinalizePsbt (FinalizePsbtRequest) returns (FinalizePsbtResponse); +} + +message ListUnspentRequest { + // The minimum number of confirmations to be included. + int32 min_confs = 1; + + // The maximum number of confirmations to be included. + int32 max_confs = 2; + + // An optional filter to only include outputs belonging to an account. + string account = 3; + + /* + When min_confs and max_confs are zero, setting false implicitly + overrides max_confs to be MaxInt32, otherwise max_confs remains + zero. An error is returned if the value is true and both min_confs + and max_confs are non-zero. (default: false) + */ + bool unconfirmed_only = 4; +} + +message ListUnspentResponse { + // A list of utxos satisfying the specified number of confirmations. + repeated lnrpc.Utxo utxos = 1; +} + +message LeaseOutputRequest { + /* + An ID of 32 random bytes that must be unique for each distinct application + using this RPC which will be used to bound the output lease to. + */ + bytes id = 1; + + // The identifying outpoint of the output being leased. + lnrpc.OutPoint outpoint = 2; + + // The time in seconds before the lock expires. If set to zero, the default + // lock duration is used. + uint64 expiration_seconds = 3; +} + +message LeaseOutputResponse { + /* + The absolute expiration of the output lease represented as a unix timestamp. + */ + uint64 expiration = 1; +} + +message ReleaseOutputRequest { + // The unique ID that was used to lock the output. + bytes id = 1; + + // The identifying outpoint of the output being released. + lnrpc.OutPoint outpoint = 2; +} + +message ReleaseOutputResponse { +} + +message KeyReq { + /* + Is the key finger print of the root pubkey that this request is targeting. + This allows the WalletKit to possibly serve out keys for multiple HD chains + via public derivation. + */ + int32 key_finger_print = 1; + + /* + The target key family to derive a key from. In other contexts, this is + known as the "account". + */ + int32 key_family = 2; +} + +message AddrRequest { + /* + The name of the account to retrieve the next address of. If empty, the + default wallet account is used. + */ + string account = 1; + + /* + The type of address to derive. + */ + AddressType type = 2; + + /* + Whether a change address should be derived. + */ + bool change = 3; +} +message AddrResponse { + /* + The address encoded using a bech32 format. + */ + string addr = 1; +} + +enum AddressType { + UNKNOWN = 0; + WITNESS_PUBKEY_HASH = 1; + NESTED_WITNESS_PUBKEY_HASH = 2; + HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; +} +message Account { + // The name used to identify the account. + string name = 1; + + // The type of addresses the account supports. + AddressType address_type = 2; + + /* + The public key backing the account that all keys are derived from + represented as an extended key. This will always be empty for the default + imported account in which single public keys are imported into. + */ + string extended_public_key = 3; + + /* + The fingerprint of the root key from which the account public key was + derived from. This will always be zero for the default imported account in + which single public keys are imported into. The bytes are in big-endian + order. + */ + bytes master_key_fingerprint = 4; + + /* + The derivation path corresponding to the account public key. This will + always be empty for the default imported account in which single public keys + are imported into. + */ + string derivation_path = 5; + + /* + The number of keys derived from the external branch of the account public + key. This will always be zero for the default imported account in which + single public keys are imported into. + */ + uint32 external_key_count = 6; + + /* + The number of keys derived from the internal branch of the account public + key. This will always be zero for the default imported account in which + single public keys are imported into. + */ + uint32 internal_key_count = 7; + + // Whether the wallet stores private keys for the account. + bool watch_only = 8; +} + +message AddressProperty { + /* + The address encoded using the appropriate format depending on the + address type (base58, bech32, bech32m). + + Note that lnd's internal/custom keys for channels and other + functionality are derived from the same scope. Since they + aren't really used as addresses and will never have an + on-chain balance, we'll show the public key instead (only if + the show_custom_accounts flag is provided). + */ + string address = 1; + + // Denotes if the address is a change address. + bool is_internal = 2; + + // The balance of the address. + int64 balance = 3; +} + +message AccountWithAddresses { + // The name used to identify the account. + string name = 1; + + // The type of addresses the account supports. + AddressType address_type = 2; + + /* + The derivation path corresponding to the account public key. This will + always be empty for the default imported account in which single public keys + are imported into. + */ + string derivation_path = 3; + + /* + List of address, its type internal/external & balance. + Note that the order of addresses will be random and not according to the + derivation index, since that information is not stored by the underlying + wallet. + */ + repeated AddressProperty addresses = 4; +} + +message ListAccountsRequest { + // An optional filter to only return accounts matching this name. + string name = 1; + + // An optional filter to only return accounts matching this address type. + AddressType address_type = 2; +} + +message ListAccountsResponse { + repeated Account accounts = 1; +} + +message RequiredReserveRequest { + // The number of additional channels the user would like to open. + uint32 additional_public_channels = 1; +} + +message RequiredReserveResponse { + // The amount of reserve required. + int64 required_reserve = 1; +} + +message ListAddressesRequest { + // An optional filter to only return addresses matching this account. + string account_name = 1; + + // An optional flag to return LND's custom accounts (Purpose=1017) + // public key along with other addresses. + bool show_custom_accounts = 2; +} + +message ListAddressesResponse { + // A list of all the accounts and their addresses. + repeated AccountWithAddresses account_with_addresses = 1; +} + +message SignMessageWithAddrRequest { + // The message to be signed. When using REST, this field must be encoded as + // base64. + bytes msg = 1; + + // The address which will be used to look up the private key and sign the + // corresponding message. + string addr = 2; +} + +message SignMessageWithAddrResponse { + // The compact ECDSA signature for the given message encoded in base64. + string signature = 1; +} + +message VerifyMessageWithAddrRequest { + // The message to be signed. When using REST, this field must be encoded as + // base64. + bytes msg = 1; + + // The compact ECDSA signature to be verified over the given message + // ecoded in base64. + string signature = 2; + + // The address which will be used to look up the public key and verify the + // the signature. + string addr = 3; +} + +message VerifyMessageWithAddrResponse { + // Whether the signature was valid over the given message. + bool valid = 1; + + // The pubkey recovered from the signature. + bytes pubkey = 2; +} + +message ImportAccountRequest { + // A name to identify the account with. + string name = 1; + + /* + A public key that corresponds to a wallet account represented as an extended + key. It must conform to a derivation path of the form + m/purpose'/coin_type'/account'. + */ + string extended_public_key = 2; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public key was derived from. This may be required + by some hardware wallets for proper identification and signing. The bytes + must be in big-endian order. + */ + bytes master_key_fingerprint = 3; + + /* + An address type is only required when the extended account public key has a + legacy version (xpub, tpub, etc.), such that the wallet cannot detect what + address scheme it belongs to. + */ + AddressType address_type = 4; + + /* + Whether a dry run should be attempted when importing the account. This + serves as a way to confirm whether the account is being imported correctly + by returning the first N addresses for the external and internal branches of + the account. If these addresses match as expected, then it should be safe to + import the account as is. + */ + bool dry_run = 5; +} +message ImportAccountResponse { + // The details of the imported account. + Account account = 1; + + /* + The first N addresses that belong to the external branch of the account. + The external branch is typically used for external non-change addresses. + These are only returned if a dry run was specified within the request. + */ + repeated string dry_run_external_addrs = 2; + + /* + The first N addresses that belong to the internal branch of the account. + The internal branch is typically used for change addresses. These are only + returned if a dry run was specified within the request. + */ + repeated string dry_run_internal_addrs = 3; +} + +message ImportPublicKeyRequest { + // A compressed public key represented as raw bytes. + bytes public_key = 1; + + // The type of address that will be generated from the public key. + AddressType address_type = 2; +} +message ImportPublicKeyResponse { +} + +message ImportTapscriptRequest { + /* + The internal public key, serialized as 32-byte x-only public key. + */ + bytes internal_public_key = 1; + + oneof script { + /* + The full script tree with all individual leaves is known and the root + hash can be constructed from the full tree directly. + */ + TapscriptFullTree full_tree = 2; + + /* + Only a single script leaf is known. To construct the root hash, the full + inclusion proof must also be provided. + */ + TapscriptPartialReveal partial_reveal = 3; + + /* + Only the root hash of the Taproot script tree (or other form of Taproot + commitment) is known. + */ + bytes root_hash_only = 4; + + /* + Only the final, tweaked Taproot key is known and no additional + information about the internal key or type of tweak that was used to + derive it. When this is set, the wallet treats the key in + internal_public_key as the Taproot key directly. This can be useful for + tracking arbitrary Taproot outputs without the goal of ever being able + to spend from them through the internal wallet. + */ + bool full_key_only = 5; + } +} + +message TapscriptFullTree { + /* + The complete, ordered list of all tap leaves of the tree. + */ + repeated TapLeaf all_leaves = 1; +} + +message TapLeaf { + // The leaf version. Should be 0xc0 (192) in case of a SegWit v1 script. + uint32 leaf_version = 1; + + // The script of the tap leaf. + bytes script = 2; +} + +message TapscriptPartialReveal { + // The tap leaf that is known and will be revealed. + TapLeaf revealed_leaf = 1; + + // The BIP-0341 serialized inclusion proof that is required to prove that + // the revealed leaf is part of the tree. This contains 0..n blocks of 32 + // bytes. If the tree only contained a single leaf (which is the revealed + // leaf), this can be empty. + bytes full_inclusion_proof = 2; +} + +message ImportTapscriptResponse { + /* + The resulting pay-to-Taproot address that represents the imported internal + key with the script committed to it. + */ + string p2tr_address = 1; +} + +message Transaction { + /* + The raw serialized transaction. Despite the field name, this does need to be + specified in raw bytes (or base64 encoded when using REST) and not in hex. + To not break existing software, the field can't simply be renamed. + */ + bytes tx_hex = 1; + + /* + An optional label to save with the transaction. Limited to 500 characters. + */ + string label = 2; +} +message PublishResponse { + /* + If blank, then no error occurred and the transaction was successfully + published. If not the empty string, then a string representation of the + broadcast error. + + TODO(roasbeef): map to a proper enum type + */ + string publish_error = 1; +} + +message SendOutputsRequest { + /* + The number of satoshis per kilo weight that should be used when crafting + this transaction. + */ + int64 sat_per_kw = 1; + + /* + A slice of the outputs that should be created in the transaction produced. + */ + repeated signrpc.TxOut outputs = 2; + + // An optional label for the transaction, limited to 500 characters. + string label = 3; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 4; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 5; +} +message SendOutputsResponse { + /* + The serialized transaction sent out on the network. + */ + bytes raw_tx = 1; +} + +message EstimateFeeRequest { + /* + The number of confirmations to shoot for when estimating the fee. + */ + int32 conf_target = 1; +} +message EstimateFeeResponse { + /* + The amount of satoshis per kw that should be used in order to reach the + confirmation target in the request. + */ + int64 sat_per_kw = 1; +} + +enum WitnessType { + UNKNOWN_WITNESS = 0; + + /* + A witness that allows us to spend the output of a commitment transaction + after a relative lock-time lockout. + */ + COMMITMENT_TIME_LOCK = 1; + + /* + A witness that allows us to spend a settled no-delay output immediately on a + counterparty's commitment transaction. + */ + COMMITMENT_NO_DELAY = 2; + + /* + A witness that allows us to sweep the settled output of a malicious + counterparty's who broadcasts a revoked commitment transaction. + */ + COMMITMENT_REVOKE = 3; + + /* + A witness that allows us to sweep an HTLC which we offered to the remote + party in the case that they broadcast a revoked commitment state. + */ + HTLC_OFFERED_REVOKE = 4; + + /* + A witness that allows us to sweep an HTLC output sent to us in the case that + the remote party broadcasts a revoked commitment state. + */ + HTLC_ACCEPTED_REVOKE = 5; + + /* + A witness that allows us to sweep an HTLC output that we extended to a + party, but was never fulfilled. This HTLC output isn't directly on the + commitment transaction, but is the result of a confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 6; + + /* + A witness that allows us to sweep an HTLC output that was offered to us, and + for which we have a payment preimage. This HTLC output isn't directly on our + commitment transaction, but is the result of confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 7; + + /* + A witness that allows us to sweep an HTLC that we offered to the remote + party which lies in the commitment transaction of the remote party. We can + spend this output after the absolute CLTV timeout of the HTLC as passed. + */ + HTLC_OFFERED_REMOTE_TIMEOUT = 8; + + /* + A witness that allows us to sweep an HTLC that was offered to us by the + remote party. We use this witness in the case that the remote party goes to + chain, and we know the pre-image to the HTLC. We can sweep this without any + additional timeout. + */ + HTLC_ACCEPTED_REMOTE_SUCCESS = 9; + + /* + A witness that allows us to sweep an HTLC from the remote party's commitment + transaction in the case that the broadcast a revoked commitment, but then + also immediately attempt to go to the second level to claim the HTLC. + */ + HTLC_SECOND_LEVEL_REVOKE = 10; + + /* + A witness type that allows us to spend a regular p2wkh output that's sent to + an output which is under complete control of the backing wallet. + */ + WITNESS_KEY_HASH = 11; + + /* + A witness type that allows us to sweep an output that sends to a nested P2SH + script that pays to a key solely under our control. + */ + NESTED_WITNESS_KEY_HASH = 12; + + /* + A witness type that allows us to spend our anchor on the commitment + transaction. + */ + COMMITMENT_ANCHOR = 13; + + /* + A witness type that is similar to the COMMITMENT_NO_DELAY type, + but it omits the tweak that randomizes the key we need to + spend with a channel peer supplied set of randomness. + */ + COMMITMENT_NO_DELAY_TWEAKLESS = 14; + + /* + A witness type that allows us to spend our output on the counterparty's + commitment transaction after a confirmation. + */ + COMMITMENT_TO_REMOTE_CONFIRMED = 15; + + /* + A witness type that allows us to sweep an HTLC output that we extended + to a party, but was never fulfilled. This _is_ the HTLC output directly + on our commitment transaction, and the input to the second-level HTLC + timeout transaction. It can only be spent after CLTV expiry, and + commitment confirmation. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED = 16; + + /* + A witness type that allows us to sweep an HTLC output that was offered + to us, and for which we have a payment preimage. This _is_ the HTLC + output directly on our commitment transaction, and the input to the + second-level HTLC success transaction. It can only be spent after the + commitment has confirmed. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED = 17; + + /* + A witness type that allows us to spend our output on our local + commitment transaction after a relative and absolute lock-time lockout as + part of the script enforced lease commitment type. + */ + LEASE_COMMITMENT_TIME_LOCK = 18; + + /* + A witness type that allows us to spend our output on the counterparty's + commitment transaction after a confirmation and absolute locktime as part + of the script enforced lease commitment type. + */ + LEASE_COMMITMENT_TO_REMOTE_CONFIRMED = 19; + + /* + A witness type that allows us to sweep an HTLC output that we extended + to a party, but was never fulfilled. This HTLC output isn't directly on + the commitment transaction, but is the result of a confirmed second-level + HTLC transaction. As a result, we can only spend this after a CSV delay + and CLTV locktime as part of the script enforced lease commitment type. + */ + LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 20; + + /* + A witness type that allows us to sweep an HTLC output that was offered + to us, and for which we have a payment preimage. This HTLC output isn't + directly on our commitment transaction, but is the result of confirmed + second-level HTLC transaction. As a result, we can only spend this after + a CSV delay and CLTV locktime as part of the script enforced lease + commitment type. + */ + LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 21; + + /* + A witness type that allows us to spend a regular p2tr output that's sent + to an output which is under complete control of the backing wallet. + */ + TAPROOT_PUB_KEY_SPEND = 22; +} + +message PendingSweep { + // The outpoint of the output we're attempting to sweep. + lnrpc.OutPoint outpoint = 1; + + // The witness type of the output we're attempting to sweep. + WitnessType witness_type = 2; + + // The value of the output we're attempting to sweep. + uint32 amount_sat = 3; + + /* + Deprecated, use sat_per_vbyte. + The fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee + rate is only determined once a sweeping transaction for the output is + created, so it's possible for this to be 0 before this. + */ + uint32 sat_per_byte = 4 [deprecated = true]; + + // The number of broadcast attempts we've made to sweep the output. + uint32 broadcast_attempts = 5; + + /* + The next height of the chain at which we'll attempt to broadcast the + sweep transaction of the output. + */ + uint32 next_broadcast_height = 6; + + // The requested confirmation target for this output. + uint32 requested_conf_target = 8; + + // Deprecated, use requested_sat_per_vbyte. + // The requested fee rate, expressed in sat/vbyte, for this output. + uint32 requested_sat_per_byte = 9 [deprecated = true]; + + /* + The fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee + rate is only determined once a sweeping transaction for the output is + created, so it's possible for this to be 0 before this. + */ + uint64 sat_per_vbyte = 10; + + // The requested fee rate, expressed in sat/vbyte, for this output. + uint64 requested_sat_per_vbyte = 11; + + /* + Whether this input must be force-swept. This means that it is swept even + if it has a negative yield. + */ + bool force = 7; +} + +message PendingSweepsRequest { +} + +message PendingSweepsResponse { + /* + The set of outputs currently being swept by lnd's central batching engine. + */ + repeated PendingSweep pending_sweeps = 1; +} + +message BumpFeeRequest { + // The input we're attempting to bump the fee of. + lnrpc.OutPoint outpoint = 1; + + // The target number of blocks that the input should be spent within. + uint32 target_conf = 2; + + /* + Deprecated, use sat_per_vbyte. + The fee rate, expressed in sat/vbyte, that should be used to spend the input + with. + */ + uint32 sat_per_byte = 3 [deprecated = true]; + + /* + Whether this input must be force-swept. This means that it is swept even + if it has a negative yield. + */ + bool force = 4; + + /* + The fee rate, expressed in sat/vbyte, that should be used to spend the input + with. + */ + uint64 sat_per_vbyte = 5; +} + +message BumpFeeResponse { +} + +message ListSweepsRequest { + /* + Retrieve the full sweep transaction details. If false, only the sweep txids + will be returned. Note that some sweeps that LND publishes will have been + replaced-by-fee, so will not be included in this output. + */ + bool verbose = 1; +} + +message ListSweepsResponse { + message TransactionIDs { + /* + Reversed, hex-encoded string representing the transaction ids of the + sweeps that our node has broadcast. Note that these transactions may + not have confirmed yet, we record sweeps on broadcast, not confirmation. + */ + repeated string transaction_ids = 1; + } + + oneof sweeps { + lnrpc.TransactionDetails transaction_details = 1; + TransactionIDs transaction_ids = 2; + } +} + +message LabelTransactionRequest { + // The txid of the transaction to label. Note: When using gRPC, the bytes + // must be in little-endian (reverse) order. + bytes txid = 1; + + // The label to add to the transaction, limited to 500 characters. + string label = 2; + + // Whether to overwrite the existing label, if it is present. + bool overwrite = 3; +} + +message LabelTransactionResponse { +} + +// The possible change address types for default accounts and single imported +// public keys. By default, P2WPKH will be used. We don't provide the +// possibility to choose P2PKH as it is a legacy key scope, nor NP2WPKH as +// no key scope permits to do so. For custom accounts, no change type should +// be provided as the coin selection key scope will always be used to generate +// the change address. +enum ChangeAddressType { + // CHANGE_ADDRESS_TYPE_UNSPECIFIED indicates that no change address type is + // provided. We will then use P2WPKH address type for change (BIP0084 key + // scope). + CHANGE_ADDRESS_TYPE_UNSPECIFIED = 0; + + // CHANGE_ADDRESS_TYPE_P2TR indicates to use P2TR address for change output + // (BIP0086 key scope). + CHANGE_ADDRESS_TYPE_P2TR = 1; +} + +message FundPsbtRequest { + oneof template { + /* + Use an existing PSBT packet as the template for the funded PSBT. + + The packet must contain at least one non-dust output. If one or more + inputs are specified, no coin selection is performed. In that case every + input must be an UTXO known to the wallet that has not been locked + before. The sum of all inputs must be sufficiently greater than the sum + of all outputs to pay a miner fee with the specified fee rate. A change + output is added to the PSBT if necessary. + */ + bytes psbt = 1; + + /* + Use the outputs and optional inputs from this raw template. + */ + TxTemplate raw = 2; + } + + oneof fees { + /* + The target number of blocks that the transaction should be confirmed in. + */ + uint32 target_conf = 3; + + /* + The fee rate, expressed in sat/vbyte, that should be used to spend the + input with. + */ + uint64 sat_per_vbyte = 4; + } + + /* + The name of the account to fund the PSBT with. If empty, the default wallet + account is used. + */ + string account = 5; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 6; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 7; + + // The address type for the change. If empty, P2WPKH addresses will be used + // for default accounts and single imported public keys. For custom + // accounts, no change type should be provided as the coin selection key + // scope will always be used to generate the change address. + ChangeAddressType change_type = 8; +} +message FundPsbtResponse { + /* + The funded but not yet signed PSBT packet. + */ + bytes funded_psbt = 1; + + /* + The index of the added change output or -1 if no change was left over. + */ + int32 change_output_index = 2; + + /* + The list of lock leases that were acquired for the inputs in the funded PSBT + packet. + */ + repeated UtxoLease locked_utxos = 3; +} + +message TxTemplate { + /* + An optional list of inputs to use. Every input must be an UTXO known to the + wallet that has not been locked before. The sum of all inputs must be + sufficiently greater than the sum of all outputs to pay a miner fee with the + fee rate specified in the parent message. + + If no inputs are specified, coin selection will be performed instead and + inputs of sufficient value will be added to the resulting PSBT. + */ + repeated lnrpc.OutPoint inputs = 1; + + /* + A map of all addresses and the amounts to send to in the funded PSBT. + */ + map outputs = 2; +} + +message UtxoLease { + /* + A 32 byte random ID that identifies the lease. + */ + bytes id = 1; + + // The identifying outpoint of the output being leased. + lnrpc.OutPoint outpoint = 2; + + /* + The absolute expiration of the output lease represented as a unix timestamp. + */ + uint64 expiration = 3; + + /* + The public key script of the leased output. + */ + bytes pk_script = 4; + + /* + The value of the leased output in satoshis. + */ + uint64 value = 5; +} + +message SignPsbtRequest { + /* + The PSBT that should be signed. The PSBT must contain all required inputs, + outputs, UTXO data and custom fields required to identify the signing key. + */ + bytes funded_psbt = 1; +} + +message SignPsbtResponse { + // The signed transaction in PSBT format. + bytes signed_psbt = 1; + + // The indices of signed inputs. + repeated uint32 signed_inputs = 2; +} + +message FinalizePsbtRequest { + /* + A PSBT that should be signed and finalized. The PSBT must contain all + required inputs, outputs, UTXO data and partial signatures of all other + signers. + */ + bytes funded_psbt = 1; + + /* + The name of the account to finalize the PSBT with. If empty, the default + wallet account is used. + */ + string account = 5; +} +message FinalizePsbtResponse { + // The fully signed and finalized transaction in PSBT format. + bytes signed_psbt = 1; + + // The fully signed and finalized transaction in the raw wire format. + bytes raw_final_tx = 2; +} + +message ListLeasesRequest { +} + +message ListLeasesResponse { + // The list of currently leased utxos. + repeated UtxoLease locked_utxos = 1; +} diff --git a/proto/walletunlocker.proto b/proto/walletunlocker.proto new file mode 100644 index 000000000..faf3c0892 --- /dev/null +++ b/proto/walletunlocker.proto @@ -0,0 +1,338 @@ +syntax = "proto3"; + +import "lightning.proto"; + +package lnrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// WalletUnlocker is a service that is used to set up a wallet password for +// lnd at first startup, and unlock a previously set up wallet. +service WalletUnlocker { + /* + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. + */ + rpc GenSeed (GenSeedRequest) returns (GenSeedResponse); + + /* + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet (InitWalletRequest) returns (InitWalletResponse); + + /* lncli: `unlock` + UnlockWallet is used at startup of lnd to provide a password to unlock + the wallet database. + */ + rpc UnlockWallet (UnlockWalletRequest) returns (UnlockWalletResponse); + + /* lncli: `changepassword` + ChangePassword changes the password of the encrypted wallet. This will + automatically unlock the wallet database if successful. + */ + rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse); +} + +message GenSeedRequest { + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 1; + + /* + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + When using REST, this field must be encoded as base64. + */ + bytes seed_entropy = 2; +} +message GenSeedResponse { + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + + /* + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; +} + +message InitWalletRequest { + /* + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. When using REST, this field + must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 3; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 4; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 5; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 6; + + /* + extended_master_key is an alternative to specifying cipher_seed_mnemonic and + aezeed_passphrase. Instead of deriving the master root key from the entropy + of an aezeed cipher seed, the given extended master root key is used + directly as the wallet's master key. This allows users to import/use a + master key from another wallet. When doing so, lnd still uses its default + SegWit only (BIP49/84) derivation paths and funds from custom/non-default + derivation paths will not automatically appear in the on-chain wallet. Using + an 'xprv' instead of an aezeed also has the disadvantage that the wallet's + birthday is not known as that is an information that's only encoded in the + aezeed, not the xprv. Therefore a birthday needs to be specified in + extended_master_key_birthday_timestamp or a "safe" default value will be + used. + */ + string extended_master_key = 7; + + /* + extended_master_key_birthday_timestamp is the optional unix timestamp in + seconds to use as the wallet's birthday when using an extended master key + to restore the wallet. lnd will only start scanning for funds in blocks that + are after the birthday which can speed up the process significantly. If the + birthday is not known, this should be left at its default value of 0 in + which case lnd will start scanning from the first SegWit block (481824 on + mainnet). + */ + uint64 extended_master_key_birthday_timestamp = 8; + + /* + watch_only is the third option of initializing a wallet: by importing + account xpubs only and therefore creating a watch-only wallet that does not + contain any private keys. That means the wallet won't be able to sign for + any of the keys and _needs_ to be run with a remote signer that has the + corresponding private keys and can serve signing RPC requests. + */ + WatchOnly watch_only = 9; + + /* + macaroon_root_key is an optional 32 byte macaroon root key that can be + provided when initializing the wallet rather than letting lnd generate one + on its own. + */ + bytes macaroon_root_key = 10; +} +message InitWalletResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after creating the wallet. If the stateless_init parameter was set to true, + this is the ONLY copy of the macaroon and MUST be stored safely by the + caller. Otherwise a copy of this macaroon is also persisted on disk by the + daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} + +message WatchOnly { + /* + The unix timestamp in seconds of when the master key was created. lnd will + only start scanning for funds in blocks that are after the birthday which + can speed up the process significantly. If the birthday is not known, this + should be left at its default value of 0 in which case lnd will start + scanning from the first SegWit block (481824 on mainnet). + */ + uint64 master_key_birthday_timestamp = 1; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public keys were derived from. This may be + required by some hardware wallets for proper identification and signing. The + bytes must be in big-endian order. + */ + bytes master_key_fingerprint = 2; + + /* + The list of accounts to import. There _must_ be an account for all of lnd's + main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + coin type is always 0, even for testnet/regtest) and lnd's internal key + scope (m/1017'/'/'), where account is the key family as + defined in `keychain/derivation.go` (currently indices 0 to 9). + */ + repeated WatchOnlyAccount accounts = 3; +} + +message WatchOnlyAccount { + /* + Purpose is the first number in the derivation path, must be either 49, 84 + or 1017. + */ + uint32 purpose = 1; + + /* + Coin type is the second number in the derivation path, this is _always_ 0 + for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + testnet or regtest. + */ + uint32 coin_type = 2; + + /* + Account is the third number in the derivation path. For purposes 49 and 84 + at least the default account (index 0) needs to be created but optional + additional accounts are allowed. For purpose 1017 there needs to be exactly + one account for each of the key families defined in `keychain/derivation.go` + (currently indices 0 to 9) + */ + uint32 account = 3; + + /* + The extended public key at depth 3 for the given account. + */ + string xpub = 4; +} + +message UnlockWalletRequest { + /* + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. When using REST, this field must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 2; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 3; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its file system. + */ + bool stateless_init = 4; +} +message UnlockWalletResponse { +} + +message ChangePasswordRequest { + /* + current_password should be the current valid passphrase used to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes current_password = 1; + + /* + new_password should be the new passphrase that will be needed to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes new_password = 2; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 3; + + /* + new_macaroon_root_key is an optional argument instructing the daemon to + rotate the macaroon root key when set to true. This will invalidate all + previously generated macaroons. + */ + bool new_macaroon_root_key = 4; +} +message ChangePasswordResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after rotating the macaroon root key. If both the stateless_init and + new_macaroon_root_key parameter were set to true, this is the ONLY copy of + the macaroon that was created from the new root key and MUST be stored + safely by the caller. Otherwise a copy of this macaroon is also persisted on + disk by the daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} diff --git a/proto/watchtowerrpc/watchtower.proto b/proto/watchtowerrpc/watchtower.proto new file mode 100644 index 000000000..f3be62163 --- /dev/null +++ b/proto/watchtowerrpc/watchtower.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package watchtowerrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"; + +// Watchtower is a service that grants access to the watchtower server +// functionality of the daemon. +service Watchtower { + /* lncli: tower info + GetInfo returns general information concerning the companion watchtower + including its public key and URIs where the server is currently + listening for clients. + */ + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); +} + +message GetInfoRequest { +} + +message GetInfoResponse { + // The public key of the watchtower. + bytes pubkey = 1; + + // The listening addresses of the watchtower. + repeated string listeners = 2; + + // The URIs of the watchtower. + repeated string uris = 3; +} diff --git a/proto/wtclientrpc/wtclient.proto b/proto/wtclientrpc/wtclient.proto new file mode 100644 index 000000000..413f61afa --- /dev/null +++ b/proto/wtclientrpc/wtclient.proto @@ -0,0 +1,224 @@ +syntax = "proto3"; + +package wtclientrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"; + +// WatchtowerClient is a service that grants access to the watchtower client +// functionality of the daemon. +service WatchtowerClient { + /* + AddTower adds a new watchtower reachable at the given address and + considers it for new sessions. If the watchtower already exists, then + any new addresses included will be considered when dialing it for + session negotiations and backups. + */ + rpc AddTower (AddTowerRequest) returns (AddTowerResponse); + + /* + RemoveTower removes a watchtower from being considered for future session + negotiations and from being used for any subsequent backups until it's added + again. If an address is provided, then this RPC only serves as a way of + removing the address from the watchtower instead. + */ + rpc RemoveTower (RemoveTowerRequest) returns (RemoveTowerResponse); + + // ListTowers returns the list of watchtowers registered with the client. + rpc ListTowers (ListTowersRequest) returns (ListTowersResponse); + + // GetTowerInfo retrieves information for a registered watchtower. + rpc GetTowerInfo (GetTowerInfoRequest) returns (Tower); + + // Stats returns the in-memory statistics of the client since startup. + rpc Stats (StatsRequest) returns (StatsResponse); + + // Policy returns the active watchtower client policy configuration. + rpc Policy (PolicyRequest) returns (PolicyResponse); +} + +message AddTowerRequest { + // The identifying public key of the watchtower to add. + bytes pubkey = 1; + + // A network address the watchtower is reachable over. + string address = 2; +} + +message AddTowerResponse { +} + +message RemoveTowerRequest { + // The identifying public key of the watchtower to remove. + bytes pubkey = 1; + + /* + If set, then the record for this address will be removed, indicating that is + is stale. Otherwise, the watchtower will no longer be used for future + session negotiations and backups. + */ + string address = 2; +} + +message RemoveTowerResponse { +} + +message GetTowerInfoRequest { + // The identifying public key of the watchtower to retrieve information for. + bytes pubkey = 1; + + // Whether we should include sessions with the watchtower in the response. + bool include_sessions = 2; + + // Whether to exclude exhausted sessions in the response info. This option + // is only meaningful if include_sessions is true. + bool exclude_exhausted_sessions = 3; +} + +message TowerSession { + /* + The total number of successful backups that have been made to the + watchtower session. + */ + uint32 num_backups = 1; + + /* + The total number of backups in the session that are currently pending to be + acknowledged by the watchtower. + */ + uint32 num_pending_backups = 2; + + // The maximum number of backups allowed by the watchtower session. + uint32 max_backups = 3; + + /* + Deprecated, use sweep_sat_per_vbyte. + The fee rate, in satoshis per vbyte, that will be used by the watchtower for + the justice transaction in the event of a channel breach. + */ + uint32 sweep_sat_per_byte = 4 [deprecated = true]; + + /* + The fee rate, in satoshis per vbyte, that will be used by the watchtower for + the justice transaction in the event of a channel breach. + */ + uint32 sweep_sat_per_vbyte = 5; +} + +message Tower { + // The identifying public key of the watchtower. + bytes pubkey = 1; + + // The list of addresses the watchtower is reachable over. + repeated string addresses = 2; + + // Deprecated, use the active_session_candidate field under the + // correct identifier in the client_type map. + // Whether the watchtower is currently a candidate for new sessions. + bool active_session_candidate = 3 [deprecated = true]; + + // Deprecated, use the num_sessions field under the correct identifier + // in the client_type map. + // The number of sessions that have been negotiated with the watchtower. + uint32 num_sessions = 4 [deprecated = true]; + + // Deprecated, use the sessions field under the correct identifier in the + // client_type map. + // The list of sessions that have been negotiated with the watchtower. + repeated TowerSession sessions = 5 [deprecated = true]; + + // A list sessions held with the tower. + repeated TowerSessionInfo session_info = 6; +} + +message TowerSessionInfo { + // Whether the watchtower is currently a candidate for new sessions. + bool active_session_candidate = 1; + + // The number of sessions that have been negotiated with the watchtower. + uint32 num_sessions = 2; + + // The list of sessions that have been negotiated with the watchtower. + repeated TowerSession sessions = 3; + + // The session's policy type. + PolicyType policy_type = 4; +} + +message ListTowersRequest { + // Whether we should include sessions with the watchtower in the response. + bool include_sessions = 1; + + // Whether to exclude exhausted sessions in the response info. This option + // is only meaningful if include_sessions is true. + bool exclude_exhausted_sessions = 2; +} + +message ListTowersResponse { + // The list of watchtowers available for new backups. + repeated Tower towers = 1; +} + +message StatsRequest { +} + +message StatsResponse { + /* + The total number of backups made to all active and exhausted watchtower + sessions. + */ + uint32 num_backups = 1; + + /* + The total number of backups that are pending to be acknowledged by all + active and exhausted watchtower sessions. + */ + uint32 num_pending_backups = 2; + + /* + The total number of backups that all active and exhausted watchtower + sessions have failed to acknowledge. + */ + uint32 num_failed_backups = 3; + + // The total number of new sessions made to watchtowers. + uint32 num_sessions_acquired = 4; + + // The total number of watchtower sessions that have been exhausted. + uint32 num_sessions_exhausted = 5; +} + +enum PolicyType { + // Selects the policy from the legacy tower client. + LEGACY = 0; + + // Selects the policy from the anchor tower client. + ANCHOR = 1; +} + +message PolicyRequest { + /* + The client type from which to retrieve the active offering policy. + */ + PolicyType policy_type = 1; +} + +message PolicyResponse { + /* + The maximum number of updates each session we negotiate with watchtowers + should allow. + */ + uint32 max_updates = 1; + + /* + Deprecated, use sweep_sat_per_vbyte. + The fee rate, in satoshis per vbyte, that will be used by watchtowers for + justice transactions in response to channel breaches. + */ + uint32 sweep_sat_per_byte = 2 [deprecated = true]; + + /* + The fee rate, in satoshis per vbyte, that will be used by watchtowers for + justice transactions in response to channel breaches. + */ + uint32 sweep_sat_per_vbyte = 3; +} diff --git a/terminal.go b/terminal.go index 3d0bc964d..cd2795fdf 100644 --- a/terminal.go +++ b/terminal.go @@ -196,18 +196,39 @@ type LightningTerminal struct { restHandler http.Handler restCancel func() + + mobileListener *bufconn.Listener + mobileReadyChan chan struct{} } // New creates a new instance of the lightning-terminal daemon. -func New() *LightningTerminal { +func New(listener *bufconn.Listener, readyChan chan struct{}) *LightningTerminal { return &LightningTerminal{ - statusMgr: status.NewStatusManager(), + statusMgr: status.NewStatusManager(), + mobileListener: listener, + mobileReadyChan: readyChan, } } // Run starts everything and then blocks until either the application is shut // down or a critical error happens. func (g *LightningTerminal) Run() error { + // DEBUG: log to file + stdoutPath := "/data/user/0/com.liminal/files/stdout.log" + stderrPath := "/data/user/0/com.liminal/files/stderr.log" + stdoutFile, err := os.OpenFile(stdoutPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + os.Exit(1) + } + stderrFile, err := os.OpenFile(stderrPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + os.Exit(1) + } + os.Stdout = stdoutFile + os.Stderr = stderrFile + defer stdoutFile.Close() + defer stderrFile.Close() + // Hook interceptor for os signals. shutdownInterceptor, err := signal.Intercept() if err != nil { @@ -283,6 +304,23 @@ func (g *LightningTerminal) Run() error { litrpc.RegisterProxyServer(g.rpcProxy.grpcServer, g.rpcProxy) litrpc.RegisterStatusServer(g.rpcProxy.grpcServer, g.statusMgr) + // Set up the in-memory listener if one was defined + go func() { + if g.mobileListener != nil { + tlsConfig, err := buildTLSConfigForHttp2(g.cfg) + if err != nil { + log.Infof("Failed to create mobileListener tlsConfig: %v", err) + os.Exit(1) + } + tlsListener := tls.NewListener(g.mobileListener, tlsConfig) + err = g.rpcProxy.grpcServer.Serve(tlsListener) + if err != nil { + log.Errorf("GRPCSERVER.SERVE FAILED! %v", err) + os.Exit(1) + } + } + }() + // Start the main web server that dispatches requests either to the // static UI file server or the RPC proxy. This makes it possible to // unlock lnd through the UI.