Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
FROM node:20-alpine AS frontend
ARG BUILDPLATFORM

FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend

# Set the base path for the frontend build
# This can be overridden at build time with --build-arg BASE_PATH=<url> e.g. --build-arg BASE_PATH=/hub
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Go to the [Deploy it yourself](#deploy-it-yourself) section below.
By default Alby Hub uses the embedded LDK based lightning node. Optionally it can be configured to use an external node:

- LND
- LDK Server
- Phoenixd
- Cashu
- CLN
Expand Down Expand Up @@ -236,6 +237,58 @@ _To configure via env, the following parameters must be provided:_
- `LND_CERT_FILE`: the location where LND's `tls.cert` file can be found (used with the LND backend)
- `LND_MACAROON_FILE`: the location where LND's `admin.macaroon` file can be found (used with the LND backend)

### LDK Server backend parameters

LDK Server can be configured via env or the UI.

To configure via env, provide:

- `LN_BACKEND_TYPE`: `LDK_SERVER`
- `LDK_SERVER_GRPC_ADDRESS`: the `ldk-server` gRPC address, e.g. `127.0.0.1:3536`
- `LDK_SERVER_TLS_CERT_FILE`: path to the `ldk-server` TLS certificate, usually `<storage_dir>/tls.crt`
- `LDK_SERVER_API_KEY`: the hex-encoded API key used by `ldk-server`

```bash
xxd -p -c 64 /var/lib/ldk-server/bitcoin/api_key
```

If Alby Hub runs on a different machine than `ldk-server`:

- set `grpc_service_address` in `ldk-server` to a reachable bind address such as `0.0.0.0:3536`
- add the public hostname or IP to `[tls].hosts`
- copy `tls.crt` to the Hub machine
- keep clocks reasonably in sync, because `ldk-server` rejects stale HMAC timestamps

Troubleshooting remote auth:

- `LDK_SERVER_API_KEY` must be the 64-character hex string derived from the raw `api_key` file, not the raw file bytes themselves
- if you use `ldk-server-cli`, pass the hex string directly or inline the `xxd` call
- do not use `KEY="$(xxd -p -c 64 /path/to/api_key)" ldk-server-cli ... --api-key "$KEY"` because Bash expands `"$KEY"` before that temporary assignment is applied

Working examples:

```bash
KEY="$(xxd -p -c 64 /var/lib/ldk-server/bitcoin/api_key)"
ldk-server-cli --base-url 141.95.84.44:3536 --api-key "$KEY" --tls-cert /path/to/tls.crt get-node-info
```

```bash
ldk-server-cli --base-url 141.95.84.44:3536 --api-key "$(xxd -p -c 64 /var/lib/ldk-server/bitcoin/api_key)" --tls-cert /path/to/tls.crt get-node-info
```

#### Optional: JIT receiving over LSPS2

If you want Hub to receive via `ldk-server` without pre-existing inbound liquidity, configure an LSPS2 client in `ldk-server`:

```toml
[liquidity.lsps2_client]
node_pubkey = "<lsp node pubkey>"
address = "<lsp host>:9735"
# token = "<optional token>"
```

With that in place, Hub can use `ldk-server`'s JIT invoice RPCs for receiving through the remote node.

### LDK Backend parameters

- `LDK_ESPLORA_SERVER`: By default the optimized Alby esplora is used. You can configure your own esplora server (note: the public blockstream one is slow and can cause onchain syncing and issues with opening channels)
Expand Down
37 changes: 36 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,14 @@ func (api *api) RequestMempoolApi(ctx context.Context, endpoint string) (interfa
}

if res.StatusCode != http.StatusOK {
if strings.HasPrefix(endpoint, "/v1/lightning/nodes/") && strings.Contains(string(body), `"error":"Failed to get node"`) {
logger.Logger.WithFields(logrus.Fields{
"endpoint": endpoint,
"status_code": res.StatusCode,
}).Debug("Mempool node details unavailable")
return map[string]interface{}{}, nil
}

logger.Logger.WithFields(logrus.Fields{
"endpoint": endpoint,
"status_code": res.StatusCode,
Expand Down Expand Up @@ -1519,7 +1527,7 @@ func (api *api) GetInfo(ctx context.Context) (*InfoResponse, error) {
info.LdkVssEnabled = ldkVssEnabled == "true"
info.JitChannelsEnabled = jitChannelsEnabled != "false"
info.VssSupported = backendType == config.LDKBackendType && api.cfg.GetEnv().LDKVssUrl != ""
info.SupportsBolt12 = backendType == config.LDKBackendType || backendType == config.CLNBackendType
info.SupportsBolt12 = backendType == config.LDKBackendType || backendType == config.CLNBackendType || backendType == config.LDKServerBackendType
info.AutoUnlockPasswordEnabled = autoUnlockPassword != ""
info.AutoUnlockPasswordSupported = api.cfg.GetEnv().IsDefaultClientId()
info.Relays = []InfoResponseRelay{}
Expand Down Expand Up @@ -1816,6 +1824,33 @@ func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
}
}

if setupRequest.LDKServerAddress != "" {
err = api.cfg.SetUpdate("LDKServerAddress", setupRequest.LDKServerAddress, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save ldk-server address")
return err
}
}
if setupRequest.LDKServerTlsCertFile != "" {
certBytes, err := os.ReadFile(setupRequest.LDKServerTlsCertFile)
if err != nil {
logger.Logger.WithError(err).Error("Failed to read ldk-server TLS cert file")
return err
}
err = api.cfg.SetUpdate("LDKServerTlsCertPem", string(certBytes), setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save ldk-server TLS cert")
return err
}
}
if setupRequest.LDKServerApiKey != "" {
err = api.cfg.SetUpdate("LDKServerApiKey", setupRequest.LDKServerApiKey, setupRequest.UnlockPassword)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save ldk-server API key")
return err
}
}

if setupRequest.CashuMintUrl != "" {
err = api.cfg.SetUpdate("CashuMintUrl", setupRequest.CashuMintUrl, setupRequest.UnlockPassword)
if err != nil {
Expand Down
69 changes: 37 additions & 32 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ type SetupRequest struct {
PhoenixdAddress string `json:"phoenixdAddress"`
PhoenixdAuthorization string `json:"phoenixdAuthorization"`

// ldk-server fields
LDKServerAddress string `json:"ldkServerAddress"`
LDKServerTlsCertFile string `json:"ldkServerTlsCertFile"`
LDKServerApiKey string `json:"ldkServerApiKey"`

// Cashu fields
CashuMintUrl string `json:"cashuMintUrl"`

Expand Down Expand Up @@ -304,38 +309,38 @@ type InfoResponseRelay struct {
}

type InfoResponse struct {
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
OAuthRedirect bool `json:"oauthRedirect"`
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
AlbyAuthUrl string `json:"albyAuthUrl"`
NextBackupReminder string `json:"nextBackupReminder"`
AlbyUserIdentifier string `json:"albyUserIdentifier"`
AlbyAccountConnected bool `json:"albyAccountConnected"`
Version string `json:"version"`
Network string `json:"network"`
EnableAdvancedSetup bool `json:"enableAdvancedSetup"`
LdkVssEnabled bool `json:"ldkVssEnabled"`
VssSupported bool `json:"vssSupported"`
StartupState string `json:"startupState"`
StartupError string `json:"startupError"`
StartupErrorTime time.Time `json:"startupErrorTime"`
AutoUnlockPasswordSupported bool `json:"autoUnlockPasswordSupported"`
AutoUnlockPasswordEnabled bool `json:"autoUnlockPasswordEnabled"`
Currency string `json:"currency"`
BitcoinDisplayFormat string `json:"bitcoinDisplayFormat"`
Relays []InfoResponseRelay `json:"relays"`
NodeAlias string `json:"nodeAlias"`
MempoolUrl string `json:"mempoolUrl"`
ChainDataSourceType string `json:"chainDataSourceType,omitempty"`
ChainDataSourceAddress string `json:"chainDataSourceAddress,omitempty"`
JitChannelsLiquiditySource string `json:"jitChannelsLiquiditySource,omitempty"`
JitChannelsMinPaymentSizeMsat *uint64 `json:"jitChannelsMinPaymentSizeMsat,omitempty"`
JitChannelsMaxPaymentSizeMsat *uint64 `json:"jitChannelsMaxPaymentSizeMsat,omitempty"`
JitChannelsEnabled bool `json:"jitChannelsEnabled"`
HideUpdateBanner bool `json:"hideUpdateBanner"`
SupportsBolt12 bool `json:"supportsBolt12"`
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
OAuthRedirect bool `json:"oauthRedirect"`
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
AlbyAuthUrl string `json:"albyAuthUrl"`
NextBackupReminder string `json:"nextBackupReminder"`
AlbyUserIdentifier string `json:"albyUserIdentifier"`
AlbyAccountConnected bool `json:"albyAccountConnected"`
Version string `json:"version"`
Network string `json:"network"`
EnableAdvancedSetup bool `json:"enableAdvancedSetup"`
LdkVssEnabled bool `json:"ldkVssEnabled"`
VssSupported bool `json:"vssSupported"`
StartupState string `json:"startupState"`
StartupError string `json:"startupError"`
StartupErrorTime time.Time `json:"startupErrorTime"`
AutoUnlockPasswordSupported bool `json:"autoUnlockPasswordSupported"`
AutoUnlockPasswordEnabled bool `json:"autoUnlockPasswordEnabled"`
Currency string `json:"currency"`
BitcoinDisplayFormat string `json:"bitcoinDisplayFormat"`
Relays []InfoResponseRelay `json:"relays"`
NodeAlias string `json:"nodeAlias"`
MempoolUrl string `json:"mempoolUrl"`
ChainDataSourceType string `json:"chainDataSourceType,omitempty"`
ChainDataSourceAddress string `json:"chainDataSourceAddress,omitempty"`
JitChannelsLiquiditySource string `json:"jitChannelsLiquiditySource,omitempty"`
JitChannelsMinPaymentSizeMsat *uint64 `json:"jitChannelsMinPaymentSizeMsat,omitempty"`
JitChannelsMaxPaymentSizeMsat *uint64 `json:"jitChannelsMaxPaymentSizeMsat,omitempty"`
JitChannelsEnabled bool `json:"jitChannelsEnabled"`
HideUpdateBanner bool `json:"hideUpdateBanner"`
SupportsBolt12 bool `json:"supportsBolt12"`
}

type UpdateSettingsRequest struct {
Expand Down
25 changes: 25 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,31 @@ func (cfg *config) init(env *AppConfig) error {
}
}

// ldk-server specific to support env variables
if cfg.Env.LDKServerAddress != "" {
err := cfg.SetUpdate("LDKServerAddress", cfg.Env.LDKServerAddress, "")
if err != nil {
return err
}
}
if cfg.Env.LDKServerTlsCertFile != "" {
certBytes, err := os.ReadFile(cfg.Env.LDKServerTlsCertFile)
if err != nil {
logger.Logger.WithError(err).Error("Failed to read ldk-server TLS cert file")
return err
}
err = cfg.SetUpdate("LDKServerTlsCertPem", string(certBytes), "")
if err != nil {
return err
}
}
if cfg.Env.LDKServerApiKey != "" {
err := cfg.SetUpdate("LDKServerApiKey", cfg.Env.LDKServerApiKey, "")
if err != nil {
return err
}
}

// CLN specific to support env variables
if cfg.Env.CLNAddress != "" {
err := cfg.SetUpdate("CLNAddress", cfg.Env.CLNAddress, "")
Expand Down
16 changes: 10 additions & 6 deletions config/models.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package config

const (
LNDBackendType = "LND"
LDKBackendType = "LDK"
PhoenixBackendType = "PHOENIX"
CashuBackendType = "CASHU"
CLNBackendType = "CLN"
BarkBackendType = "BARK"
LNDBackendType = "LND"
LDKBackendType = "LDK"
LDKServerBackendType = "LDK_SERVER"
PhoenixBackendType = "PHOENIX"
CashuBackendType = "CASHU"
CLNBackendType = "CLN"
BarkBackendType = "BARK"
)

const (
Expand Down Expand Up @@ -47,6 +48,9 @@ type AppConfig struct {
LDKBitcoindRpcPort string `envconfig:"LDK_BITCOIND_RPC_PORT"`
LDKBitcoindRpcUser string `envconfig:"LDK_BITCOIND_RPC_USER"`
LDKBitcoindRpcPassword string `envconfig:"LDK_BITCOIND_RPC_PASSWORD"`
LDKServerAddress string `envconfig:"LDK_SERVER_GRPC_ADDRESS"`
LDKServerTlsCertFile string `envconfig:"LDK_SERVER_TLS_CERT_FILE"`
LDKServerApiKey string `envconfig:"LDK_SERVER_API_KEY"`
MempoolApi string `envconfig:"MEMPOOL_API" default:"https://mempool.space/api"`
AlbyClientId string `envconfig:"ALBY_OAUTH_CLIENT_ID" default:"J2PbXS1yOf"`
AlbyClientSecret string `envconfig:"ALBY_OAUTH_CLIENT_SECRET" default:"rABK2n16IWjLTZ9M1uKU"`
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/PendingClosedChannelsAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export function PendingClosedChannelsAlert({
}

const pendingDetails = [
...balance.pendingBalancesDetails,
...balance.pendingSweepBalancesDetails,
...(balance.pendingBalancesDetails ?? []),
...(balance.pendingSweepBalancesDetails ?? []),
];

return (
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/backendType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export const backendTypeConfigs: Record<BackendType, BackendTypeConfig> = {
hasChannelManagement: true,
hasNodeBackup: true,
},
LDK_SERVER: {
hasMnemonic: false,
hasChannelManagement: true,
hasNodeBackup: false,
},
PHOENIX: {
hasMnemonic: false,
hasChannelManagement: false,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { BarkForm } from "src/screens/setup/node/BarkForm";
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 { LDKServerForm } from "src/screens/setup/node/LDKServerForm";
import { LNDForm } from "src/screens/setup/node/LNDForm";
import { PhoenixdForm } from "src/screens/setup/node/PhoenixdForm";
import { PresetNodeForm } from "src/screens/setup/node/PresetNodeForm";
Expand Down Expand Up @@ -558,6 +559,10 @@ const routes: RouteObject[] = [
path: "ldk",
element: <LDKForm />,
},
{
path: "ldk_server",
element: <LDKServerForm />,
},
{
path: "cln",
element: <CLNForm />,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/screens/setup/SetupNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const backendTypeDisplayConfigs: Partial<
title: "LDK",
icon: <LDKIcon />,
},
LDK_SERVER: {
title: "LDK Server",
icon: <LDKIcon />,
},
PHOENIX: {
title: "phoenixd",
icon: <PhoenixdIcon />,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/screens/setup/SetupSecurity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export function SetupSecurity() {
</span>
</div>
{store.nodeInfo.backendType === "LND" ||
store.nodeInfo.backendType === "LDK_SERVER" ||
store.nodeInfo.backendType === "CLN" ||
store.nodeInfo.backendType === "PHOENIX" ? (
<div className="flex gap-3 items-center">
Expand Down
Loading
Loading