LndHub: giving each app user their own lnd wallet without a daemon per user
Wrapper for Lightning Network Daemon. It provides separate accounts for end-users
At a glance
- What is it?
- LndHub is a wrapper around the Lightning Network Daemon that serves many end-user accounts from a single lnd instance, with access scoped by macaroon. It trades full node isolation for operational simplicity, and the deployment story is the part that needs attention.
- Who is it for?
- Adopt LndHub when you are shipping a custodial-style Lightning wallet to app users and want one lnd instance to back many accounts, with macaroon scoped access and a simple HTTP API your frontend can call. Do not adopt it when users must hold their own keys, when non-Bitcoin Lightning networks are in scope, or when you need the LndHub test suite to pass, since package.json defines the test script as a failing placeholder.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 94 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One lnd, many user accounts, one macaroon per account
LndHub is described as a wrapper for the Lightning Network Daemon that provides separate accounts with minimum trust for end users. The phrase that matters is separate accounts, and the mechanism behind it is macaroon-based access control against a single lnd.
The design is the opposite of running one full node per user, which is what a non-custodial Lightning wallet requires. A non-custodial setup gives every user their own lnd, their own channel set and their own on-chain wallet, and the operator carries the cost of that many processes. LndHub consolidates: one lnd holds the funds, LndHub issues per-user credentials, and the application talks to LndHub rather than to lnd directly.
The trust consequence is direct and the README is candid about it. The account separation is a logical boundary implemented by the wrapper, not a cryptographic custody boundary, because the lnd instance and its admin macaroon sit behind it. The README calls it minimum trust, which reads as an explicit acknowledgment that trust is reduced but not eliminated. For a product where users accept a custodian, that is a workable trade. For a product promising users their own keys, it is the wrong architecture.
The repository reflects a JavaScript service built with Express, with the entry point at index.js and separate modules for the two chains of responsibility, bitcoin.js and lightning.js, plus btc-decoder.js for invoice handling. The rpc.proto file at the root is the gRPC contract it uses to speak to lnd, and the class/ and controllers/ directories hold the request handling layer.
Installing from source: bitcoind, lnd, redis and two credential files
The installation section is short because most of the work is in the dependencies rather than in LndHub itself. You need bitcoind, lnd and redis running, and the README links two external guides for Raspibolt and macOS setups before it gives the direct instructions.
The clone and install are three commands:
git clone [email protected]:BlueWallet/LndHub.git
cd LndHub
npm iAfter that comes the configuration, and this is where the operational detail lives. You edit LndHub's config.js, then copy two files into the root folder of LndHub: admin.macaroon, for Bitcoin mainnet usually stored in ~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon, and tls.cert, usually stored in ~/.lnd/tls.cert.
Two operational requirements are easy to miss. The first is wallet state. LndHub expects lnd's wallet to be unlocked, and if it is not, LndHub will attempt to unlock it with the password stored in config.lnd.password. That means the wallet password ends up in a config file, which is a decision about where your secrets live that you should make deliberately. The second is durability. The README tells you not to forget to configure disk persistence for redis, and gives the example of setting appendonly to yes in redis.conf.
There is one optional dependency you can drop. If you have no bitcoind instance, for example if you use neutrino, or no bitcoind wallet, you can remove the bitcoind settings from config.js. The README is explicit about the limit of that option: the feature is limited to Bitcoin, so it does not work if you use any other cryptocurrency with lnd, for example Litecoin.
Configuration as a JSON blob for Heroku, and Docker with redis outside
LndHub is packaged for two deployment styles, and the README describes each with a different set of caveats.
On Heroku you add three config vars: CONFIG, a json serialized config object, MACAROON, the hex-encoded admin.macaroon, and TLSCERT, the hex-encoded tls.cert. Everything that would live in config.js in a local install travels as environment variables instead, which is the standard platform constraint rather than something LndHub adds.
The Docker route is the more explicit of the two, and the README's warning is the important sentence. LndHub is available on Docker Hub as bluewalletorganization/lndhub, and this requires a separate instance of redis and lnd, and optionally bitcoind. So the container is not self-contained. It packages the LndHub service, not the Lightning node, and a compose file has to wire three or four services together. The README points at Umbrel's implementation for a concrete compose example.
The Dockerfile shows what the image does. It builds on node:16-bullseye-slim, installs python3 as a build requirement, runs npm i and npm run dockerbuild to compile the Babel sources, and copies the result into a node:16-alpine runtime image. The final stage creates a dedicated lndhub user, creates a logs folder with matching ownership, and switches to that user so the process does not run as root. It sets PORT=3000 and exposes 3000. The entrypoint copies the certificate and admin macaroon into the container before starting, which means those two files arrive as mounted secrets rather than baked into the image.
That entrypoint detail has a consequence worth planning around. The container expects LND_CERT_FILE and LND_ADMIN_MACAROON_FILE to point at the mounted files, and the copy happens at start time. A deployment that expects to rotate the macaroon without restarting will not get that behaviour.
The dependency set tells you what the service actually does
package.json lists the dependencies, and the list describes the architecture more precisely than the README does.
The RPC side is @grpc/grpc-js and @grpc/proto-loader, which is how the service reaches lnd over gRPC using rpc.proto. The state side is ioredis, and the presence of express-rate-limit, helmet, morgan and winston shows an HTTP service with rate limiting, security headers, request logging and structured logging in place. The Bitcoin side is bitcoinjs-lib and bignumber.js, which handle transaction and value handling, and bolt11 for invoice encoding. qr-image generates the QR codes an app displays, mustache renders the templates/ directory, node-fetch and request with request-promise handle outbound calls, and jayson provides a JSON-RPC client, which is how a Bitcoin backend is reached when the node is not a full bitcoind.
That last pair of points deserves emphasis. jayson plus btc-decoder.js means LndHub can talk to a remote or lightweight Bitcoin backend over JSON-RPC, which is what makes the neutrino and no-bitcoind-wallet configurations possible. bignumber.js rather than float arithmetic for balances is the right choice in a wallet, and the presence of uuid is what the per-account macaroon identifiers are built from.
The scripts section of package.json also shows how the project is built and run: dockerbuild compiles the sources with Babel while ignoring node_modules and spec files, dev and start both run babel-node against index.js, and lint runs eslint over controllers/ and class/. The version field reads 1.4.3, the license field is MIT, and the author is Igor Korsakov.
Where LndHub stops: no test suite, and the reference client
The test story is the weakest point in the repository, and it is visible rather than hidden. The package.json test script reads echo "Error: no test specified" && exit 1, which is the npm default for an unconfigured project. In other words, running npm test does not run tests, it exits with an error on purpose.
The README points elsewhere for verification, and the destinations are informative. It names a reference client implementation usable in a ReactNative or Nodejs environment, pointing at BlueWallet's lightning-custodian-wallet.js, and it names the acceptance tests that live in BlueWallet's repository under tests/integration/lightning-custodian-wallet.test.js. So the acceptance coverage for LndHub's behaviour lives in the consuming project's repository rather than in this one, and the README presents that as the place to look.
That arrangement has an obvious upside. The contract that matters is the one the mobile client depends on, and testing it from the client's side tests the real integration rather than a mock of it. The downside is equally obvious. A change to LndHub's API can break the client without anything in this repository noticing, and a team running LndHub for its own product has no test harness to run against.
There is a second gap, which is the documentation of the API itself. The repository has a doc/ directory and a templates/ directory, and the static/ directory holds the front end assets, but the README does not document the endpoints, the authentication scheme or the request formats. A client integrator is expected to read the controllers/ directory or to copy the reference client, which is a workable but slow way to integrate.
For a service that holds real funds, the absence of a runnable test suite in the repository is a genuine risk, and it is one of the things to weigh before deploying a version.
The alternative approach: a daemon per user
The real alternative to LndHub is not another wrapper but the architecture it replaces. A non-custodial Lightning wallet gives each user their own lnd instance, and the wallet software holds the node credentials rather than a server issuing them.
The difference in approach is exactly where the custody sits. With a daemon per user, the user's node holds the channels and the funds, and the operator's server never sees a private key or a seed. The operator's job is reduced to sending a handful of bytes per channel operation over the Lightning protocol, which is why custodial and non-custodial wallets are sometimes described as differing mainly in who runs the node.
The cost of that approach is process and resource management. Each lnd instance carries its own channel database, its own on-chain wallet and its own memory footprint, and users need to be online often enough for their channels to stay healthy. That is the problem LndHub exists to solve, and it solves it by collapsing all of that into one node behind a shared interface.
Two other approaches sit between the two poles, and the README points to both without endorsing either. Umbrel's implementation, referenced in the README, is a packaged deployment with its own compose file, which is closer to a managed node than to a shared backend. And the reference client, living in BlueWallet, is a wallet that holds a per-user macaroon and talks to a custodian over HTTP, which is a different client architecture from one that embeds a node.
The deciding question is therefore not technical. If your users accept a custodian holding their Lightning balance in a shared lnd, LndHub is the economical way to run it. If they do not, no amount of wrapper code changes the answer, and the daemon-per-user model is the only honest answer.
Editorial conclusion
Adopt LndHub when you are shipping a custodial-style Lightning wallet to app users and want one lnd instance to back many accounts, with macaroon scoped access and a simple HTTP API your frontend can call. Do not adopt it when users must hold their own keys, when non-Bitcoin Lightning networks are in scope, or when you need the LndHub test suite to pass, since package.json defines the test script as a failing placeholder. Verify first that your lnd wallet can be unlocked with config.lnd.password, that admin.macaroon and tls.cert are in place, that redis is configured with appendonly set to yes, and that the LndHub version you deploy matches the lnd release it talks to.
Frequently asked questions
What does LndHub do and what is its trust model?
It is a wrapper around lnd that provides separate accounts with minimum trust for end users, meaning one lnd instance backs many accounts and access is scoped per user with macaroons. The README's phrasing acknowledges that trust is reduced but not eliminated, since the lnd admin macaroon sits behind the service.
How do I install LndHub?
Clone the repository, run `npm i`, install bitcoind, lnd and redis, edit config.js, and copy admin.macaroon and tls.cert into the root folder of LndHub. The README also points to external guides for Raspibolt and macOS, and to Umbrel's docker-compose implementation.
Can LndHub run without bitcoind?
Yes, if you use neutrino or have no bitcoind wallet, you can remove the bitcoind settings from config.js. The README notes that this feature is limited to Bitcoin, so it does not work with other cryptocurrencies on lnd, such as Litecoin.
Does LndHub have a test suite I can run?
The package.json test script is the npm placeholder echo "Error: no test specified" && exit 1, so running npm test does not execute tests. The README points to acceptance tests kept in the BlueWallet repository, and to a reference client implementation in that same project.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bluewallet-lndhub)Was this useful?