feat: add CLN backend (#2026)

* feat: add CLN as a lnclient backend

* feat: add hold invoice support for CLN backend

* fix: reduce CLN form to just addresses and lightning dir

* feat: add README for CLN grpc go code generation

* fix: cln backend does not support keysend with given preimages

* fix: hold invoice notifications in CLN backend

* fix: remove dead code in CLN backend from ListTransactions

* fix: env example CLN_ADDRESS_HOLD with different port to show it's a different service

* fix: cleanup of CLN ressources in all cases

* fix: cln backend's GetNetworkGraph only fetches specified nodeId's

* fix: cln backend: only advertise hold methods for nip47 if hold plugin enabled

* fix: cln's Shutdown should not stop CLN itself

* fix: relax the LND README line regarding env configuration

* fix: more nil checks in clnInvoiceToTransaction

* fix: prevent feerate overflow in CLN's RedeemOnchainFunds

* fix: don't access nil errors for empty reponses of certain CLN methods

* fix: nil instead of empty string in cln's GetNetworkGraph return types

* fix: nil checks for created_at in cln's clnInvoiceToTransaction

* fix: set minimum tls version to 1.2 for cln backend grpc connections

* fix: cln's subscribeOpenHoldInvoices doesn't give up as fast

* fix: deduplicate graph edges in cln's GetNetworkGraph

* fix: print the error string, not pointer address, in cln's ListChannels

* fix: remove cln's ListTransactions completely

* fix: use ListPeers instead of ListPeerChannels in cln's ListPeers

* feat: cln's MakeHoldInvoice supports minCltvExpiryDelta

* fix: use named return err in NewCLNService

* fix: cln listpeers log message

* fix: incorrect import

* fix: compile errors after rename

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
daywalker90 2026-05-03 21:36:13 +02:00 committed by GitHub
parent fe1f338ed3
commit 28706ecf31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 58303 additions and 3 deletions

View file

@ -40,4 +40,13 @@ FRONTEND_URL=http://localhost:5173
# Boltz API
#BOLTZ_API=https://api.testnet.boltz.exchange
#NETWORK=testnet
#NETWORK=testnet
# CLN Backend
#LN_BACKEND_TYPE=CLN
# CLN's grpc-host:grpc-port
#CLN_ADDRESS=127.0.0.1:9737
# CLN's lightning directory containing the grpc certificates, usually ~/.lightning/<network>/
#CLN_LIGHTNING_DIR=/path/to/.lightning/bitcoin
# CLN's hold plugin https://github.com/BoltzExchange/hold gRPC address
#CLN_ADDRESS_HOLD=127.0.0.1:9738

View file

@ -29,6 +29,7 @@ By default Alby Hub uses the embedded LDK based lightning node. Optionally it ca
- LND
- Phoenixd
- Cashu
- CLN
- want more? please open an issue.
## Development
@ -210,9 +211,22 @@ Migration of the database is currently experimental. Please make a backup before
- `ENABLE_ADVANCED_SETUP`: set to `false` to force a specific backend type (combined with backend parameters below)
### CLN Backend parameters
Can be configured via env or the UI
- `LN_BACKEND_TYPE`: CLN
- `CLN_ADDRESS`: the CLN grpc address (grpc-host and grpc-port), e.g. `127.0.0.1:9737`
- `CLN_LIGHTNING_DIR`: CLN's lightning directory containing the grpc certificates, usually `~/.lightning/<network>`
Optional for hold invoice methods support:
- `CLN_ADDRESS_HOLD`: the CLN hold plugin grpc address (grpc-host and grpc-port), e.g. `127.0.0.1:9738`
If you are copying the certificates to another machine make sure you get the `ca.pem`, `client.pem` and `client-key.pem` from the lightning directory and optionally from the `hold` directory inside the lightning directory and keep the sub-directory structure of the hold directory.
### LND Backend parameters
Currently only LND can be configured via env. Other node types must be configured via the UI.
LND can be configured via env. Other node types may need to be configured via the UI.
_To configure via env, the following parameters must be provided:_

View file

@ -1644,6 +1644,30 @@ func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
}
}
if setupRequest.CLNAddress != "" {
err = api.cfg.SetUpdate("CLNAddress", setupRequest.CLNAddress, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save CLN address")
return err
}
}
if setupRequest.CLNLightningDir != "" {
err = api.cfg.SetUpdate("CLNLightningDir", setupRequest.CLNLightningDir, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save CLN Lightning directory path")
return err
}
}
if setupRequest.CLNAddressHold != "" {
err = api.cfg.SetUpdate("CLNAddressHold", setupRequest.CLNAddressHold, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save cln hold plugin address")
return err
}
}
return nil
}

View file

@ -277,6 +277,11 @@ type SetupRequest struct {
// Cashu fields
CashuMintUrl string `json:"cashuMintUrl"`
// CLN fields
CLNAddress string `json:"clnAddress"`
CLNLightningDir string `json:"clnLightningDir"`
CLNAddressHold string `json:"clnAddressHold"`
}
type CreateAppResponse struct {

View file

@ -112,6 +112,26 @@ func (cfg *config) init(env *AppConfig) error {
}
}
// CLN specific to support env variables
if cfg.Env.CLNAddress != "" {
err := cfg.SetUpdate("CLNAddress", cfg.Env.CLNAddress, "")
if err != nil {
return err
}
}
if cfg.Env.CLNLightningDir != "" {
err := cfg.SetUpdate("CLNLightningDir", cfg.Env.CLNLightningDir, "")
if err != nil {
return err
}
}
if cfg.Env.CLNAddressHold != "" {
err := cfg.SetUpdate("CLNAddressHold", cfg.Env.CLNAddressHold, "")
if err != nil {
return err
}
}
return nil
}

View file

@ -5,6 +5,7 @@ const (
LDKBackendType = "LDK"
PhoenixBackendType = "PHOENIX"
CashuBackendType = "CASHU"
CLNBackendType = "CLN"
)
const (
@ -59,6 +60,9 @@ type AppConfig struct {
LogDBQueries bool `envconfig:"LOG_DB_QUERIES" default:"false"`
BoltzApi string `envconfig:"BOLTZ_API" default:"https://api.boltz.exchange"`
HideUpdateBanner bool `envconfig:"HIDE_UPDATE_BANNER" default:"false"`
CLNAddress string `envconfig:"CLN_ADDRESS"`
CLNLightningDir string `envconfig:"CLN_LIGHTNING_DIR"`
CLNAddressHold string `envconfig:"CLN_ADDRESS_HOLD"`
}
func (c *AppConfig) IsDefaultClientId() bool {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -27,4 +27,9 @@ export const backendTypeConfigs: Record<BackendType, BackendTypeConfig> = {
hasChannelManagement: false,
hasNodeBackup: false,
},
CLN: {
hasMnemonic: false,
hasChannelManagement: true,
hasNodeBackup: false,
},
};

View file

@ -62,6 +62,7 @@ import { SetupFinish } from "src/screens/setup/SetupFinish";
import { SetupNode } from "src/screens/setup/SetupNode";
import { SetupPassword } from "src/screens/setup/SetupPassword";
import { SetupSecurity } from "src/screens/setup/SetupSecurity";
import { CLNForm } from "src/screens/setup/node/CLNForm";
import { CashuForm } from "src/screens/setup/node/CashuForm";
import { LDKForm } from "src/screens/setup/node/LDKForm";
import { LNDForm } from "src/screens/setup/node/LNDForm";
@ -548,6 +549,10 @@ const routes: RouteObject[] = [
path: "ldk",
element: <LDKForm />,
},
{
path: "cln",
element: <CLNForm />,
},
{
path: "preset",
element: <PresetNodeForm />,

View file

@ -9,6 +9,7 @@ import { cn } from "src/lib/utils";
import { BackendType } from "src/types";
import cashu from "src/assets/images/node/cashu.png";
import cln from "src/assets/images/node/cln.png";
import lnd from "src/assets/images/node/lnd.png";
import { backendTypeConfigs } from "src/lib/backendType";
import useSetupStore from "src/state/SetupStore";
@ -37,6 +38,10 @@ const backendTypeDisplayConfigs: Partial<
title: "Cashu Mint",
icon: <img src={cashu} />,
},
CLN: {
title: "CLN",
icon: <img src={cln} />,
},
};
const backendTypeDisplayConfigList = Object.entries(

View file

@ -68,6 +68,7 @@ export function SetupSecurity() {
</span>
</div>
{store.nodeInfo.backendType === "LND" ||
store.nodeInfo.backendType === "CLN" ||
store.nodeInfo.backendType === "PHOENIX" ? (
<div className="flex gap-3 items-center">
<div className="shrink-0">

View file

@ -0,0 +1,86 @@
import React from "react";
import { useNavigate } from "react-router";
import Container from "src/components/Container";
import TwoColumnLayoutHeader from "src/components/TwoColumnLayoutHeader";
import { Button } from "src/components/ui/button";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import useSetupStore from "src/state/SetupStore";
export function CLNForm() {
const navigate = useNavigate();
const setupStore = useSetupStore();
const [clnAddress, setClnAddress] = React.useState<string>(
setupStore.nodeInfo.clnAddress || ""
);
const [clnLightningDir, setClnLightningDir] = React.useState<string>(
setupStore.nodeInfo.clnLightningDir || ""
);
const [clnAddressHold, setClnAddressHold] = React.useState<string>(
setupStore.nodeInfo.clnAddressHold || ""
);
// TODO: proper onboarding
function onSubmit(e: React.FormEvent) {
e.preventDefault();
handleSubmit({
clnAddress,
clnLightningDir,
clnAddressHold,
});
}
async function handleSubmit(data: object) {
setupStore.updateNodeInfo({
backendType: "CLN",
...data,
});
navigate("/setup/security");
}
return (
<Container>
<TwoColumnLayoutHeader
title="Configure CLN"
description="Fill out wallet details to finish setup."
/>
<form className="w-full grid gap-5 mt-6" onSubmit={onSubmit}>
<div className="grid gap-1.5">
<Label htmlFor="cln-address">CLN Address (GRPC)</Label>
<Input
required
name="cln-address"
onChange={(e) => setClnAddress(e.target.value)}
value={clnAddress}
id="cln-address"
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="cln-lightning-dir">
CLN Lightning directory (full path)
</Label>
<Input
required
name="cln-lightning-dir"
onChange={(e) => setClnLightningDir(e.target.value)}
value={clnLightningDir}
type="text"
id="cln-lightning-dir"
/>
</div>
<div className="grid gap-1.5">
<Label htmlFor="cln-address-hold">
(optional) CLN hold plugin Address (GRPC)
</Label>
<Input
name="cln-address-hold"
onChange={(e) => setClnAddressHold(e.target.value)}
value={clnAddressHold}
id="cln-address-hold"
/>
</div>
<Button>Next</Button>
</form>
</Container>
);
}

View file

@ -11,7 +11,7 @@ import {
WalletMinimalIcon,
} from "lucide-react";
export type BackendType = "LND" | "LDK" | "PHOENIX" | "CASHU";
export type BackendType = "LND" | "LDK" | "PHOENIX" | "CASHU" | "CLN";
export type Nip47RequestMethod =
| "get_info"
@ -457,6 +457,10 @@ export type SetupNodeInfo = Partial<{
phoenixdAddress?: string;
phoenixdAuthorization?: string;
clnAddress?: string;
clnLightningDir?: string;
clnAddressHold?: string;
}>;
export type LSPType = "LSPS1";

75
lnclient/cln/README.md Normal file
View file

@ -0,0 +1,75 @@
## Generating Go gRPC Code
The Go gRPC bindings for Core Lightning (CLN) are generated from proto files that live
in the **lightning** repository (`cln-grpc`) and in the **hold** plugin repository.
The generated Go files are written into the **hub** repository under `lnclient/cln/clngrpc` and `lnclient/cln/clngrpc_hold`.
### Prerequisites
Make sure the following tools are installed:
```bash
# protoc (>= 3.20 recommended)
protoc --version
# Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
Ensure `$GOPATH/bin` is in your `PATH`:
```bash
export PATH="$PATH:$(go env GOPATH)/bin"
```
This guide assumes the following directory structure:
```
~/dev/
├── lightning/
│ └── cln-grpc/
│ └── proto/
│ ├── node.proto
│ ├── primitives.proto
│ └── ...
├── hub/
| └── lnclient/
| └── cln/
| └── clngrpc/
| └── clngrpc_hold/
└── hold/
└── protos/
└── hold.proto
```
### Generating Go code
From the hub repository root, run:
```bash
protoc \
--proto_path=../lightning/cln-grpc/proto \
--go_out=./lnclient/cln/clngrpc \
--go_opt=paths=source_relative \
--go_opt=Mprimitives.proto=github.com/getAlby/hub/lnclient/cln/clngrpc \
--go_opt=Mnode.proto=github.com/getAlby/hub/lnclient/cln/clngrpc \
--go-grpc_out=./lnclient/cln/clngrpc \
--go-grpc_opt=paths=source_relative \
../lightning/cln-grpc/proto/node.proto \
../lightning/cln-grpc/proto/primitives.proto
```
and if you have the hold plugin repo:
```bash
protoc \
--proto_path=../hold/protos \
--go_out=./lnclient/cln/clngrpc_hold \
--go_opt=paths=source_relative \
--go_opt=Mhold.proto=github.com/getAlby/hub/lnclient/cln/clngrpc_hold \
--go-grpc_out=./lnclient/cln/clngrpc_hold \
--go-grpc_opt=paths=source_relative \
../hold/protos/hold.proto
```

1939
lnclient/cln/cln.go Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,466 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc v3.21.12
// source: hold.proto
package clngrpc_hold
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Hold_GetInfo_FullMethodName = "/hold.Hold/GetInfo"
Hold_Invoice_FullMethodName = "/hold.Hold/Invoice"
Hold_Inject_FullMethodName = "/hold.Hold/Inject"
Hold_List_FullMethodName = "/hold.Hold/List"
Hold_Settle_FullMethodName = "/hold.Hold/Settle"
Hold_Cancel_FullMethodName = "/hold.Hold/Cancel"
Hold_Clean_FullMethodName = "/hold.Hold/Clean"
Hold_Track_FullMethodName = "/hold.Hold/Track"
Hold_TrackAll_FullMethodName = "/hold.Hold/TrackAll"
Hold_OnionMessages_FullMethodName = "/hold.Hold/OnionMessages"
)
// HoldClient is the client API for Hold service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type HoldClient interface {
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
Invoice(ctx context.Context, in *InvoiceRequest, opts ...grpc.CallOption) (*InvoiceResponse, error)
Inject(ctx context.Context, in *InjectRequest, opts ...grpc.CallOption) (*InjectResponse, error)
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
Settle(ctx context.Context, in *SettleRequest, opts ...grpc.CallOption) (*SettleResponse, error)
Cancel(ctx context.Context, in *CancelRequest, opts ...grpc.CallOption) (*CancelResponse, error)
// Cleans cancelled invoices
Clean(ctx context.Context, in *CleanRequest, opts ...grpc.CallOption) (*CleanResponse, error)
Track(ctx context.Context, in *TrackRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TrackResponse], error)
TrackAll(ctx context.Context, in *TrackAllRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TrackAllResponse], error)
OnionMessages(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[OnionMessageResponse, OnionMessage], error)
}
type holdClient struct {
cc grpc.ClientConnInterface
}
func NewHoldClient(cc grpc.ClientConnInterface) HoldClient {
return &holdClient{cc}
}
func (c *holdClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetInfoResponse)
err := c.cc.Invoke(ctx, Hold_GetInfo_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Invoice(ctx context.Context, in *InvoiceRequest, opts ...grpc.CallOption) (*InvoiceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InvoiceResponse)
err := c.cc.Invoke(ctx, Hold_Invoice_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Inject(ctx context.Context, in *InjectRequest, opts ...grpc.CallOption) (*InjectResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InjectResponse)
err := c.cc.Invoke(ctx, Hold_Inject_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListResponse)
err := c.cc.Invoke(ctx, Hold_List_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Settle(ctx context.Context, in *SettleRequest, opts ...grpc.CallOption) (*SettleResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SettleResponse)
err := c.cc.Invoke(ctx, Hold_Settle_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Cancel(ctx context.Context, in *CancelRequest, opts ...grpc.CallOption) (*CancelResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CancelResponse)
err := c.cc.Invoke(ctx, Hold_Cancel_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Clean(ctx context.Context, in *CleanRequest, opts ...grpc.CallOption) (*CleanResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CleanResponse)
err := c.cc.Invoke(ctx, Hold_Clean_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *holdClient) Track(ctx context.Context, in *TrackRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TrackResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Hold_ServiceDesc.Streams[0], Hold_Track_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[TrackRequest, TrackResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_TrackClient = grpc.ServerStreamingClient[TrackResponse]
func (c *holdClient) TrackAll(ctx context.Context, in *TrackAllRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TrackAllResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Hold_ServiceDesc.Streams[1], Hold_TrackAll_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[TrackAllRequest, TrackAllResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_TrackAllClient = grpc.ServerStreamingClient[TrackAllResponse]
func (c *holdClient) OnionMessages(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[OnionMessageResponse, OnionMessage], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Hold_ServiceDesc.Streams[2], Hold_OnionMessages_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[OnionMessageResponse, OnionMessage]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_OnionMessagesClient = grpc.BidiStreamingClient[OnionMessageResponse, OnionMessage]
// HoldServer is the server API for Hold service.
// All implementations must embed UnimplementedHoldServer
// for forward compatibility.
type HoldServer interface {
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
Invoice(context.Context, *InvoiceRequest) (*InvoiceResponse, error)
Inject(context.Context, *InjectRequest) (*InjectResponse, error)
List(context.Context, *ListRequest) (*ListResponse, error)
Settle(context.Context, *SettleRequest) (*SettleResponse, error)
Cancel(context.Context, *CancelRequest) (*CancelResponse, error)
// Cleans cancelled invoices
Clean(context.Context, *CleanRequest) (*CleanResponse, error)
Track(*TrackRequest, grpc.ServerStreamingServer[TrackResponse]) error
TrackAll(*TrackAllRequest, grpc.ServerStreamingServer[TrackAllResponse]) error
OnionMessages(grpc.BidiStreamingServer[OnionMessageResponse, OnionMessage]) error
mustEmbedUnimplementedHoldServer()
}
// UnimplementedHoldServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedHoldServer struct{}
func (UnimplementedHoldServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedHoldServer) Invoice(context.Context, *InvoiceRequest) (*InvoiceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Invoice not implemented")
}
func (UnimplementedHoldServer) Inject(context.Context, *InjectRequest) (*InjectResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Inject not implemented")
}
func (UnimplementedHoldServer) List(context.Context, *ListRequest) (*ListResponse, error) {
return nil, status.Error(codes.Unimplemented, "method List not implemented")
}
func (UnimplementedHoldServer) Settle(context.Context, *SettleRequest) (*SettleResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Settle not implemented")
}
func (UnimplementedHoldServer) Cancel(context.Context, *CancelRequest) (*CancelResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Cancel not implemented")
}
func (UnimplementedHoldServer) Clean(context.Context, *CleanRequest) (*CleanResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Clean not implemented")
}
func (UnimplementedHoldServer) Track(*TrackRequest, grpc.ServerStreamingServer[TrackResponse]) error {
return status.Error(codes.Unimplemented, "method Track not implemented")
}
func (UnimplementedHoldServer) TrackAll(*TrackAllRequest, grpc.ServerStreamingServer[TrackAllResponse]) error {
return status.Error(codes.Unimplemented, "method TrackAll not implemented")
}
func (UnimplementedHoldServer) OnionMessages(grpc.BidiStreamingServer[OnionMessageResponse, OnionMessage]) error {
return status.Error(codes.Unimplemented, "method OnionMessages not implemented")
}
func (UnimplementedHoldServer) mustEmbedUnimplementedHoldServer() {}
func (UnimplementedHoldServer) testEmbeddedByValue() {}
// UnsafeHoldServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to HoldServer will
// result in compilation errors.
type UnsafeHoldServer interface {
mustEmbedUnimplementedHoldServer()
}
func RegisterHoldServer(s grpc.ServiceRegistrar, srv HoldServer) {
// If the following call panics, it indicates UnimplementedHoldServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Hold_ServiceDesc, srv)
}
func _Hold_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).GetInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_GetInfo_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).GetInfo(ctx, req.(*GetInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Invoice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InvoiceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).Invoice(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_Invoice_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).Invoice(ctx, req.(*InvoiceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Inject_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InjectRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).Inject(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_Inject_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).Inject(ctx, req.(*InjectRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).List(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_List_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).List(ctx, req.(*ListRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Settle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SettleRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).Settle(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_Settle_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).Settle(ctx, req.(*SettleRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CancelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).Cancel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_Cancel_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).Cancel(ctx, req.(*CancelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Clean_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CleanRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HoldServer).Clean(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Hold_Clean_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HoldServer).Clean(ctx, req.(*CleanRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Hold_Track_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TrackRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(HoldServer).Track(m, &grpc.GenericServerStream[TrackRequest, TrackResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_TrackServer = grpc.ServerStreamingServer[TrackResponse]
func _Hold_TrackAll_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TrackAllRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(HoldServer).TrackAll(m, &grpc.GenericServerStream[TrackAllRequest, TrackAllResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_TrackAllServer = grpc.ServerStreamingServer[TrackAllResponse]
func _Hold_OnionMessages_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(HoldServer).OnionMessages(&grpc.GenericServerStream[OnionMessageResponse, OnionMessage]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Hold_OnionMessagesServer = grpc.BidiStreamingServer[OnionMessageResponse, OnionMessage]
// Hold_ServiceDesc is the grpc.ServiceDesc for Hold service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Hold_ServiceDesc = grpc.ServiceDesc{
ServiceName: "hold.Hold",
HandlerType: (*HoldServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetInfo",
Handler: _Hold_GetInfo_Handler,
},
{
MethodName: "Invoice",
Handler: _Hold_Invoice_Handler,
},
{
MethodName: "Inject",
Handler: _Hold_Inject_Handler,
},
{
MethodName: "List",
Handler: _Hold_List_Handler,
},
{
MethodName: "Settle",
Handler: _Hold_Settle_Handler,
},
{
MethodName: "Cancel",
Handler: _Hold_Cancel_Handler,
},
{
MethodName: "Clean",
Handler: _Hold_Clean_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Track",
Handler: _Hold_Track_Handler,
ServerStreams: true,
},
{
StreamName: "TrackAll",
Handler: _Hold_TrackAll_Handler,
ServerStreams: true,
},
{
StreamName: "OnionMessages",
Handler: _Hold_OnionMessages_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "hold.proto",
}

View file

@ -22,6 +22,7 @@ import (
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/lnclient/cashu"
"github.com/getAlby/hub/lnclient/cln"
"github.com/getAlby/hub/lnclient/ldk"
"github.com/getAlby/hub/lnclient/lnd"
"github.com/getAlby/hub/lnclient/phoenixd"
@ -372,6 +373,11 @@ func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) e
cashuWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "cashu")
lnClient, err = cashu.NewCashuService(svc.cfg, cashuWorkdir, mnemonic, cashuMintUrl)
case config.CLNBackendType:
CLNAddress, _ := svc.cfg.Get("CLNAddress", encryptionKey)
CLNLightningDir, _ := svc.cfg.Get("CLNLightningDir", encryptionKey)
CLNAddressHold, _ := svc.cfg.Get("CLNAddressHold", encryptionKey)
lnClient, err = cln.NewCLNService(ctx, svc.eventPublisher, CLNAddress, CLNLightningDir, CLNAddressHold)
default:
logger.Logger.WithField("backend_type", lnBackend).Error("Unsupported LNBackendType")
return fmt.Errorf("unsupported backend type: %s", lnBackend)