From 6da3bbe9a2db42325935aa617cd46a7eb776b590 Mon Sep 17 00:00:00 2001 From: Andrew Orobator Date: Sat, 7 Feb 2026 17:23:53 -0500 Subject: [PATCH] feat: add node alias setting to General settings page Add inline node alias editing on the Settings > General page so self-hosted users can change their Lightning node alias without needing an Alby paid plan. The alias input pre-populates from useInfo().nodeAlias and POSTs to /api/node/alias. A toast reminds the user to restart for the change to take effect. Also adds Go doc comments to exported types in api/models.go, config/models.go, and lnclient/models.go to satisfy the 80% docstring coverage threshold. Includes tests for SetNodeAlias API covering success, empty alias, and config error scenarios. Co-Authored-By: Claude Opus 4.6 --- api/api_test.go | 43 +++++++++ api/models.go | 102 ++++++++++++++++++++- config/models.go | 27 ++++-- frontend/src/screens/settings/Settings.tsx | 58 +++++++++++- lnclient/models.go | 36 +++++++- 5 files changed, 252 insertions(+), 14 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index 0b876706e..77cc1de4d 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -3,16 +3,23 @@ package api import ( "context" "fmt" + "os" "testing" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/getAlby/hub/lnclient" + "github.com/getAlby/hub/logger" "github.com/getAlby/hub/service" "github.com/getAlby/hub/tests/mocks" ) +func TestMain(m *testing.M) { + logger.Init("") + os.Exit(m.Run()) +} + func TestGetCustomNodeCommandDefinitions(t *testing.T) { lnClient := mocks.NewMockLNClient(t) svc := mocks.NewMockService(t) @@ -221,6 +228,42 @@ func TestExecuteCustomNodeCommand(t *testing.T) { } } +func TestSetNodeAlias(t *testing.T) { + t.Run("success", func(t *testing.T) { + cfg := mocks.NewMockConfig(t) + cfg.On("SetUpdate", "NodeAlias", "SatoshiSquirrel", "").Return(nil) + + theAPI := &api{cfg: cfg} + + err := theAPI.SetNodeAlias("SatoshiSquirrel") + require.NoError(t, err) + cfg.AssertExpectations(t) + }) + + t.Run("empty alias", func(t *testing.T) { + cfg := mocks.NewMockConfig(t) + cfg.On("SetUpdate", "NodeAlias", "", "").Return(nil) + + theAPI := &api{cfg: cfg} + + err := theAPI.SetNodeAlias("") + require.NoError(t, err) + cfg.AssertExpectations(t) + }) + + t.Run("config error", func(t *testing.T) { + cfg := mocks.NewMockConfig(t) + cfg.On("SetUpdate", "NodeAlias", "test", "").Return(fmt.Errorf("database error")) + + theAPI := &api{cfg: cfg} + + err := theAPI.SetNodeAlias("test") + require.Error(t, err) + require.ErrorContains(t, err, "database error") + cfg.AssertExpectations(t) + }) +} + // instantiateAPIWithService is a helper function that returns a partially // constructed API instance. It is only suitable for the simplest of test cases. func instantiateAPIWithService(s service.Service) *api { diff --git a/api/models.go b/api/models.go index 971d80d1b..8d7cd0cef 100644 --- a/api/models.go +++ b/api/models.go @@ -11,6 +11,8 @@ import ( "github.com/getAlby/hub/swaps" ) +// API defines the interface for all Alby Hub API operations including app management, +// channel operations, payments, and node administration. type API interface { CreateApp(createAppRequest *CreateAppRequest) (*CreateAppResponse, error) UpdateApp(app *db.App, updateAppRequest *UpdateAppRequest) error @@ -85,6 +87,7 @@ type API interface { GetForwards() (*GetForwardsResponse, error) } +// App represents a connected NWC application with its permissions and usage details. type App struct { ID uint `json:"id"` Name string `json:"name"` @@ -105,6 +108,7 @@ type App struct { Metadata Metadata `json:"metadata,omitempty"` } +// ListAppsFilters specifies filter criteria for listing applications. type ListAppsFilters struct { Name string `json:"name"` AppStoreAppId string `json:"appStoreAppId"` @@ -112,11 +116,13 @@ type ListAppsFilters struct { SubWallets *bool `json:"subWallets"` } +// ListAppsResponse contains the paginated list of applications. type ListAppsResponse struct { Apps []App `json:"apps"` TotalCount uint64 `json:"totalCount"` } +// UpdateAppRequest contains the fields that can be updated for an application. type UpdateAppRequest struct { Name *string `json:"name"` MaxAmountSat *uint64 `json:"maxAmount"` @@ -128,12 +134,14 @@ type UpdateAppRequest struct { Isolated *bool `json:"isolated"` } +// TransferRequest specifies a balance transfer between two applications. type TransferRequest struct { AmountSat uint64 `json:"amountSat"` FromAppId *uint `json:"fromAppId"` ToAppId *uint `json:"toAppId"` } +// CreateAppRequest contains the parameters for creating a new NWC application connection. type CreateAppRequest struct { Name string `json:"name"` Pubkey string `json:"pubkey"` @@ -147,27 +155,32 @@ type CreateAppRequest struct { UnlockPassword string `json:"unlockPassword"` } +// CreateLightningAddressRequest contains the parameters for creating a lightning address for an app. type CreateLightningAddressRequest struct { Address string `json:"address"` AppId uint `json:"appId"` } +// InitiateSwapRequest contains the parameters for initiating a swap-in or swap-out. type InitiateSwapRequest struct { SwapAmount uint64 `json:"swapAmount"` Destination string `json:"destination"` } +// RefundSwapRequest contains the parameters for refunding a swap. type RefundSwapRequest struct { SwapId string `json:"swapId"` Address string `json:"address"` } +// EnableAutoSwapRequest contains the parameters for enabling automatic swaps. type EnableAutoSwapRequest struct { BalanceThreshold uint64 `json:"balanceThreshold"` SwapAmount uint64 `json:"swapAmount"` Destination string `json:"destination"` } +// GetAutoSwapConfigResponse contains the current auto-swap configuration. type GetAutoSwapConfigResponse struct { Type string `json:"type"` Enabled bool `json:"enabled"` @@ -176,6 +189,7 @@ type GetAutoSwapConfigResponse struct { Destination string `json:"destination"` } +// SwapInfoResponse contains fee and amount limits for swap operations. type SwapInfoResponse struct { AlbyServiceFee float64 `json:"albyServiceFee"` BoltzServiceFee float64 `json:"boltzServiceFee"` @@ -184,12 +198,15 @@ type SwapInfoResponse struct { MaxAmount uint64 `json:"maxAmount"` } +// ListSwapsResponse contains the list of all swaps. type ListSwapsResponse struct { Swaps []Swap `json:"swaps"` } +// LookupSwapResponse is an alias for Swap, returned when looking up a single swap. type LookupSwapResponse = Swap +// Swap represents a submarine swap (swap-in or swap-out) with its current state. type Swap struct { Id string `json:"id"` Type string `json:"type"` @@ -210,25 +227,30 @@ type Swap struct { UsedXpub bool `json:"usedXpub"` } +// StartRequest contains the unlock password needed to start the node. type StartRequest struct { UnlockPassword string `json:"unlockPassword"` } +// UnlockRequest contains the credentials for unlocking the node. type UnlockRequest struct { UnlockPassword string `json:"unlockPassword"` TokenExpiryDays *uint64 `json:"tokenExpiryDays"` Permission string `json:"permission,omitempty"` // "full" or "readonly" } +// BackupReminderRequest contains the next backup reminder date. type BackupReminderRequest struct { NextBackupReminder string `json:"nextBackupReminder"` } +// SendEventRequest contains the event name and properties to send for analytics. type SendEventRequest struct { Event string `json:"event"` Properties interface{} `json:"properties"` } +// SetupRequest contains all the parameters needed for initial node setup. type SetupRequest struct { LNBackendType string `json:"backendType"` UnlockPassword string `json:"unlockPassword"` @@ -251,6 +273,7 @@ type SetupRequest struct { CashuMintUrl string `json:"cashuMintUrl"` } +// CreateAppResponse contains the pairing details returned after creating a new app connection. type CreateAppResponse struct { PairingUri string `json:"pairingUri"` PairingSecret string `json:"pairingSecretKey"` @@ -263,15 +286,18 @@ type CreateAppResponse struct { ReturnTo string `json:"returnTo"` } +// User represents a user with their email address. type User struct { Email string `json:"email"` } +// InfoResponseRelay represents a Nostr relay with its URL and online status. type InfoResponseRelay struct { Url string `json:"url"` Online bool `json:"online"` } +// InfoResponse contains the overall status and configuration of the Alby Hub instance. type InfoResponse struct { BackendType string `json:"backendType"` SetupCompleted bool `json:"setupCompleted"` @@ -299,45 +325,63 @@ type InfoResponse struct { MempoolUrl string `json:"mempoolUrl"` } +// UpdateSettingsRequest contains the settings fields that can be updated. type UpdateSettingsRequest struct { Currency string `json:"currency"` BitcoinDisplayFormat string `json:"bitcoinDisplayFormat"` } +// SetNodeAliasRequest contains the new node alias to set. type SetNodeAliasRequest struct { NodeAlias string `json:"nodeAlias"` } +// MnemonicRequest contains the unlock password required to retrieve the mnemonic. type MnemonicRequest struct { UnlockPassword string `json:"unlockPassword"` } +// MnemonicResponse contains the wallet mnemonic seed phrase. type MnemonicResponse struct { Mnemonic string `json:"mnemonic"` } +// ChangeUnlockPasswordRequest contains the current and new unlock passwords. type ChangeUnlockPasswordRequest struct { CurrentUnlockPassword string `json:"currentUnlockPassword"` NewUnlockPassword string `json:"newUnlockPassword"` } +// AutoUnlockRequest contains the password used for automatic unlock on startup. type AutoUnlockRequest struct { UnlockPassword string `json:"unlockPassword"` } +// ConnectPeerRequest is an alias for lnclient.ConnectPeerRequest. type ConnectPeerRequest = lnclient.ConnectPeerRequest + +// OpenChannelRequest is an alias for lnclient.OpenChannelRequest. type OpenChannelRequest = lnclient.OpenChannelRequest + +// OpenChannelResponse is an alias for lnclient.OpenChannelResponse. type OpenChannelResponse = lnclient.OpenChannelResponse + +// CloseChannelResponse is an alias for lnclient.CloseChannelResponse. type CloseChannelResponse = lnclient.CloseChannelResponse + +// UpdateChannelRequest is an alias for lnclient.UpdateChannelRequest. type UpdateChannelRequest = lnclient.UpdateChannelRequest +// RebalanceChannelRequest contains the parameters for rebalancing a channel. type RebalanceChannelRequest struct { ReceiveThroughNodePubkey string `json:"receiveThroughNodePubkey"` AmountSat uint64 `json:"amountSat"` } +// RebalanceChannelResponse contains the fee paid for the channel rebalance. type RebalanceChannelResponse struct { TotalFeeSat uint64 `json:"totalFeeSat"` } +// RedeemOnchainFundsRequest contains the parameters for sweeping onchain funds. type RedeemOnchainFundsRequest struct { ToAddress string `json:"toAddress"` Amount uint64 `json:"amount"` @@ -345,22 +389,33 @@ type RedeemOnchainFundsRequest struct { SendAll bool `json:"sendAll"` } +// RedeemOnchainFundsResponse contains the transaction ID of the sweep transaction. type RedeemOnchainFundsResponse struct { TxId string `json:"txId"` } +// OnchainBalanceResponse is an alias for lnclient.OnchainBalanceResponse. type OnchainBalanceResponse = lnclient.OnchainBalanceResponse + +// BalancesResponse is an alias for lnclient.BalancesResponse. type BalancesResponse = lnclient.BalancesResponse +// SendPaymentResponse is a Transaction returned after sending a payment. type SendPaymentResponse = Transaction + +// MakeInvoiceResponse is a Transaction returned after creating an invoice. type MakeInvoiceResponse = Transaction + +// LookupInvoiceResponse is a Transaction returned when looking up an invoice. type LookupInvoiceResponse = Transaction +// ListTransactionsResponse contains the paginated list of transactions. type ListTransactionsResponse struct { TotalCount uint64 `json:"totalCount"` Transactions []Transaction `json:"transactions"` } +// Transaction represents a Lightning payment or invoice with its current state. // TODO: camelCase type Transaction struct { Type string `json:"type"` @@ -381,8 +436,10 @@ type Transaction struct { FailureReason string `json:"failureReason"` } +// Metadata is a map of arbitrary key-value pairs attached to transactions and apps. type Metadata = map[string]interface{} +// Boostagram represents a podcast boost payment with its associated metadata. type Boostagram struct { AppName string `json:"appName"` Name string `json:"name"` @@ -400,74 +457,91 @@ type Boostagram struct { ValueMsatTotal int64 `json:"valueMsatTotal"` } -// debug api +// SendPaymentProbesRequest contains the invoice for probing a payment route. type SendPaymentProbesRequest struct { Invoice string `json:"invoice"` } +// SendPaymentProbesResponse contains the result of probing a payment route. type SendPaymentProbesResponse struct { Error string `json:"error"` } +// SendSpontaneousPaymentProbesRequest contains the parameters for probing a spontaneous payment route. type SendSpontaneousPaymentProbesRequest struct { Amount uint64 `json:"amount"` NodeId string `json:"nodeId"` } +// SendSpontaneousPaymentProbesResponse contains the result of probing a spontaneous payment route. type SendSpontaneousPaymentProbesResponse struct { Error string `json:"error"` } const ( + // LogTypeNode is the log type identifier for node logs. LogTypeNode = "node" - LogTypeApp = "app" + // LogTypeApp is the log type identifier for application logs. + LogTypeApp = "app" ) +// GetLogOutputRequest specifies the maximum length of log output to retrieve. type GetLogOutputRequest struct { MaxLen int `query:"maxLen"` } +// GetLogOutputResponse contains the log output string. type GetLogOutputResponse struct { Log string `json:"logs"` } +// SignMessageRequest contains the message to be signed by the node. type SignMessageRequest struct { Message string `json:"message"` } +// SignMessageResponse contains the signed message and its signature. type SignMessageResponse struct { Message string `json:"message"` Signature string `json:"signature"` } +// PayInvoiceRequest contains the optional amount and metadata for paying an invoice. type PayInvoiceRequest struct { Amount *uint64 `json:"amount"` Metadata Metadata `json:"metadata"` } +// MakeOfferRequest contains the description for creating a BOLT12 offer. type MakeOfferRequest struct { Description string `json:"description"` } +// MakeInvoiceRequest contains the amount and description for creating an invoice. type MakeInvoiceRequest struct { Amount uint64 `json:"amount"` Description string `json:"description"` } +// ResetRouterRequest contains the key used to authorize a router reset. type ResetRouterRequest struct { Key string `json:"key"` } +// BasicBackupRequest contains the unlock password needed to create a backup. type BasicBackupRequest struct { UnlockPassword string `json:"unlockPassword"` } +// BasicRestoreWailsRequest contains the unlock password needed to restore from a backup via Wails. type BasicRestoreWailsRequest struct { UnlockPassword string `json:"unlockPassword"` } +// NetworkGraphResponse is an alias for lnclient.NetworkGraphResponse. type NetworkGraphResponse = lnclient.NetworkGraphResponse +// LSPOrderRequest contains the parameters for requesting a channel from an LSP. type LSPOrderRequest struct { Amount uint64 `json:"amount"` LSPType string `json:"lspType"` @@ -475,6 +549,7 @@ type LSPOrderRequest struct { Public bool `json:"public"` } +// LSPOrderResponse contains the invoice and liquidity details for an LSP channel order. type LSPOrderResponse struct { Invoice string `json:"invoice"` Fee uint64 `json:"fee"` @@ -483,12 +558,14 @@ type LSPOrderResponse struct { OutgoingLiquidity uint64 `json:"outgoingLiquidity"` } +// WalletCapabilitiesResponse contains the NIP-47 scopes, methods, and notification types supported by the wallet. type WalletCapabilitiesResponse struct { Scopes []string `json:"scopes"` Methods []string `json:"methods"` NotificationTypes []string `json:"notificationTypes"` } +// Channel represents a Lightning channel with its balances, fees, and status. type Channel struct { LocalBalance int64 `json:"localBalance"` LocalSpendableBalance int64 `json:"localSpendableBalance"` @@ -511,25 +588,34 @@ type Channel struct { IsOutbound bool `json:"isOutbound"` } +// MigrateNodeStorageRequest contains the target storage backend for migration. type MigrateNodeStorageRequest struct { To string `json:"to"` } +// HealthAlarmKind represents the type of a health alarm. type HealthAlarmKind string const ( - HealthAlarmKindAlbyService HealthAlarmKind = "alby_service" - HealthAlarmKindNodeNotReady HealthAlarmKind = "node_not_ready" - HealthAlarmKindChannelsOffline HealthAlarmKind = "channels_offline" + // HealthAlarmKindAlbyService indicates an issue with the Alby service connection. + HealthAlarmKindAlbyService HealthAlarmKind = "alby_service" + // HealthAlarmKindNodeNotReady indicates the Lightning node is not ready. + HealthAlarmKindNodeNotReady HealthAlarmKind = "node_not_ready" + // HealthAlarmKindChannelsOffline indicates one or more channels are offline. + HealthAlarmKindChannelsOffline HealthAlarmKind = "channels_offline" + // HealthAlarmKindNostrRelayOffline indicates a Nostr relay is offline. HealthAlarmKindNostrRelayOffline HealthAlarmKind = "nostr_relay_offline" + // HealthAlarmKindVssNoSubscription indicates VSS has no active subscription. HealthAlarmKindVssNoSubscription HealthAlarmKind = "vss_no_subscription" ) +// HealthAlarm represents a single health check alarm with its kind and details. type HealthAlarm struct { Kind HealthAlarmKind `json:"kind"` RawDetails any `json:"rawDetails,omitempty"` } +// NewHealthAlarm creates a new HealthAlarm with the given kind and raw details. func NewHealthAlarm(kind HealthAlarmKind, rawDetails any) HealthAlarm { return HealthAlarm{ Kind: kind, @@ -537,29 +623,35 @@ func NewHealthAlarm(kind HealthAlarmKind, rawDetails any) HealthAlarm { } } +// HealthResponse contains the list of active health alarms. type HealthResponse struct { Alarms []HealthAlarm `json:"alarms,omitempty"` } +// CustomNodeCommandArgDef defines a single argument for a custom node command. type CustomNodeCommandArgDef struct { Name string `json:"name"` Description string `json:"description"` } +// CustomNodeCommandDef defines a custom node command with its name, description, and arguments. type CustomNodeCommandDef struct { Name string `json:"name"` Description string `json:"description"` Args []CustomNodeCommandArgDef `json:"args"` } +// CustomNodeCommandsResponse contains the list of available custom node commands. type CustomNodeCommandsResponse struct { Commands []CustomNodeCommandDef `json:"commands"` } +// ExecuteCustomNodeCommandRequest contains the command name to execute. type ExecuteCustomNodeCommandRequest struct { Command string `json:"command"` } +// GetForwardsResponse contains the total forwarding statistics for the node. type GetForwardsResponse struct { OutboundAmountForwardedMsat uint64 `json:"outboundAmountForwardedMsat"` TotalFeeEarnedMsat uint64 `json:"totalFeeEarnedMsat"` diff --git a/config/models.go b/config/models.go index d2bbb7d92..96bd86f4d 100644 --- a/config/models.go +++ b/config/models.go @@ -1,20 +1,30 @@ package config const ( - LNDBackendType = "LND" - LDKBackendType = "LDK" + // LNDBackendType is the backend type identifier for LND nodes. + LNDBackendType = "LND" + // LDKBackendType is the backend type identifier for LDK nodes. + LDKBackendType = "LDK" + // PhoenixBackendType is the backend type identifier for Phoenixd nodes. PhoenixBackendType = "PHOENIX" - CashuBackendType = "CASHU" + // CashuBackendType is the backend type identifier for Cashu wallets. + CashuBackendType = "CASHU" ) const ( - OnchainAddressKey = "OnchainAddress" + // OnchainAddressKey is the config key for the onchain address. + OnchainAddressKey = "OnchainAddress" + // AutoSwapBalanceThresholdKey is the config key for the auto-swap balance threshold. AutoSwapBalanceThresholdKey = "AutoSwapBalanceThreshold" - AutoSwapAmountKey = "AutoSwapAmount" - AutoSwapDestinationKey = "AutoSwapDestination" - AutoSwapXpubIndexStart = "AutoSwapXpubIndexStart" + // AutoSwapAmountKey is the config key for the auto-swap amount. + AutoSwapAmountKey = "AutoSwapAmount" + // AutoSwapDestinationKey is the config key for the auto-swap destination address. + AutoSwapDestinationKey = "AutoSwapDestination" + // AutoSwapXpubIndexStart is the config key for the auto-swap xpub derivation index start. + AutoSwapXpubIndexStart = "AutoSwapXpubIndexStart" ) +// AppConfig holds the application-level configuration loaded from environment variables. type AppConfig struct { Relay string `envconfig:"RELAY" default:"wss://relay.getalby.com/v1"` LNBackendType string `envconfig:"LN_BACKEND_TYPE"` @@ -60,10 +70,12 @@ type AppConfig struct { BoltzApi string `envconfig:"BOLTZ_API" default:"https://api.boltz.exchange"` } +// IsDefaultClientId returns true if the Alby OAuth client ID is set to the default value. func (c *AppConfig) IsDefaultClientId() bool { return c.AlbyClientId == "J2PbXS1yOf" } +// GetBaseFrontendUrl returns the base frontend URL, falling back to the BaseUrl if FrontendUrl is not set. func (c *AppConfig) GetBaseFrontendUrl() string { url := c.FrontendUrl if url == "" { @@ -72,6 +84,7 @@ func (c *AppConfig) GetBaseFrontendUrl() string { return url } +// Config defines the interface for accessing and managing application configuration. type Config interface { Unlock(encryptionKey string) error Get(key string, encryptionKey string) (string, error) diff --git a/frontend/src/screens/settings/Settings.tsx b/frontend/src/screens/settings/Settings.tsx index 74427cf7e..df95f69cc 100644 --- a/frontend/src/screens/settings/Settings.tsx +++ b/frontend/src/screens/settings/Settings.tsx @@ -1,9 +1,11 @@ import { StarsIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { toast } from "sonner"; import Loading from "src/components/Loading"; import SettingsHeader from "src/components/SettingsHeader"; import { Badge } from "src/components/ui/badge"; +import { Button } from "src/components/ui/button"; +import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { Select, @@ -33,9 +35,17 @@ function Settings() { const { theme, darkMode, setTheme, setDarkMode } = useTheme(); const [fiatCurrencies, setFiatCurrencies] = useState<[string, string][]>([]); + const [nodeAlias, setNodeAlias] = useState(""); + const [isSavingAlias, setIsSavingAlias] = useState(false); const { data: info, mutate: reloadInfo } = useInfo(); + React.useEffect(() => { + if (info?.nodeAlias !== undefined) { + setNodeAlias(info.nodeAlias); + } + }, [info?.nodeAlias]); + useEffect(() => { async function fetchCurrencies() { try { @@ -95,6 +105,26 @@ function Settings() { ); } + async function saveNodeAlias(e: React.FormEvent) { + e.preventDefault(); + setIsSavingAlias(true); + try { + await request("/api/node/alias", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ nodeAlias }), + }); + await reloadInfo(); + toast("Node alias updated. Restart your node to apply the change."); + } catch (error) { + handleRequestError("Failed to update node alias", error); + } finally { + setIsSavingAlias(false); + } + } + if (!info) { return ; } @@ -221,6 +251,32 @@ function Settings() { + + {/* Node Section */} + {info.backendType === "LDK" && ( +
+

Node

+
+ + setNodeAlias(e.target.value)} + placeholder="Alby Hub" + className="w-full md:w-60" + /> +

+ Your node alias is visible to channel partners, connected peers, + and on lightning network explorers. Changes take effect after + restarting your node. +

+
+ +
+ )} ); } diff --git a/lnclient/models.go b/lnclient/models.go index fd22f4313..be13b4148 100644 --- a/lnclient/models.go +++ b/lnclient/models.go @@ -7,14 +7,17 @@ import ( // TODO: remove JSON tags from these models (LNClient models should not be exposed directly) +// TLVRecord represents a Type-Length-Value record used in Lightning keysend payments. type TLVRecord struct { Type uint64 `json:"type"` // hex-encoded value Value string `json:"value"` } +// Metadata is a map of arbitrary key-value pairs associated with transactions. type Metadata = map[string]interface{} +// NodeInfo contains basic information about the Lightning node. type NodeInfo struct { Alias string Color string @@ -24,6 +27,7 @@ type NodeInfo struct { BlockHash string } +// Transaction represents a Lightning transaction (payment or invoice). // TODO: use uint for fields that cannot be negative type Transaction struct { Type string @@ -41,6 +45,7 @@ type Transaction struct { SettleDeadline *uint32 // block number for accepted hold invoices } +// OnchainTransaction represents a Bitcoin onchain transaction. type OnchainTransaction struct { AmountSat uint64 `json:"amountSat"` CreatedAt uint64 `json:"createdAt"` @@ -50,12 +55,14 @@ type OnchainTransaction struct { TxId string `json:"txId"` } +// NodeConnectionInfo contains the connection details for a Lightning node, including optional Tor address. type NodeConnectionInfo struct { Pubkey string `json:"pubkey"` Address string `json:"address"` Port int `json:"port"` } +// LNClient defines the interface that all Lightning node backend implementations must satisfy. type LNClient interface { SendPaymentSync(payReq string, amount *uint64) (*PayInvoiceResponse, error) SendKeysend(amount uint64, destination string, customRecords []TLVRecord, preimage string) (*PayKeysendResponse, error) @@ -97,6 +104,7 @@ type LNClient interface { ExecuteCustomNodeCommand(ctx context.Context, command *CustomNodeCommandRequest) (*CustomNodeCommandResponse, error) } +// Channel represents a Lightning payment channel with its balances and configuration. type Channel struct { LocalBalance int64 LocalSpendableBalance int64 @@ -118,33 +126,39 @@ type Channel struct { IsOutbound bool } +// NodeStatus indicates whether the Lightning node is ready and provides internal status details. type NodeStatus struct { IsReady bool `json:"isReady"` InternalNodeStatus interface{} `json:"internalNodeStatus"` } +// ConnectPeerRequest contains the pubkey and network address of a peer to connect to. type ConnectPeerRequest struct { Pubkey string `json:"pubkey"` Address string `json:"address"` Port uint16 `json:"port"` } +// OpenChannelRequest contains the parameters for opening a new Lightning channel. type OpenChannelRequest struct { Pubkey string `json:"pubkey"` AmountSats int64 `json:"amountSats"` Public bool `json:"public"` } +// OpenChannelResponse contains the funding transaction ID of the newly opened channel. type OpenChannelResponse struct { FundingTxId string `json:"fundingTxId"` } +// CloseChannelRequest contains the parameters for closing a Lightning channel. type CloseChannelRequest struct { ChannelId string `json:"channelId"` NodeId string `json:"nodeId"` Force bool `json:"force"` } +// UpdateChannelRequest contains the parameters for updating a channel's forwarding fees. type UpdateChannelRequest struct { ChannelId string `json:"channelId"` NodeId string `json:"nodeId"` @@ -153,9 +167,11 @@ type UpdateChannelRequest struct { MaxDustHtlcExposureFromFeeRateMultiplier uint64 `json:"maxDustHtlcExposureFromFeeRateMultiplier"` } +// CloseChannelResponse is returned after successfully closing a channel. type CloseChannelResponse struct { } +// PendingBalanceDetails contains details about funds pending from a channel closure. type PendingBalanceDetails struct { ChannelId string `json:"channelId"` NodeId string `json:"nodeId"` @@ -164,6 +180,7 @@ type PendingBalanceDetails struct { FundingTxVout uint32 `json:"fundingTxVout"` } +// OnchainBalanceResponse contains the onchain wallet balance breakdown. type OnchainBalanceResponse struct { Spendable int64 `json:"spendable"` Total int64 `json:"total"` @@ -174,12 +191,14 @@ type OnchainBalanceResponse struct { InternalBalances interface{} `json:"internalBalances"` } +// PeerDetails contains information about a connected Lightning peer. type PeerDetails struct { NodeId string `json:"nodeId"` Address string `json:"address"` IsPersisted bool `json:"isPersisted"` IsConnected bool `json:"isConnected"` } +// LightningBalanceResponse contains the Lightning channel balance breakdown. type LightningBalanceResponse struct { TotalSpendable int64 `json:"totalSpendable"` TotalReceivable int64 `json:"totalReceivable"` @@ -189,77 +208,92 @@ type LightningBalanceResponse struct { NextMaxReceivableMPP int64 `json:"nextMaxReceivableMPP"` } +// PayInvoiceResponse contains the preimage and fee for a paid invoice. type PayInvoiceResponse struct { Preimage string `json:"preimage"` Fee uint64 `json:"fee"` } +// PayOfferResponse contains the preimage, fee, and payment hash for a paid BOLT12 offer. type PayOfferResponse = struct { Preimage string `json:"preimage"` Fee uint64 `json:"fee"` PaymentHash string `json:"payment_hash"` } +// PayKeysendResponse contains the fee for a keysend payment. type PayKeysendResponse struct { Fee uint64 `json:"fee"` } +// BalancesResponse contains both the onchain and Lightning channel balances. type BalancesResponse struct { Onchain OnchainBalanceResponse `json:"onchain"` Lightning LightningBalanceResponse `json:"lightning"` } +// NetworkGraphResponse is an alias for the network graph data returned by the node. type NetworkGraphResponse = interface{} +// PaymentFailedEventProperties contains the transaction and reason for a failed payment event. type PaymentFailedEventProperties struct { Transaction *Transaction Reason string } +// PaymentForwardedEventProperties contains the forwarding statistics for a forwarded payment event. type PaymentForwardedEventProperties struct { TotalFeeEarnedMsat uint64 OutboundAmountForwardedMsat uint64 } +// CustomNodeCommandArgDef defines a single argument for a custom node command. type CustomNodeCommandArgDef struct { Name string Description string } +// CustomNodeCommandDef defines a custom node command with its name, description, and arguments. type CustomNodeCommandDef struct { Name string Description string Args []CustomNodeCommandArgDef } +// CustomNodeCommandArg represents a name-value pair argument for a custom node command. type CustomNodeCommandArg struct { Name string Value string } +// CustomNodeCommandRequest contains the command name and arguments to execute. type CustomNodeCommandRequest struct { Name string Args []CustomNodeCommandArg } +// CustomNodeCommandResponse contains the response from executing a custom node command. type CustomNodeCommandResponse struct { Response interface{} } +// NewCustomNodeCommandResponseEmpty creates a CustomNodeCommandResponse with an empty struct response. func NewCustomNodeCommandResponseEmpty() *CustomNodeCommandResponse { return &CustomNodeCommandResponse{ Response: struct{}{}, } } +// ErrUnknownCustomNodeCommand is returned when an unrecognized custom node command is requested. var ErrUnknownCustomNodeCommand = errors.New("unknown custom node command") -// default invoice expiry in seconds (1 day) +// DEFAULT_INVOICE_EXPIRY is the default invoice expiry in seconds (1 day). const DEFAULT_INVOICE_EXPIRY = 86400 type holdInvoiceCanceledError struct { } +// NewHoldInvoiceCanceledError returns an error indicating that a hold invoice was canceled. func NewHoldInvoiceCanceledError() error { return &holdInvoiceCanceledError{} }