-
Notifications
You must be signed in to change notification settings - Fork 0
Installation
-
LNbits
>= 1.5.6(the background task manager differs between releases: the extension useslnbits.task_managerwhen available and falls back to the legacylnbits.tasksmodule on LNbits 1.5.x — both work, but nothing below 1.5.x has been tested). -
Python
>= 3.10(whatever ships with your LNbits image). - A working Lightning backend configured in LNbits for the extension's wallet backend. See Lightning backend below.
- Network access to the Nostr relays you configure (
wss://…).
The extension adds no new Python dependencies. It reuses LNbits' bundled
coincurve, websockets, and bech32 packages and LNbits' own Nostr utilities
(lnbits.utils.nostr).
v0.1.3 is released (icon fix on top of the v0.1.2 frontend rendering fix +
header nav links — see Development). Once the
registry pull request
(lnbits/lnbits-extensions #832)
is merged it will be installable from the LNbits extension manager:
- Log in to LNbits.
- Open Extensions and enable CLINK for your user.
- The extension appears under CLINK in the sidebar.
Until the PR is merged (or if you do not want to wait), use Option B.
The release pipeline automatically publishes GitHub releases and opens a pull request to the official
lnbits/lnbits-extensionsregistry.
If you do not want to wait for the registry PR (or the extension manager is not available), install by placing the code in LNbits' extensions directory:
# Inside the LNbits app folder
cd extensions
git clone https://github.com/WoompaLoompa/lnbits-clink.git clinkImportant: LNbits scans the directory
<LNBITS_EXTENSIONS_PATH>/extensions/ (that is, it appends an extensions
sub-folder to the configured path — the default lnbits gives
lnbits/extensions). The extension folder must be named clink — LNbits
derives the extension id from the folder name and imports it as the clink
Python package. (The repository is named lnbits-clink only because GitHub
disallows single-word lowercase repo names.)
Then restart LNbits. On a stock install (non-admin-gated extension) the extension is available to every user without per-user enablement.
The repository ships a top-level manifest.json
({"repos":[{"id":"clink","organisation":"WoompaLoompa","repository":"lnbits-clink"}]}).
LNbits (>= 1.5.6) merges manifest URLs into the installable-extension listing,
so clink becomes installable from the extension manager before the
registry PR is merged — no manual copy needed:
# Add the manifest URL alongside the default registry manifest
LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/WoompaLoompa/lnbits-clink/main/manifest.json"For each repo entry LNbits resolves the repository's releases/latest
GitHub release and the config.json from its default branch to build an
installable extension. The extension id comes from config.json["id"]
(clink).
GitHub rate limits: manifest resolution calls the GitHub API (
releases/latest) and is rate-limited to 60 requests/hour per IP for unauthenticated clients. A busy Fly machine shares its IP, so this budget is easily exhausted. SetLNBITS_EXT_GITHUB_TOKENto a GitHub token (fine-grained, read-onlyContentson the extension repo) to avoid intermittent failures — LNbits documents this setting as "required due to GitHub rate-limit":fly secrets set -a <app> LNBITS_EXT_GITHUB_TOKEN=<token>
The LNbits Docker image ships without extensions, and anything written to the container filesystem is lost on redeploy. Extensions must live on a persistent volume.
-
Point
LNBITS_EXTENSIONS_PATHat a path on your volume infly.tomland redeploy:[env] LNBITS_EXTENSIONS_PATH = "/app/data/extensions"
LNbits creates
<path>/extensions/automatically on startup. -
Upload the code.
gitis usually not installed in the container, so package the repo locally and push it over SFTP:tar czf clink.tgz --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' \ -C /path/to/lnbits-clink . printf 'put clink.tgz /tmp/clink.tgz\nbye\n' | fly ssh sftp shell -a clink-lnbits
-
Extract it into the volume (note the nested
extensionsfolder):fly ssh console -a clink-lnbits -C 'sh -s' <<'EOF' mkdir -p /app/data/extensions/extensions/clink tar xzf /tmp/clink.tgz -C /app/data/extensions/extensions/clink rm /tmp/clink.tgz ls /app/data/extensions/extensions/clink/config.json EOF
-
Restart the machine so LNbits discovers the extension and runs migrations:
fly machine restart <machine-id> -a clink-lnbits
-
Verify (see Verifying the install).
- Extensions stored under
/app/data/...survivefly deployand machine restarts because the volume is persistent. - The LNbits extension builder in the admin UI (
/extensions/builder) can only build from the official registry — it cannot import an existing repo, so it is not usable for this extension. - On the demo app
clink-lnbitsthe Lightning backend is nowClinkWallet(see CLINK as the Lightning backend): invoices are requested from a real Lightning.pub node over Nostr. Earlier the backend wasFakeWallet(in-memory, always "payable"), which is still useful for walking the UI/protocol flow without moving real funds. - The demo app runs both manifests:
LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/WoompaLoompa/lnbits-clink/main/manifest.json", soclinkshows up in the extension manager there.
The superadmin flow differs from the old per-user extension toggle:
-
With
LNBITS_ADMIN_UI=true, LNbits auto-generates a superuser on first run and stores it in<data>/data/.super_user. Read it over SSH:fly ssh console -a clink-lnbits -C 'cd /app && uv run lnbits-cli superuser' -
Open
https://<your-lnbits>/adminand log in as the superuser (or uselnbits-cli superuser-urlto get a?usr=<id>deep link). -
Create a wallet/user to click around with:
fly ssh console -a clink-lnbits -C 'sh -s' <<'EOF' cd /app uv run lnbits-cli users new -u alice -p change-me EOF
-
Log in as that user at the dashboard. On LNbits 1.5.x, a non-admin-gated extension like
clinkis enabled for every user automatically — the per-userextensionslist in the admin API only gates extensions listed inLNBITS_ADMIN_EXTENSIONS. The CLINK pages are then reachable at/clink/payand/clink/subscriptions.
The extension configures its node with a single environment variable:
-
LNBITS_CLINK_FUNDING_NOFFER— anoffer1...string that points at a Lightning.pub node's offer service. When set, theClinkWalletfunding source is ready to receive; when unset it reports a clear error and LNbits falls back toVoidWalletafter the startup retries. -
Backend wallet: the extension is not just a consumer of whatever backend is configured — it can be the backend (see below). The public checkout, debit settlement, and subscriptions call
create_invoice/pay_invoiceon the configured backend. -
First relay: you add relays from the UI or the API (
/clink/api/v1/relays). Nothing works until at least one relay is enabled.
For a real end-to-end experience (actually creating and paying Lightning invoices) you need a real Lightning backend such as:
-
LND (
LNDWallet) -
Core Lightning (
CoreLightningWallet) -
Eclair (
EclairWallet) - A remote service such as Alby (
AlbyWallet)
The default VoidWallet backend reports "cannot create invoices" — it is only
useful for exercising the protocol layer, not for payments. FakeWallet
(in-memory) lets you walk the full checkout/subscription UI flow.
Since v0.1.4 CLINK itself can be the funding source: invoices are requested
from a remote Lightning.pub node's offer service (kind 21001) using only the
node's noffer1... string. Receive-first: create_invoice is fully wired;
pay_invoice returns a "receive-only" error until a Lightning.pub account is
added to the node connection (planned milestone).
clink/__init__.py registers the ClinkWallet class on the lnbits.wallets
module at extension load. LNbits resolves the backend with
getattr(lnbits.wallets, settings.lnbits_backend_wallet_class), and because
extensions are imported before that lookup at startup, selecting
ClinkWallet requires no core changes. The class implements the same
Wallet API as the bundled backends (verified against LNbits 1.5.6 and
1.6.x).
-
Make
ClinkWalletselectable — add it to the allowed funding sources.lnbits_allowed_funding_sourcesis not stored in the DB on this instance, so it comes from the env secret:fly secrets set -a clink-lnbits \ "LNBITS_ALLOWED_FUNDING_SOURCES=<the-22-core-defaults>,ClinkWallet"
(Keep the existing defaults in the value or the other backends vanish from the dropdown.) The dropdown in
/admin → Funding Sourceswill then listClinkWallet. -
Point the wallet at the node — set the noffer secret (this is what the
ClinkWalletconstructor reads):fly secrets set -a clink-lnbits "LNBITS_CLINK_FUNDING_NOFFER=noffer1..."
fly secrets setrestarts the machine. Settings precedence note: values in thesystem_settingstable override env vars, so do the DB step below too. -
Switch the backend class in the DB — this is the exact row the admin UI writes when you pick a backend and save.
lnbits_backend_wallet_classis stored insystem_settings(JSON-encoded string), and it takes precedence overLNBITS_BACKEND_WALLET_CLASS:fly ssh console -a clink-lnbits -C 'sh -c "python3 - <<\"PY\" import sqlite3 db = sqlite3.connect(\"/app/data/database.sqlite3\") db.execute(\"UPDATE system_settings SET value=? WHERE id=?\", ('\"'\"'\"ClinkWallet\"'\"'\"', \"'\"'\"'lnbits_backend_wallet_class'\"'\"'\")) db.commit() PY"'
Alternatively — and equivalent — select
ClinkWalletin/admin → Funding Sources → LNbits Wallet (Current backend wallet)and save. -
Restart and verify:
fly machine restart <machine-id> -a clink-lnbits fly logs -a clink-lnbits --no-tail -n 200 | grep -i "funding source" # expect: clink: registered ClinkWallet as an LNbits funding source # Funding source: ClinkWallet # SUCCESS | ✔️ Backend ClinkWallet connected and with a balance of 0 msat.
status()only validates the noffer (no network call), so a healthy boot does not guarantee the relay is reachable — create a real invoice to check the receive path:fly ssh console -a clink-lnbits -C 'cd /app && PYTHONPATH=/app/data/extensions/extensions \ /app/.venv/bin/python - <<"PY" import asyncio, clink, lnbits.wallets async def main(): w = lnbits.wallets.ClinkWallet() print(await w.status()) r = await w.create_invoice(1000, "check", "check") print(r.ok, r.error_message, r.payment_request[:40]) asyncio.run(main()) PY'
ok: True+ a BOLT11 string means the node answered over the relay.
-
Watchdog: the automatic voiding check
(
lnbits_watchdog_switch_to_voidwallet) is off on this instance. If you enable it, the watchdog compares tracked vs. node balance — with a receive-only wallet that reports 0 msat, the balance delta may trigger a switch, so keep it off (or pair it with a node-account wallet once that lands). -
Receive-only caveat: while
ClinkWalletis the backend, every invoice on the instance comes from the remote node, and the instance cannot pay out. KeepFakeWalletas an alternative in the allowed list to switch back quickly if needed.
# Public info endpoint: relay list + listener state
curl https://<your-lnbits>/clink/api/v1/info
# Auth-protected pages (401 without a user context is expected)
curl -i https://<your-lnbits>/clink/pay
# In the container
uv run lnbits-cli extensions list
# Installed extensions:
# - clink (0.0)On startup the LNbits log confirms migrations and background tasks:
running migration clink.1
running migration clink.2
running migration clink.3
✔️ All migrations done.
clink: listener task started
clink: subscription poller started
-
Blank/white extension pages (
/clink/pay,/clink/subscriptions) with a console error likeCannot read properties of undefined (reading 'user')mean the frontend read the oldthis.gobject. On LNbits 1.5.x extension pages must usewindow.ginstead (see Development). Use a release that includes the fix (v0.1.1+). -
Tabs/dialog inputs stacking on top of each other (Offers/Debits/Relays tabs collapsing into one another, overlapping form fields) means the page still ships self-closing component tags (
<q-tab ... />). The in-DOM template is browser-parsed before Vue compiles it, so self-closed non-void tags swallow their siblings. Usev0.1.2+(see Development). -
Extension card shows version
0.0and a broken icon in the LNbits extension manager. That happens when the extension was installed by copying the repo folder into the extensions directory: LNbits records version0.0and no GitHub metadata. Reinstall from a release (via the extension manager or a manifest) so theinstalled_extensionsrow gets the real version (0.1.3) and an absolute icon URL (https://github.com/WoompaLoompa/lnbits-clink/raw/main/static/clink.png). -
A button/tab icon renders as two or three icons (e.g. the Plans tab showed
event+repeat). The icon name isn't a single ligature in the loaded Material Icons font. Use a single-glyph icon name (see Development); the affected names were fixed inv0.1.3.
lnbits-clink — CLINK (Nostr-native Lightning) for LNbits.