ThunderHub Documentation

repository·master·Indexed 19 days ago

https://github.com/apotdevin/thunderhub

ThunderHub is a Lightning Node Manager (version 0.18.4) that provides a user interface to monitor and manage Lightning Network nodes from any browser. It features a delegated provider architecture to support multiple implementations like LND and Eclair, and can be deployed via Docker and Docker Compose. The system utilizes a GraphQL API, Apollo Client for the frontend, and Drizzle ORM for database management supporting both SQLite and PostgreSQL.

Tokens
16.7K
Snippets
61
Records
72
Agent score
67%

What's inside ThunderHub

  1. How the Lightning Provider architecture works

    master

    ThunderHub uses a delegated provider pattern to support multiple Lightning implementations (like LND, Eclair, etc.).

    1. GraphQL Resolvers receive requests.
    2. NodeService receives the request and dispatches it based on the account.type.
    3. ProviderRegistryService maps the NodeType to a specific implementation of the LightningProvider interface.
    4. The LightningProvider (e.g., LndService) executes the actual logic.

    Resolvers never interact with providers directly; they always go through the NodeService and ProviderRegistryService abstraction layer.

    GraphQL Resolvers
           │
           ▼
       NodeService          ← dispatches by account.type
           │
           ▼
    ProviderRegistryService ← maps NodeType → LightningProvider
           │
           ├── LndService (type: 'lnd')
           ├── YourService (type: 'your-type')
           └── ...
  2. Register a new Lightning provider in the registry

    master

    After creating your service and module, you must register it so the NodeService can find it.

    1. Inject and Map: In src/server/modules/node/provider-registry.service.ts, inject your new service into the constructor and add it to the providers Map using your new NodeType.
    2. Import Module: In src/server/modules/node/provider-registry.module.ts, add your new module to the imports array.
    // src/server/modules/node/provider-registry.service.ts
    @Injectable()
    export class ProviderRegistryService {
      private providers = new Map<string, LightningProvider>();
    
      constructor(
        lndService: LndService,
        yourTypeService: YourTypeService, // ← add this
      ) {
        this.providers.set(NodeType.LND, lndService);
        this.providers.set(NodeType.YOUR_TYPE, yourTypeService); // ← add this
      }
    }
    
    // src/server/modules/node/provider-registry.module.ts
    @Module({
      imports: [LndModule, YourTypeModule], // ← add YourTypeModule
      providers: [ProviderRegistryService],
      exports: [ProviderRegistryService],
    })
    export class ProviderRegistryModule {}
  3. Implement a new LightningProvider service

    master

    To add a new provider, create a service that implements the LightningProvider interface from src/server/modules/node/lightning.types.ts.

    Key Implementation Details:

    • getCapabilities(): Return a Set<Capability> indicating what features your provider supports (e.g., Capability.CHANNELS, Capability.INVOICES). The UI uses this to hide unsupported features.
    • connect(config): This method is called once per account at startup. It should return a connection object (e.g., an Axios instance, gRPC client, or WebSocket) which is then passed as the first argument to all other provider methods.
    • Method Implementation: Every method in the LightningProvider interface must be implemented. If a method is not supported, throw an error.
    • Return Shapes: Data returned by your methods must match the shapes expected by the existing GraphQL resolvers. Use the LndService as a reference for expected return structures (e.g., public_key, alias, etc.).
    import { Injectable } from '@nestjs/common';
    import {
      Capability,
      LightningProvider,
      // ... import option types
    } from '../lightning.types';
    
    @Injectable()
    export class YourTypeService implements LightningProvider {
      getCapabilities(): Set<Capability> {
        return new Set([
          Capability.WALLET_INFO,
          Capability.CHANNELS,
          // ...
        ]);
      }
    
      connect(config: {
        socket: string;
        cert?: string;
        macaroon?: string;
        authToken?: string;
      }): YourConnectionType {
        return createYourClient({
          url: config.socket,
          token: config.authToken,
        });
      }
    
      async getWalletInfo(connection: YourConnectionType) {
        const info = await connection.getInfo();
        return {
          public_key: info.pubkey,
          alias: info.alias,
          chains: [info.chain],
        };
      }
    
      // ... implement remaining methods
    }
  4. Apollo Client Cache Configuration and Pagination

    master

    The ThunderHub Apollo Client uses a custom InMemoryCache configuration to handle paginated GraphQL queries for invoices and payments.

    Specifically, the getInvoices and getPayments fields on the Query type use a custom merge function. This function implements client-side deduplication based on the __ref or id field of the items, allowing for seamless infinite scrolling or pagination by merging incoming results into the existing list without duplicates.

    // The client automatically handles merging for these fields:
    // - Query.getInvoices
    // - Query.getPayments
  5. Configure a new Lightning provider account in YAML

    master

    Users select your provider implementation using the type field in the accounts section of the configuration file.

    Common fields available to all types include:

    • name
    • serverUrl
    • password
    • type (your custom type string)
    • authToken

    If your provider requires custom configuration fields, you must add them to AccountType and UnresolvedAccountType in src/server/modules/files/files.types.ts.

    masterPassword: 'your-password'
    accounts:
      - name: 'My Node'
        type: your-type
        serverUrl: 'https://localhost:3001'
        authToken: 'your-auth-token'
        password: 'account-password'
  6. Manage event logs with EventLogProvider and useEventLog

    master

    ThunderHub provides a centralized event logging system for managing application events (successes, errors, etc.). To use it, wrap your component tree with EventLogProvider. You can then use the useEventLog hook to interact with the log by adding new events or clearing the existing log.

    Event Structure

    When adding an event via addEvent, you provide:

    • title: A string title for the event.
    • summary: An array of EventField objects (containing label and value).
    • details (optional): An array of EventField objects for expanded information.
    • status (optional): Either 'success' or 'error' (defaults to 'success').
    • type (optional): A string identifying the event category (defaults to 'event').

    The log maintains a maximum of 100 entries, with the newest entries appearing first.

    import { EventLogProvider, useEventLog } from './path-to-context';
    
    function App() {
      return (
        <EventLogProvider>
          <MyComponent />
        </EventLogProvider>
      );
    }
    
    function MyComponent() {
      const { addEvent, clearEvents } = useEventLog();
    
      const handleAction = () => {
        addEvent({
          title: 'Transaction Completed',
          summary: [{ label: 'Amount', value: '0.001 BTC' }],
          details: [{ label: 'TXID', value: 'abc123...' }],
          status: 'success',
          type: 'lightning'
        });
      };
    
      return <button onClick={handleAction}>Log Event</button>;
    }
  7. Configure ThunderHub UI settings via ConfigProvider

    master

    ThunderHub uses a ConfigProvider to manage UI state such as themes, currency preferences, sidebar visibility, and channel display settings. The configuration is persisted in localStorage (excluding the theme which is stored separately).

    To use the configuration in your components, wrap your application in the ConfigProvider and use the useConfigState and useConfigDispatch hooks.

    import { ConfigProvider, useConfigState, useConfigDispatch } from './context/ConfigContext';
    
    function App() {
      return (
        <ConfigProvider initialConfig={{ theme: 'dark' }}>
          <MyComponent />
        </ConfigProvider>
      );
    }
    
    function MyComponent() {
      const config = useConfigState();
      const dispatch = useConfigDispatch();
    
      const toggleSidebar = () => {
        dispatch({ type: 'change', sidebar: !config.sidebar });
      };
    
      return <button onClick={toggleSidebar}>Toggle Sidebar</button>;
    }
  8. Use NodeSlugProvider to manage node-specific routing

    master

    Wrap your application (or a specific sub-tree) with NodeSlugProvider to enable context-aware routing based on the current Lightning node's slug. This provider automatically detects the nodeSlug from the URL parameters and provides utilities to build paths and navigate relative to that node.

    When the nodeSlug changes, the provider automatically calls client.resetStore() from Apollo Client to prevent data leakage between different nodes.

    import { NodeSlugProvider } from './path-to-hooks';
    
    function App() {
      return (
        <NodeSlugProvider>
          <YourAppContent />
        </NodeSlugProvider>
      );
    }
  9. Configure ThunderHub via Docker Compose

    master

    ThunderHub can be deployed using Docker Compose. The service maps port 3000 for web access and uses a volume to persist data to a local directory named ./local_data.

    Key environment variables for configuration include:

    • LOG_LEVEL: Sets the verbosity of logs (e.g., debug).
    • ACCOUNT_CONFIG_PATH: Specifies the absolute path within the container to the YAML configuration file (defaults to /data/thubConfig.yaml when using the standard volume mapping).
    services:
      thunderhub:
        build: .
        restart: on-failure
        ports:
          - 3000:3000
        volumes:
          - ./local_data:/data
        environment:
          LOG_LEVEL: "debug"
          ACCOUNT_CONFIG_PATH: "/data/thubConfig.yaml"
  10. Configure Lightning Terminal (LitD) via Docker

    master

    Lightning Terminal (LitD) services in this setup use several command-line flags to configure the integrated LND node and Taproot Assets features.

    Common configuration flags include:

    • --network: The blockchain network (e.g., regtest).
    • --lnd-mode=integrated: Enables the integrated LND mode.
    • --lnd.bitcoin.node: The hostname of the Bitcoin node (e.g., bitcoind).
    • --lnd.bitcoind.rpchost: The RPC host and port for Bitcoin.
    • --lnd.bitcoind.rpcuser / --lnd.bitcoind.rpcpass: Credentials for the Bitcoin RPC.
    • --uipassword: Password for the Lightning Terminal web UI.
    • --taproot-assets.universe.public-access: Access level for the Taproot Assets universe (e.g., rw).
    • --taproot-assets.proofcourieraddr: The URI for the proof courier (e.g., universerpc://<hostname>:8443).
    # Example LitD command configuration
    command:
      - --httpslisten=0.0.0.0:8443
      - --uipassword=testpassword123!
      - --lnd-mode=integrated
      - --network=regtest
      - --lnd.bitcoin.node=bitcoind
      - --lnd.bitcoind.rpchost=bitcoind:18443
      - --lnd.bitcoind.rpcuser=rpcuser
      - --lnd.bitcoind.rpcpass=rpcpassword