feat: Add phoenixd mnemonic display (#3931)

Co-authored-by: alan <alan@lnbits.com>
This commit is contained in:
Tiago Vasconcelos
2026-04-16 13:42:58 +02:00
committed by GitHub
co-authored by alan
parent 7a2ddd9826
commit 07b1521dad
7 changed files with 82 additions and 6 deletions
+4 -1
View File
@@ -324,7 +324,8 @@ class AssetSettings(LNbitsSettings):
"heif", "heif",
"heics", "heics",
"text/plain", "text/plain",
"text/json" "text/xml", "text/json",
"text/xml",
"application/json", "application/json",
"application/pdf", "application/pdf",
] ]
@@ -588,6 +589,8 @@ class ZBDFundingSource(LNbitsSettings):
class PhoenixdFundingSource(LNbitsSettings): class PhoenixdFundingSource(LNbitsSettings):
phoenixd_api_endpoint: str | None = Field(default="http://localhost:9740/") phoenixd_api_endpoint: str | None = Field(default="http://localhost:9740/")
phoenixd_api_password: str | None = Field(default=None) phoenixd_api_password: str | None = Field(default=None)
phoenixd_data_dir: str | None = Field(default=None)
phoenixd_mnemonic: str | None = Field(default=None)
class AlbyFundingSource(LNbitsSettings): class AlbyFundingSource(LNbitsSettings):
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
+2
View File
@@ -275,6 +275,8 @@ window.localisation.en = {
requires_server_restart: requires_server_restart:
'Changing these settings requires a server restart to take effect.', 'Changing these settings requires a server restart to take effect.',
funding_source_info: 'Select the active funding wallet', funding_source_info: 'Select the active funding wallet',
phoenixd_warning:
"Phoenixd mnemonic is only available if phoenixd data-dir is specified and is readable by LNbits. It's not indicative of phoenixd not running. It just means LNbits cannot access the mnemonic to display it here.",
latest_update: 'You are on the latest version {version}.', latest_update: 'You are on the latest version {version}.',
notifications: 'Notifications', notifications: 'Notifications',
notifications_configure: 'Configure Notifications', notifications_configure: 'Configure Notifications',
@@ -202,7 +202,18 @@ window.app.component('lnbits-admin-funding-sources', {
'Phoenixd', 'Phoenixd',
{ {
phoenixd_api_endpoint: 'Endpoint', phoenixd_api_endpoint: 'Endpoint',
phoenixd_api_password: 'Key' phoenixd_api_password: 'Key',
phoenixd_data_dir: {
label: 'Data Directory',
hint: 'Directory where phoenixd stores its data, including the seed phrase.'
},
phoenixd_mnemonic: {
label: 'Phoenixd Seed Phrase',
hint: 'Only available if phoenixd data-dir is specified',
readonly: true,
copy: true,
qrcode: true
}
} }
], ],
[ [
@@ -52,6 +52,7 @@
:label="prop.label" :label="prop.label"
:hint="prop.hint" :hint="prop.hint"
:value="prop.value" :value="prop.value"
:readonly="prop.readonly || false"
> >
<q-btn <q-btn
v-if="prop.copy" v-if="prop.copy"
@@ -73,6 +74,15 @@
></q-btn> ></q-btn>
</q-input> </q-input>
</div> </div>
<p
v-if="fund === 'PhoenixdWallet' && key === 'phoenixd_mnemonic'"
class="col-12 q-my-md"
>
<span>
<q-icon name="warning" color="orange" size="xs"></q-icon>
<span v-text="$t('phoenixd_warning')"></span>
</span>
</p>
</div> </div>
<q-expansion-item <q-expansion-item
v-if=" v-if="
+52 -2
View File
@@ -4,9 +4,11 @@ import hashlib
import json import json
import urllib.parse import urllib.parse
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from pathlib import Path
from typing import Any from typing import Any
import httpx import httpx
from embit.bip39 import mnemonic_is_valid
from httpx import RequestError, TimeoutException from httpx import RequestError, TimeoutException
from loguru import logger from loguru import logger
from websockets import connect from websockets import connect
@@ -61,6 +63,8 @@ class PhoenixdWallet(Wallet):
} }
self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers) self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers)
self._seed_mnemonic_to_persist: str | None = None
self._load_mnemonic_from_seed_file()
async def cleanup(self): async def cleanup(self):
try: try:
@@ -69,6 +73,7 @@ class PhoenixdWallet(Wallet):
logger.warning(f"Error closing wallet connection: {e}") logger.warning(f"Error closing wallet connection: {e}")
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
await self._persist_loaded_mnemonic()
try: try:
r = await self.client.get("/getinfo", timeout=10) r = await self.client.get("/getinfo", timeout=10)
r.raise_for_status() r.raise_for_status()
@@ -101,7 +106,6 @@ class PhoenixdWallet(Wallet):
unhashed_description: bytes | None = None, unhashed_description: bytes | None = None,
**kwargs, **kwargs,
) -> InvoiceResponse: ) -> InvoiceResponse:
try: try:
msats_amount = amount msats_amount = amount
data: dict[str, Any] = { data: dict[str, Any] = {
@@ -309,7 +313,7 @@ class PhoenixdWallet(Wallet):
and message_json.get("type") == "payment_received" and message_json.get("type") == "payment_received"
): ):
logger.info( logger.info(
f'payment-received: {message_json["paymentHash"]}' f"payment-received: {message_json['paymentHash']}"
) )
yield message_json["paymentHash"] yield message_json["paymentHash"]
@@ -319,3 +323,49 @@ class PhoenixdWallet(Wallet):
"retrying in 5 seconds" "retrying in 5 seconds"
) )
await asyncio.sleep(5) await asyncio.sleep(5)
def _load_mnemonic_from_seed_file(self):
data_dir = settings.phoenixd_data_dir
if not data_dir:
return
seed_path = Path(data_dir).expanduser() / "seed.dat"
if not seed_path.is_file():
return
try:
mnemonic = seed_path.read_text(encoding="utf-8").strip()
if mnemonic == settings.phoenixd_mnemonic:
return
except OSError as exc:
logger.warning(f"Failed to read Phoenixd seed file '{seed_path}': {exc}")
return
if not mnemonic:
logger.warning(f"Phoenixd seed file '{seed_path}' is empty.")
return
if not mnemonic_is_valid(mnemonic):
logger.warning(
f"Phoenixd seed file '{seed_path}' does not contain a valid "
"BIP39 mnemonic."
)
return
settings.phoenixd_mnemonic = mnemonic
self._seed_mnemonic_to_persist = mnemonic
async def _persist_loaded_mnemonic(self):
if not self._seed_mnemonic_to_persist:
return
logger.info("Updating 'PHOENIXD_MNEMONIC' mnemonic settings.")
try:
from lnbits.core.crud.settings import set_settings_field
await set_settings_field(
"phoenixd_mnemonic", self._seed_mnemonic_to_persist
)
self._seed_mnemonic_to_persist = None
except Exception as exc:
logger.warning(f"Failed to persist Phoenixd mnemonic: {exc}")