Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3372f7463a | ||
|
|
3f4fd93858 |
@@ -23,10 +23,3 @@ mypy.ini
|
||||
package-lock.json
|
||||
package.json
|
||||
pytest.ini
|
||||
|
||||
.mypy_cache
|
||||
.github
|
||||
.pytest_cache
|
||||
.vscode
|
||||
bin
|
||||
dist
|
||||
|
||||
@@ -10,29 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create github pre-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh release create "$tag" --prerelease --generate-notes --draft
|
||||
- id: get_upload_url
|
||||
name: Get upload url of Github release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: |
|
||||
upload_url=$(gh release view "$tag" --json uploadUrl -q ".uploadUrl")
|
||||
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
docker:
|
||||
if: github.repository == 'lnbits/lnbits'
|
||||
uses: ./.github/workflows/docker.yml
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
@@ -41,7 +19,6 @@ jobs:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
pypi:
|
||||
if: github.repository == 'lnbits/lnbits'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install dependencies for building secp256k1
|
||||
@@ -53,10 +30,3 @@ jobs:
|
||||
uses: JRubics/poetry-publish@v1.15
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
|
||||
appimage:
|
||||
needs: [ release ]
|
||||
uses: ./.github/workflows/appimage.yml
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
upload_url: ${{ needs.release.outputs.upload_url }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create github release
|
||||
@@ -23,13 +23,7 @@ jobs:
|
||||
tag: ${{ github.ref_name }}
|
||||
run: |
|
||||
gh release create "$tag" --generate-notes --draft
|
||||
- id: get_upload_url
|
||||
name: Get upload url of Github release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref_name }}
|
||||
run: |
|
||||
upload_url=$(gh release view "$tag" --json uploadUrl -q ".uploadUrl")
|
||||
upload_url=$(gh release view "$tag" --repo "${{ github.repository }}" --json uploadUrl -q ".uploadUrl")
|
||||
echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
|
||||
|
||||
docker:
|
||||
|
||||
+20
-1
@@ -6,6 +6,7 @@ import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from os import path
|
||||
@@ -18,6 +19,21 @@ from loguru import logger
|
||||
from pydantic import BaseModel, BaseSettings, Extra, Field, validator
|
||||
|
||||
|
||||
def generate_default_boltz_mnemonic() -> str | None:
|
||||
"""
|
||||
Generate a BIP39 mnemonic using the bundled embit dependency.
|
||||
If embit is unavailable at runtime, return None to avoid blocking startup.
|
||||
"""
|
||||
try:
|
||||
from embit import bip39
|
||||
except Exception as exc: # pragma: no cover - dependency missing at runtime
|
||||
logger.warning(f"Unable to generate Boltz mnemonic (embit missing): {exc}")
|
||||
return None
|
||||
|
||||
entropy = secrets.token_bytes(16) # 128 bits -> 12-word mnemonic
|
||||
return bip39.mnemonic_from_bytes(entropy)
|
||||
|
||||
|
||||
def list_parse_fallback(v: str):
|
||||
v = v.replace(" ", "")
|
||||
if len(v) > 0:
|
||||
@@ -601,9 +617,12 @@ class BreezLiquidSdkFundingSource(LNbitsSettings):
|
||||
class BoltzFundingSource(LNbitsSettings):
|
||||
boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002")
|
||||
boltz_client_macaroon: str | None = Field(default=None)
|
||||
boltz_client_wallet: str | None = Field(default="lnbits")
|
||||
boltz_client_password: str = Field(default="")
|
||||
boltz_client_cert: str | None = Field(default=None)
|
||||
boltz_mnemonic: str | None = Field(default=None)
|
||||
boltz_mnemonic: str | None = Field(
|
||||
default_factory=generate_default_boltz_mnemonic
|
||||
)
|
||||
|
||||
|
||||
class StrikeFundingSource(LNbitsSettings):
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -161,13 +161,21 @@ window.app.component('lnbits-admin-funding-sources', {
|
||||
'Boltz',
|
||||
{
|
||||
boltz_client_endpoint: 'Endpoint',
|
||||
boltz_client_macaroon: 'Admin Macaroon path or hex',
|
||||
boltz_client_cert: 'Certificate path or hex',
|
||||
boltz_client_macaroon: {
|
||||
label: 'Admin Macaroon path or hex',
|
||||
default: '/home/ubuntu/.boltz/macaroons/admin.macaroon'
|
||||
},
|
||||
boltz_client_cert: {
|
||||
label: 'Certificate path or hex',
|
||||
default: '/home/ben/.boltz/tls.cert'
|
||||
},
|
||||
boltz_client_wallet: 'Wallet Name',
|
||||
boltz_client_password: 'Wallet Password (can be empty)',
|
||||
boltz_mnemonic: {
|
||||
label: 'Liquid mnemonic (copy into greenwallet)',
|
||||
copy: true,
|
||||
qrcode: true
|
||||
qrcode: true,
|
||||
hint: 'Paste or type your liquid mnemonic'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+32
-78
@@ -5,7 +5,7 @@ from bolt11.decode import decode
|
||||
from grpc.aio import AioRpcError
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint, sha256s
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
from lnbits.settings import settings
|
||||
from lnbits.wallets.boltz_grpc_files import boltzrpc_pb2, boltzrpc_pb2_grpc
|
||||
from lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc import grpc
|
||||
@@ -38,6 +38,10 @@ class BoltzWallet(Wallet):
|
||||
raise ValueError(
|
||||
"cannot initialize BoltzWallet: missing boltz_client_endpoint"
|
||||
)
|
||||
if not settings.boltz_client_wallet:
|
||||
raise ValueError(
|
||||
"cannot initialize BoltzWallet: missing boltz_client_wallet"
|
||||
)
|
||||
|
||||
self.endpoint = normalize_endpoint(
|
||||
settings.boltz_client_endpoint, add_proto=True
|
||||
@@ -59,20 +63,31 @@ class BoltzWallet(Wallet):
|
||||
|
||||
self.rpc = boltzrpc_pb2_grpc.BoltzStub(channel)
|
||||
self.wallet_id = 0
|
||||
self.wallet_name = "lnbits"
|
||||
|
||||
if settings.boltz_mnemonic: # restore wallet from mnemonic
|
||||
self._init_wallet_task = asyncio.create_task(
|
||||
self._restore_boltz_wallet(
|
||||
settings.boltz_mnemonic, settings.boltz_client_password
|
||||
)
|
||||
)
|
||||
else: # create new wallet
|
||||
self._init_wallet_task = asyncio.create_task(self._create_boltz_wallet())
|
||||
# Auto-create wallet if running in Docker mode
|
||||
async def _init_boltz_wallet():
|
||||
try:
|
||||
wallet_name = settings.boltz_client_wallet or "lnbits"
|
||||
mnemonic = await self._fetch_wallet(wallet_name)
|
||||
if mnemonic:
|
||||
logger.info(
|
||||
"✅ Mnemonic found for Boltz wallet, saving to settings"
|
||||
)
|
||||
settings.boltz_mnemonic = mnemonic
|
||||
|
||||
from lnbits.core.crud.settings import set_settings_field
|
||||
|
||||
await set_settings_field("boltz_mnemonic", mnemonic)
|
||||
else:
|
||||
logger.warning("⚠️ No mnemonic returned from Boltz")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to auto-create Boltz wallet: {e}")
|
||||
|
||||
self._init_wallet_task = asyncio.create_task(_init_boltz_wallet())
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
request = boltzrpc_pb2.GetWalletRequest(name=self.wallet_name)
|
||||
request = boltzrpc_pb2.GetWalletRequest(name=settings.boltz_client_wallet)
|
||||
response: boltzrpc_pb2.Wallet = await self.rpc.GetWallet(
|
||||
request, metadata=self.metadata
|
||||
)
|
||||
@@ -238,85 +253,24 @@ class BoltzWallet(Wallet):
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def _check_wallet_exists(
|
||||
self, wallet_name: str
|
||||
) -> boltzrpc_pb2.Wallet | None:
|
||||
async def _fetch_wallet(self, wallet_name: str) -> str | None:
|
||||
try:
|
||||
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
|
||||
response = await self.rpc.GetWallet(request, metadata=self.metadata)
|
||||
logger.info(f"Wallet '{wallet_name}' already exists with ID {response.id}")
|
||||
return response
|
||||
return settings.boltz_mnemonic
|
||||
except AioRpcError as exc:
|
||||
details = exc.details() or "unknown error"
|
||||
if exc.code() != grpc.StatusCode.NOT_FOUND:
|
||||
logger.warning(f"Wallet '{wallet_name}' does not exist.")
|
||||
return None
|
||||
logger.error(f"Error checking wallet existence: {exc.details()}")
|
||||
return None
|
||||
logger.error(f"Error checking wallet existence: {details}")
|
||||
raise
|
||||
|
||||
async def _delete_wallet(self, wallet_id: int) -> None:
|
||||
logger.info(f"Deleting wallet '{wallet_id}'")
|
||||
delete_request = boltzrpc_pb2.RemoveWalletRequest(id=wallet_id)
|
||||
await self.rpc.RemoveWallet(delete_request, metadata=self.metadata)
|
||||
|
||||
async def _restore_wallet(self, wallet_name: str, mnemonic: str, password: str):
|
||||
logger.info(f"Restoring wallet '{wallet_name}' from mnemonic")
|
||||
credentials = boltzrpc_pb2.WalletCredentials(mnemonic=mnemonic)
|
||||
params = boltzrpc_pb2.WalletParams(
|
||||
name=wallet_name,
|
||||
currency=boltzrpc_pb2.LBTC,
|
||||
password=password,
|
||||
)
|
||||
restore_request = boltzrpc_pb2.ImportWalletRequest(
|
||||
credentials=credentials, params=params
|
||||
)
|
||||
response = await self.rpc.ImportWallet(restore_request, metadata=self.metadata)
|
||||
return response
|
||||
|
||||
async def _create_wallet(self, wallet_name: str, password: str) -> str:
|
||||
logger.info(f"Creating new wallet '{wallet_name}'")
|
||||
params = boltzrpc_pb2.WalletParams(
|
||||
name=wallet_name,
|
||||
currency=boltzrpc_pb2.LBTC,
|
||||
password=password,
|
||||
password=settings.boltz_client_password,
|
||||
)
|
||||
create_request = boltzrpc_pb2.CreateWalletRequest(params=params)
|
||||
response = await self.rpc.CreateWallet(create_request, metadata=self.metadata)
|
||||
return response.mnemonic
|
||||
|
||||
async def _restore_boltz_wallet(self, mnemonic: str, password: str):
|
||||
try:
|
||||
# delete the wallet with to name without hashing first if it exists
|
||||
wallet = await self._check_wallet_exists(self.wallet_name)
|
||||
if wallet:
|
||||
await self._delete_wallet(wallet.id)
|
||||
|
||||
# recreate the wallet with the hashed name
|
||||
self.wallet_name = sha256s(mnemonic + password)
|
||||
wallet = await self._check_wallet_exists(self.wallet_name)
|
||||
if wallet:
|
||||
logger.success("✅ Boltz wallet exists.")
|
||||
return
|
||||
await self._restore_wallet(self.wallet_name, mnemonic, password)
|
||||
logger.success("✅ Boltz wallet restored from existing mnemonic")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to restore Boltz wallet: {e}")
|
||||
|
||||
async def _create_boltz_wallet(self):
|
||||
try:
|
||||
# delete the wallet with to name without hashing first if it exists
|
||||
wallet = await self._check_wallet_exists(self.wallet_name)
|
||||
if wallet:
|
||||
await self._delete_wallet(wallet.id)
|
||||
|
||||
logger.info("No Mnemonic found for Boltz wallet, creating wallet...")
|
||||
mnemonic = await self._create_wallet(
|
||||
self.wallet_name, settings.boltz_client_password
|
||||
)
|
||||
logger.success("✅ Boltz wallet created successfully")
|
||||
settings.boltz_mnemonic = mnemonic
|
||||
from lnbits.core.crud.settings import set_settings_field
|
||||
|
||||
await set_settings_field("boltz_mnemonic", mnemonic)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to create Boltz wallet: {e}")
|
||||
|
||||
Generated
+9
-4
@@ -1333,16 +1333,21 @@ pyjwt = ">=2.10.1,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.20.1"
|
||||
version = "3.18.0"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"},
|
||||
{file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"},
|
||||
{file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
|
||||
{file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
|
||||
typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
|
||||
|
||||
[[package]]
|
||||
name = "filetype"
|
||||
version = "1.2.0"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "lnbits"
|
||||
version = "1.4.0-rc3"
|
||||
version = "1.4.0-rc2"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||
|
||||
Reference in New Issue
Block a user