Compare commits

...
Author SHA1 Message Date
dni ⚡andGitHub 7880044483 chore: update to v1.5.1-rc1 (#3858) 2026-03-09 09:10:48 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
28b3f79d9b chore(deps-dev): bump minimatch from 3.1.2 to 3.1.5 (#3815)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 08:29:20 +01:00
Tiago VasconcelosandGitHub 4c704305ac Fix: mobile view wallet issues (#3850) 2026-03-05 10:36:52 +00:00
c47fd2a4d1 SparkL2 updated docs (#3810)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2026-03-05 10:19:19 +02:00
ArcandGitHub 1255df43e6 fix: default theme change for 1.5.1 (#3838) 2026-03-05 10:09:54 +02:00
ArcandGitHub 6ccc10d327 feat: currency helper (#3849) 2026-03-05 10:09:23 +02:00
dni ⚡andGitHub 8bcf5e4f70 CI: pin boltz to version (#3852) 2026-03-05 09:54:07 +02:00
Vlad StanandGitHub 04ec643c93 feat: set sidecar mnemonic if missing (#3839) 2026-03-04 16:13:46 +02:00
18 changed files with 645 additions and 583 deletions
+8 -1
View File
@@ -191,7 +191,14 @@ BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon"
# HEXSTRING instead of path also possible # HEXSTRING instead of path also possible
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert" BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert"
# TODO: add Spark # SparkL2Wallet (external sidecar: https://github.com/lnbits/spark_sidecar)
SPARK_L2_NETWORK=MAINNET
SPARK_L2_EXTERNAL_ENDPOINT=http://127.0.0.1:8765
SPARK_L2_EXTERNAL_API_KEY=
# optional tuning
# SPARK_L2_PAY_WAIT_MS=4000
# SPARK_L2_PAY_POLL_MS=500
# SPARK_L2_STREAM_KEEPALIVE_MS=15000
# StrikeWallet # StrikeWallet
STRIKE_API_ENDPOINT=https://api.strike.me/v1 STRIKE_API_ENDPOINT=https://api.strike.me/v1
+5 -2
View File
@@ -26,8 +26,10 @@ password = "boltz"
host = "0.0.0.0" host = "0.0.0.0"
port = 9005 port = 9005
[swap]
deferredClaimSymbols = ["BTC", "L-BTC"]
[[pairs]] [[pairs]]
isLegacy = true
base = "BTC" base = "BTC"
quote = "BTC" quote = "BTC"
rate = 1 rate = 1
@@ -44,7 +46,6 @@ minSwapAmount = 50_000
swapTaproot = 10080 swapTaproot = 10080
[[pairs]] [[pairs]]
isLegacy = true
base = "L-BTC" base = "L-BTC"
quote = "BTC" quote = "BTC"
fee = 0.25 fee = 0.25
@@ -83,6 +84,7 @@ maxZeroConfAmount = 0
zmqpubrawblock = "tcp://bitcoind:29001" zmqpubrawblock = "tcp://bitcoind:29001"
port = 18_443 port = 18_443
cookie = "/root/.bitcoin/regtest/.cookie" cookie = "/root/.bitcoin/regtest/.cookie"
feeFloor = 0.2
wallet = "lnbits" wallet = "lnbits"
@@ -101,6 +103,7 @@ maxSwapAmount = 40_294_967
minSwapAmount = 10_000 minSwapAmount = 10_000
maxZeroConfAmount = 40_294_967 maxZeroConfAmount = 40_294_967
[liquid.chain] [liquid.chain]
host = "elementsd" host = "elementsd"
port = 18884 port = 18884
+1 -1
View File
@@ -28,7 +28,7 @@ services:
- lnd-2 - lnd-2
- boltz-postgres - boltz-postgres
restart: always restart: always
image: boltz/boltz:latest image: boltz/boltz:v3.12.1
ports: ports:
- 9000:9000 - 9000:9000
entrypoint: "sh -c 'sleep 30; /boltz-backend/bin/boltzd'" entrypoint: "sh -c 'sleep 30; /boltz-backend/bin/boltzd'"
+2 -2
View File
@@ -50,7 +50,7 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
| **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. | | **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
| **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. | | **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
| **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. | | **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
| **Spark (L2)** | Self-custodial | ❌ | Medium | ❌ | Medium | Automatic | Easy | Low | Minimal fees | Medium | Runs via Spark sidecar; seed-based self-custody. | | **Spark (L2)** | Self-custodial | ❌ | Medium | ❌ | High | Automatic | Easy | Low | Minimal fees | Medium | Runs via Spark sidecar; seed-based self-custody. |
| **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. | | **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
| **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. | | **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
| **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. | | **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
@@ -60,7 +60,7 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
## Spark (L2) ## Spark (L2)
Spark L2 uses a local Node.js sidecar to expose an HTTP API that LNbits can use as a funding source. It is self-custodial and secured by a standard mnemonic seed. You can let LNbits run the sidecar internally or run your own instance (see `https://github.com/lnbits/spark_sidecar`). Spark L2 uses a local Node.js sidecar to expose an HTTP API that LNbits can use as a funding source. It is self-custodial and secured by a standard mnemonic seed. Sidecar repo `https://github.com/lnbits/spark_sidecar`.
### Notes for readers ### Notes for readers
+11 -25
View File
@@ -41,8 +41,8 @@ A backend wallet is selected and configured entirely through LNbits environment
| [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) | | [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) |
| [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) | | [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) |
| [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) | | [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) |
| [Spark (L2)](#spark-l2) | | | | [Spark L2](#spark-l2) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
| [Cliche Wallet](#cliche-wallet) | [ZBD](#zbd) | [Phoenixd](#phoenixd) | | [Cliche Wallet](#cliche-wallet) | | |
| [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) | | [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) |
| [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) | | [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) |
| [Fake Wallet](#fake-wallet) | | | | [Fake Wallet](#fake-wallet) | | |
@@ -128,32 +128,18 @@ Old REST interface using [RTL c-lightning-REST](https://github.com/Ride-The-Ligh
- `SPARK_URL`: `http://10.147.17.230:9737/rpc` - `SPARK_URL`: `http://10.147.17.230:9737/rpc`
- `SPARK_TOKEN`: `secret_access_key` - `SPARK_TOKEN`: `secret_access_key`
## Spark (L2) ## Spark L2
Spark L2 is a self-custodial funding source that uses a small Node.js sidecar to talk to the Spark network and expose an HTTP API. You can let LNbits run the sidecar internally, or run it yourself and point LNbits at it. Self-custodial funding source using the [Spark L2](https://docs.spark.money/start/overview) network. Requires a Node.js [sidecar](https://github.com/lnbits/spark_sidecar) that bridges lnbits talking to Spark. Works in addition with any Spark-compatible seed (Wallet of Satoshi, BuhoGO, BlitzWallet).
If the sidecar is started with a `mnemonic` then that mnemonic will be used. Otherwhise if a mnemonic is set for the `Spark L2` LNbits funding source then that mnemonic will be used.
**Required env vars** ### Optional tuning
- `LNBITS_BACKEND_WALLET_CLASS`: `SparkL2Wallet` - `SPARK_L2_PAY_WAIT_MS`: `4000` _(payment timeout in ms)_
- `SPARK_L2_PAY_POLL_MS`: `500` _(polling interval in ms)_
- `SPARK_L2_STREAM_KEEPALIVE_MS`: `15000` _(SSE keepalive in ms)_
**Internal sidecar (LNbits-managed)** ### Example: run the sidecar
- `SPARK_L2_INTERNAL_SIDECAR_VERSION`: `0.1.1`
- `SPARK_L2_MNEMONIC`: `...` (optional; auto-generated if blank)
- `SPARK_L2_NETWORK`: `MAINNET`
Optional tuning:
- `SPARK_L2_PAY_WAIT_MS`: `4000`
- `SPARK_L2_PAY_POLL_MS`: `500`
- `SPARK_L2_STREAM_KEEPALIVE_MS`: `15000`
**External sidecar**
- `SPARK_L2_EXTERNAL_ENDPOINT`: `http://127.0.0.1:8765`
- `SPARK_L2_EXTERNAL_API_KEY`: `...` (if your sidecar requires auth)
Example: run the sidecar manually (external mode)
```bash ```bash
git clone https://github.com/lnbits/spark_sidecar.git git clone https://github.com/lnbits/spark_sidecar.git
@@ -167,7 +153,7 @@ SPARK_PAY_WAIT_MS=20000 \
node server.mjs node server.mjs
``` ```
For testing, you can generate a 12-word mnemonic at `https://iancoleman.io/bip39/`. Store it securely — it controls your funds. Then select **Spark (L2)** as the funding source in LNbits. For testing, you can generate a 12-word mnemonic at https://iancoleman.io/bip39/. Store it securely — it controls your funds. Then select Spark (L2) as the funding source in LNbits.
## LND (REST) ## LND (REST)
+4 -3
View File
@@ -292,13 +292,13 @@ class ThemesSettings(LNbitsSettings):
lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png") lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png")
lnbits_apple_touch_icon: str | None = Field(default=None) lnbits_apple_touch_icon: str | None = Field(default=None)
lnbits_default_reaction: str = Field(default="confettiBothSides") lnbits_default_reaction: str = Field(default="confettiBothSides")
lnbits_default_theme: str = Field(default="salvador") lnbits_default_theme: str = Field(default="bitcoin")
lnbits_default_border: str = Field(default="hard-border") lnbits_default_border: str = Field(default="hard-border")
lnbits_default_gradient: bool = Field(default=True) lnbits_default_gradient: bool = Field(default=True)
lnbits_default_bgimage: str | None = Field(default=None) lnbits_default_bgimage: str | None = Field(default=None)
lnbits_default_dark: bool = Field(default=True) lnbits_default_dark: bool = Field(default=True)
lnbits_default_card_rounded: bool = Field(default=False) lnbits_default_card_rounded: bool = Field(default=True)
lnbits_default_card_gradient: bool = Field(default=False) lnbits_default_card_gradient: bool = Field(default=True)
lnbits_default_card_shadow: bool = Field(default=False) lnbits_default_card_shadow: bool = Field(default=False)
@@ -610,6 +610,7 @@ class SparkL2FundingSource(LNbitsSettings):
spark_l2_network: str = Field(default="MAINNET") spark_l2_network: str = Field(default="MAINNET")
spark_l2_external_endpoint: str | None = Field(default="http://localhost:8765") spark_l2_external_endpoint: str | None = Field(default="http://localhost:8765")
spark_l2_external_api_key: str | None = Field(default=None) spark_l2_external_api_key: str | None = Field(default=None)
spark_l2_mnemonic: str | None = Field(default=None)
spark_l2_pay_wait_ms: int = Field(default=4000, ge=0) spark_l2_pay_wait_ms: int = Field(default=4000, ge=0)
spark_l2_pay_poll_ms: int = Field(default=500, ge=0) spark_l2_pay_poll_ms: int = Field(default=500, ge=0)
spark_l2_stream_keepalive_ms: int = Field(default=15000, ge=0) spark_l2_stream_keepalive_ms: int = Field(default=15000, ge=0)
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -234,7 +234,12 @@ window.app.component('lnbits-admin-funding-sources', {
{ {
spark_l2_external_endpoint: { spark_l2_external_endpoint: {
label: 'External Sidecar Endpoint', label: 'External Sidecar Endpoint',
hint: 'If specified then this endpoint will be used instead of the internal sidecar. Make sure to also specify the API key if your sidecar requires authentication.', hint: 'Make sure to also specify the API key if your sidecar requires authentication.',
value: ''
},
spark_l2_mnemonic: {
label: 'External Sidecar Mnemonic',
hint: 'Mnemonic for the Spark wallet on the external sidecar. Required if the side car does not have its own mnemonic.',
value: '' value: ''
}, },
spark_l2_external_api_key: { spark_l2_external_api_key: {
@@ -17,6 +17,13 @@ window.app.component('lnbits-manage-wallet-list', {
} else { } else {
this.activeWalletId = null this.activeWalletId = null
} }
},
'g.user.wallets': {
handler() {
this.paymentEvents()
},
deep: true,
immediate: true
} }
}, },
created() { created() {
+16
View File
@@ -131,6 +131,22 @@ window._lnbitsUtils = {
currency: currency || 'sat' currency: currency || 'sat'
}).format(value) }).format(value)
}, },
getCurrencySymbol(currency) {
const code = (currency || '').toUpperCase()
if (code === 'BTC' || code === 'XBT' || code === 'SAT' || code === 'SATS') {
return '₿'
}
try {
const parts = new Intl.NumberFormat(window.i18n.global.locale, {
style: 'currency',
currency: code
}).formatToParts(0)
const symbolPart = parts.find(part => part.type === 'currency')
return symbolPart?.value || code || '¤'
} catch (e) {
return code || '¤'
}
},
formatSat(value) { formatSat(value) {
return new Intl.NumberFormat(window.i18n.global.locale).format(value) return new Intl.NumberFormat(window.i18n.global.locale).format(value)
}, },
@@ -27,12 +27,9 @@
></q-btn> ></q-btn>
<q-toolbar-title> <q-toolbar-title>
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/"> <q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
<q-img <q-avatar v-if="g.settings.customLogo" height="30px">
v-if="customLogo" <img alt="Logo" :src="g.settings.customLogo" />
height="30px" </q-avatar>
alt="Logo"
:src="customLogo"
></q-img>
<span v-else-if="g.settings.siteTitle == 'LNbits'" <span v-else-if="g.settings.siteTitle == 'LNbits'"
><strong>LN</strong>bits</span ><strong>LN</strong>bits</span
> >
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -204,7 +204,7 @@
</q-card> </q-card>
</div> </div>
<div <div
v-if="!g.mobileSimple || !$q.screen.lt.md" v-show="!g.mobileSimple || !$q.screen.lt.md"
class="col-12 col-md-5 q-gutter-y-md" class="col-12 col-md-5 q-gutter-y-md"
> >
<lnbits-wallet-extra <lnbits-wallet-extra
+35 -1
View File
@@ -8,6 +8,8 @@ from typing import Any, cast
import httpx import httpx
from bolt11 import decode as bolt11_decode from bolt11 import decode as bolt11_decode
from coincurve.keys import PrivateKey
from embit.bip39 import mnemonic_from_bytes, mnemonic_is_valid
from loguru import logger from loguru import logger
from lnbits.helpers import normalize_endpoint from lnbits.helpers import normalize_endpoint
@@ -79,6 +81,11 @@ class SparkL2Wallet(Wallet):
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:
res = await self._request("POST", "/v1/balance") res = await self._request("POST", "/v1/balance")
status = res.get("status")
if status == "missing_mnemonic":
await self._check_sidecar_mnemonic()
return StatusResponse("Spark sidecar mnemonic not set", 0)
balance_msat = res.get("balance_msat") balance_msat = res.get("balance_msat")
if balance_msat is not None: if balance_msat is not None:
return StatusResponse(None, int(balance_msat)) return StatusResponse(None, int(balance_msat))
@@ -237,7 +244,7 @@ class SparkL2Wallet(Wallet):
) -> dict[str, Any]: ) -> dict[str, Any]:
error_message = None error_message = None
try: try:
r = await self.client.request(method, path, json=json_data) r = await self.client.request(method, path, json=json_data, timeout=30)
r.raise_for_status() r.raise_for_status()
j = r.json() j = r.json()
except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc: except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc:
@@ -319,3 +326,30 @@ class SparkL2Wallet(Wallet):
if mapped.failed: if mapped.failed:
return False return False
return None return None
async def _check_sidecar_mnemonic(self):
if settings.spark_l2_mnemonic:
valid = mnemonic_is_valid(settings.spark_l2_mnemonic)
if not valid:
logger.warning("SPARK_L2_MNEMONIC is set but invalid. Please recheck!")
return
await self._set_sidecar_mnemonic(settings.spark_l2_mnemonic)
return
logger.info("SPARK_L2_MNEMONIC is not set, one will be generated for you.")
mnemonic = mnemonic_from_bytes(PrivateKey().secret)
await self._set_sidecar_mnemonic(mnemonic)
async def _set_sidecar_mnemonic(self, mnemonic: str):
logger.info("Checking 'SPARK_L2_MNEMONIC' on the Spark sidecar.")
payload = {"mnemonic": mnemonic}
resp = await self._request("POST", "/v1/mnemonic", payload)
status = resp.get("status")
logger.info(f"Spark sidecar mnemonic status: {status}")
if status == "set":
logger.info("Updating 'SPARK_L2_MNEMONIC' mnemonic settings.")
from lnbits.core.crud.settings import set_settings_field
await set_settings_field("spark_l2_mnemonic", mnemonic)
else:
logger.info("Nothing to do for 'SPARK_L2_MNEMONIC' on the Spark sidecar.")
+3 -3
View File
@@ -1386,9 +1386,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "lnbits" name = "lnbits"
version = "1.5.0" version = "1.5.1-rc1"
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
description = "LNbits, free and open-source Lightning wallet and accounts system." description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }] authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
Generated
+1 -1
View File
@@ -1247,7 +1247,7 @@ wheels = [
[[package]] [[package]]
name = "lnbits" name = "lnbits"
version = "1.5.0" version = "1.5.1rc1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiosqlite" }, { name = "aiosqlite" },