Compare commits

...
Author SHA1 Message Date
dni ⚡ b0ff997ec1 make fe tests run 2025-12-23 11:14:47 +01:00
dni ⚡ eb55170905 prettier 2025-12-23 11:01:41 +01:00
dni ⚡ dc466ae738 bundle 2025-12-23 10:59:30 +01:00
dni ⚡ dd5fe8b7a2 bundle 2025-12-23 10:59:04 +01:00
dni ⚡ 5196ee6a15 reverting 2025-12-23 10:59:00 +01:00
dni ⚡ 3498cfb5b8 build 2025-12-23 10:58:58 +01:00
dni ⚡ 30ffe437a0 add watch 2025-12-23 10:58:57 +01:00
dni ⚡ 574641d432 fixup! 2025-12-23 10:58:56 +01:00
dni ⚡ 90d14629d9 build umd module 2025-12-23 10:58:54 +01:00
dni ⚡ 3be06269c2 build umd components 2025-12-23 10:58:53 +01:00
dni ⚡ d7723696d8 fixi 2025-12-23 10:58:52 +01:00
dni ⚡ 91f7b7730d betweeni 2025-12-23 10:58:51 +01:00
dni ⚡ b2aaf97cf3 test: add frontend tests for vue components 2025-12-23 10:58:46 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dni ⚡
8e0d1cc100 chore(deps): bump fastapi-sso from 0.18.0 to 0.19.0 (#3681)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
2025-12-23 09:18:33 +01:00
dni ⚡andGitHub cb3fd56647 chore: update to version v1.4.0 (#3684) 2025-12-22 14:34:03 +01:00
Vlad StanandGitHub 9f383bfee6 fix: cached user check (#3682) 2025-12-22 10:24:07 +01:00
dni ⚡andGitHub 3b2e28dd60 chore: update to v1.4.0-rc4 (#3675) 2025-12-22 09:27:16 +01:00
132192bc94 test: add boltz fundingsource to regtest (#3677)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-12-22 09:23:46 +01:00
281c3df826 fix: unlimited admin upload (#3679)
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2025-12-19 20:11:34 +00:00
Vlad StanandGitHub 08591f34c2 fix: extension spinner not stopping after install (#3680) 2025-12-19 20:01:04 +00:00
ArcandGitHub f0e8ae0f5c Adds support for stripe readers (#3678) 2025-12-19 08:23:56 +01:00
168cb726b1 Make funding source fields more explicit (#3676)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-12-18 13:44:22 +00:00
dni ⚡andGitHub 91ac245307 fix: release lnbits-boltz with proper tag (#3674) 2025-12-17 14:36:29 +01:00
dni ⚡andGitHub d098e2f710 chore: update to v1.4.0-rc3 (#3672) 2025-12-17 13:57:22 +01:00
dni ⚡andGitHub a0f65f4cda fix: Boltzclient fundingsource. use a hashed wallet_name (#3673) 2025-12-17 13:57:05 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
606ee215b4 chore(deps-dev): bump filelock from 3.18.0 to 3.20.1 (#3669)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-17 09:13:18 +01:00
dni ⚡andGitHub 7716f819f6 CI: create github prerelease and appimage (#3668) 2025-12-17 07:21:47 +01:00
40 changed files with 4438 additions and 155 deletions
+7
View File
@@ -23,3 +23,10 @@ mypy.ini
package-lock.json package-lock.json
package.json package.json
pytest.ini pytest.ini
.mypy_cache
.github
.pytest_cache
.vscode
bin
dist
-3
View File
@@ -18,7 +18,6 @@ ENABLE_LOG_TO_FILE=true
# https://loguru.readthedocs.io/en/stable/api/logger.html#file # https://loguru.readthedocs.io/en/stable/api/logger.html#file
LOG_ROTATION="100 MB" LOG_ROTATION="100 MB"
LOG_RETENTION="3 months" LOG_RETENTION="3 months"
# for database cleanup commands # for database cleanup commands
# CLEANUP_WALLETS_DAYS=90 # CLEANUP_WALLETS_DAYS=90
@@ -187,7 +186,6 @@ BOLTZ_CLIENT_ENDPOINT=127.0.0.1:9002
BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon" 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"
BOLTZ_CLIENT_WALLET="lnbits"
# StrikeWallet # StrikeWallet
STRIKE_API_ENDPOINT=https://api.strike.me/v1 STRIKE_API_ENDPOINT=https://api.strike.me/v1
@@ -333,4 +331,3 @@ LNBITS_RESERVE_FEE_PERCENT=1.0
###################################### ######################################
###### Logging and Development ####### ###### Logging and Development #######
###################################### ######################################
+15 -1
View File
@@ -12,6 +12,13 @@ jobs:
lint: lint:
uses: ./.github/workflows/lint.yml uses: ./.github/workflows/lint.yml
test-frontend:
needs: [ lint ]
uses: ./.github/workflows/make.yml
with:
make: test-frontend
npm: true
test-api: test-api:
needs: [ lint ] needs: [ lint ]
strategy: strategy:
@@ -75,7 +82,14 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.10"] python-version: ["3.10"]
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"] backend-wallet-class:
- BoltzWallet
- LndRestWallet
- LndWallet
- CoreLightningWallet
- CoreLightningRestWallet
- LNbitsWallet
- EclairWallet
with: with:
custom-pytest: "uv run pytest tests/regtest" custom-pytest: "uv run pytest tests/regtest"
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
+1
View File
@@ -62,3 +62,4 @@ jobs:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache
build-args: LNBITS_TAG=${{ inputs.tag }}
+4 -2
View File
@@ -40,8 +40,8 @@ jobs:
run: | run: |
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker cd docker
chmod +x ./tests chmod +x ./start-regtest
./tests ./start-regtest
sudo chmod -R a+rwx . sudo chmod -R a+rwx .
- name: Run pytest - name: Run pytest
@@ -63,6 +63,8 @@ jobs:
LNBITS_ENDPOINT: http://localhost:5001 LNBITS_ENDPOINT: http://localhost:5001
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee" LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
ECLAIR_URL: http://127.0.0.1:8082 ECLAIR_URL: http://127.0.0.1:8082
BOLTZ_CLIENT_ENDPOINT: 127.0.0.1:9002
BOLTZ_MNEMONIC: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
LNBITS_MAX_OUTGOING_PAYMENT_AMOUNT_SATS: 1000000000 LNBITS_MAX_OUTGOING_PAYMENT_AMOUNT_SATS: 1000000000
LNBITS_MAX_INCOMING_PAYMENT_AMOUNT_SATS: 1000000000 LNBITS_MAX_INCOMING_PAYMENT_AMOUNT_SATS: 1000000000
ECLAIR_PASS: lnbits ECLAIR_PASS: lnbits
+30
View File
@@ -10,7 +10,29 @@ permissions:
jobs: 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: docker:
if: github.repository == 'lnbits/lnbits'
uses: ./.github/workflows/docker.yml uses: ./.github/workflows/docker.yml
with: with:
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
@@ -19,6 +41,7 @@ jobs:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
pypi: pypi:
if: github.repository == 'lnbits/lnbits'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Install dependencies for building secp256k1 - name: Install dependencies for building secp256k1
@@ -30,3 +53,10 @@ jobs:
uses: JRubics/poetry-publish@v1.15 uses: JRubics/poetry-publish@v1.15
with: with:
pypi_token: ${{ secrets.PYPI_API_KEY }} 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 }}
+8 -2
View File
@@ -14,7 +14,7 @@ jobs:
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
outputs: outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.get_upload_url.outputs.upload_url }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Create github release - name: Create github release
@@ -23,7 +23,13 @@ jobs:
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
run: | run: |
gh release create "$tag" --generate-notes --draft gh release create "$tag" --generate-notes --draft
upload_url=$(gh release view "$tag" --repo "${{ github.repository }}" --json uploadUrl -q ".uploadUrl") - 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" echo "upload_url=$upload_url" >> "$GITHUB_OUTPUT"
docker: docker:
+1 -1
View File
@@ -1,4 +1,4 @@
name: tests name: tests pytest
on: on:
workflow_call: workflow_call:
+3 -2
View File
@@ -1,6 +1,7 @@
FROM boltz/boltz-client:latest AS boltz ARG LNBITS_TAG=latest
FROM lnbits/lnbits:latest FROM boltz/boltz-client:latest AS boltz
FROM lnbits/lnbits:${LNBITS_TAG}
COPY --from=boltz /bin/boltzd /bin/boltzcli /usr/local/bin/ COPY --from=boltz /bin/boltzd /bin/boltzcli /usr/local/bin/
RUN ls -l /usr/local/bin/boltzd RUN ls -l /usr/local/bin/boltzd
+4
View File
@@ -41,6 +41,10 @@ dev:
docker: docker:
docker build -t lnbits/lnbits . docker build -t lnbits/lnbits .
test-frontend:
npm install
npm test
test-wallets: test-wallets:
LNBITS_DATA_FOLDER="./tests/data" \ LNBITS_DATA_FOLDER="./tests/data" \
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \ LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
+1 -1
View File
@@ -16,7 +16,7 @@ async def create_user_asset(user_id: str, file: UploadFile, is_public: bool) ->
if file.content_type.lower() not in settings.lnbits_assets_allowed_mime_types: if file.content_type.lower() not in settings.lnbits_assets_allowed_mime_types:
raise ValueError(f"File type '{file.content_type}' not allowed.") raise ValueError(f"File type '{file.content_type}' not allowed.")
if user_id not in settings.lnbits_assets_no_limit_users: if not settings.is_unlimited_assets_user(user_id):
user_assets_count = await get_user_assets_count(user_id) user_assets_count = await get_user_assets_count(user_id)
if user_assets_count >= settings.lnbits_max_assets_per_user: if user_assets_count >= settings.lnbits_max_assets_per_user:
raise ValueError( raise ValueError(
+2 -2
View File
@@ -337,7 +337,7 @@ async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
return {"paid": False, "status": "failed"} return {"paid": False, "status": "failed"}
try: try:
status = await payment.check_status() payment = await update_pending_payment(payment)
except Exception: except Exception:
if wallet and wallet.id == payment.wallet_id: if wallet and wallet.id == payment.wallet_id:
return {"paid": False, "details": payment} return {"paid": False, "details": payment}
@@ -346,7 +346,7 @@ async def api_payment(payment_hash, x_api_key: str | None = Header(None)):
if wallet and wallet.id == payment.wallet_id: if wallet and wallet.id == payment.wallet_id:
return { return {
"paid": payment.success, "paid": payment.success,
"status": f"{status!s}", "status": f"{payment.status!s}",
"preimage": payment.preimage, "preimage": payment.preimage,
"details": payment, "details": payment,
} }
+1 -1
View File
@@ -249,7 +249,7 @@ async def check_account_id_exists(
account_id = cache.get(cache_key) account_id = cache.get(cache_key)
if account_id: if account_id:
r.scope["user_id"] = account_id.id r.scope["user_id"] = account_id.id
await _check_user_access(r, account_id, conn=conn) await _check_user_access(r, account_id.id, conn=conn)
return account_id return account_id
account = await _check_account_exists(r, access_token, usr, conn=conn) account = await _check_account_exists(r, access_token, usr, conn=conn)
+21
View File
@@ -35,6 +35,7 @@ class StripeTerminalOptions(BaseModel):
capture_method: Literal["automatic", "manual"] = "automatic" capture_method: Literal["automatic", "manual"] = "automatic"
metadata: dict[str, str] = Field(default_factory=dict) metadata: dict[str, str] = Field(default_factory=dict)
reader_id: str | None = None
class StripeCheckoutOptions(BaseModel): class StripeCheckoutOptions(BaseModel):
@@ -295,6 +296,15 @@ class StripeWallet(FiatProvider):
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
async def _process_terminal_payment_intent(
self, reader_id: str, payment_intent_id: str
) -> None:
data = {"payment_intent": payment_intent_id}
r = await self.client.post(
f"/v1/terminal/readers/{reader_id}/process_payment_intent", data=data
)
r.raise_for_status()
async def _create_checkout_invoice( async def _create_checkout_invoice(
self, self,
amount_cents: int, amount_cents: int,
@@ -378,6 +388,17 @@ class StripeWallet(FiatProvider):
ok=False, ok=False,
error_message="Error: missing PaymentIntent or client_secret", error_message="Error: missing PaymentIntent or client_secret",
) )
if term.reader_id:
try:
await self._process_terminal_payment_intent(term.reader_id, pi_id)
except Exception as exc:
logger.warning(exc)
return FiatInvoiceResponse(
ok=False,
error_message=(
"Error: unable to process PaymentIntent on reader"
),
)
return FiatInvoiceResponse( return FiatInvoiceResponse(
ok=True, checking_id=pi_id, payment_request=client_secret ok=True, checking_id=pi_id, payment_request=client_secret
) )
+6 -1
View File
@@ -308,6 +308,12 @@ class AssetSettings(LNbitsSettings):
lnbits_max_assets_per_user: int = Field(default=1, ge=0) lnbits_max_assets_per_user: int = Field(default=1, ge=0)
lnbits_assets_no_limit_users: list[str] = Field(default=[]) lnbits_assets_no_limit_users: list[str] = Field(default=[])
def is_unlimited_assets_user(self, user_id: str) -> bool:
return (
settings.is_admin_user(user_id)
or user_id in self.lnbits_assets_no_limit_users
)
class FeeSettings(LNbitsSettings): class FeeSettings(LNbitsSettings):
lnbits_reserve_fee_min: int = Field(default=2000, ge=0) lnbits_reserve_fee_min: int = Field(default=2000, ge=0)
@@ -601,7 +607,6 @@ class BreezLiquidSdkFundingSource(LNbitsSettings):
class BoltzFundingSource(LNbitsSettings): class BoltzFundingSource(LNbitsSettings):
boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002") boltz_client_endpoint: str | None = Field(default="127.0.0.1:9002")
boltz_client_macaroon: str | None = Field(default=None) boltz_client_macaroon: str | None = Field(default=None)
boltz_client_wallet: str | None = Field(default="lnbits")
boltz_client_password: str = Field(default="") boltz_client_password: str = Field(default="")
boltz_client_cert: str | None = Field(default=None) boltz_client_cert: str | None = Field(default=None)
boltz_mnemonic: str | None = Field(default=None) boltz_mnemonic: str | None = Field(default=None)
File diff suppressed because one or more lines are too long
@@ -1,6 +1,30 @@
window.app.component('lnbits-error', { <template>
template: '#lnbits-error', <div class="text-center q-pa-md flex flex-center">
mixins: [window.windowMixin], <div>
<div class="error-code" v-text="code"></div>
<div class="error-message" v-text="message"></div>
<div class="q-mx-auto q-mt-lg justify-center" style="width: max-content">
<q-btn
v-if="isExtension"
color="primary"
@click="goToExtension()"
label="Go To Extension"
></q-btn>
<q-btn
v-else-if="g.isUserAuthorized"
color="primary"
@click="goToWallet()"
label="Go to Wallet"
></q-btn>
<q-btn v-else color="primary" @click="goBack()" label="Go Back"></q-btn>
<span class="q-mx-md">OR</span>
<q-btn color="secondary" @click="goHome()" label="Go Home"></q-btn>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['dynamic', 'code', 'message'], props: ['dynamic', 'code', 'message'],
computed: { computed: {
isExtension() { isExtension() {
@@ -50,4 +74,5 @@ window.app.component('lnbits-error', {
} }
} }
} }
}) }
</script>
+3
View File
@@ -0,0 +1,3 @@
import LnbitsError from './lnbits-error.vue'
window.LnbitsError = LnbitsError
@@ -89,8 +89,14 @@ window.app.component('lnbits-admin-funding-sources', {
lnd_rest_cert: 'Certificate', lnd_rest_cert: 'Certificate',
lnd_rest_macaroon: 'Macaroon', lnd_rest_macaroon: 'Macaroon',
lnd_rest_macaroon_encrypted: 'Encrypted Macaroon', lnd_rest_macaroon_encrypted: 'Encrypted Macaroon',
lnd_rest_route_hints: 'Enable Route Hints', lnd_rest_route_hints: {
lnd_rest_allow_self_payment: 'Allow Self Payment' advanced: true,
label: 'Enable Route Hints'
},
lnd_rest_allow_self_payment: {
advanced: true,
label: 'Allow Self Payment'
}
} }
], ],
[ [
@@ -160,16 +166,27 @@ window.app.component('lnbits-admin-funding-sources', {
'BoltzWallet', 'BoltzWallet',
'Boltz', 'Boltz',
{ {
boltz_client_endpoint: 'Endpoint', boltz_client_endpoint: {
boltz_client_macaroon: 'Admin Macaroon path or hex', label: 'Boltz client endpoint',
boltz_client_cert: 'Certificate path or hex', value: '127.0.0.1:9002'
boltz_client_wallet: 'Wallet Name', },
boltz_client_password: 'Wallet Password (can be empty)', boltz_client_macaroon: {
label: 'Admin Macaroon path or hex',
value: '/home/ubuntu/.boltz/macaroons/admin.macaroon'
},
boltz_client_cert: {
label: 'Certificate path or hex',
value: '/home/ubuntu/.boltz/tls.cert'
},
boltz_mnemonic: { boltz_mnemonic: {
label: 'Liquid mnemonic (copy into greenwallet)', label: 'Liquid seed phrase',
readonly: true, hint: 'Boltz will fetch once connected, but you can change later (can be opened in a liquid wallet) ',
copy: true, copy: true,
qrcode: true qrcode: true
},
boltz_client_password: {
label: 'Wallet Password (optional)',
advanced: true
} }
} }
], ],
+4
View File
@@ -168,4 +168,8 @@ window.app.use(Quasar, quasarConfig)
window.app.use(window.i18n) window.app.use(window.i18n)
window.app.use(window.router) window.app.use(window.router)
window.app.component(QrcodeVue)
window.app.component('lnbits-error', window.LnbitsError)
window.app.mount('#vue') window.app.mount('#vue')
+2 -1
View File
@@ -86,6 +86,7 @@ window.PageExtensions = {
version: release.version version: release.version
}) })
.then(response => { .then(response => {
this.selectedExtension.inProgress = false
const extension = this.extensions.find( const extension = this.extensions.find(
ext => ext.id === this.selectedExtension.id ext => ext.id === this.selectedExtension.id
) )
@@ -100,7 +101,7 @@ window.PageExtensions = {
}) })
.catch(err => { .catch(err => {
console.warn(err) console.warn(err)
extension.inProgress = false this.selectedExtension.inProgress = false
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
File diff suppressed because it is too large Load Diff
-1
View File
@@ -76,7 +76,6 @@
"js/components/lnbits-wallet-paylinks.js", "js/components/lnbits-wallet-paylinks.js",
"js/components/lnbits-wallet-extra.js", "js/components/lnbits-wallet-extra.js",
"js/components/lnbits-home-logos.js", "js/components/lnbits-home-logos.js",
"js/components/lnbits-error.js",
"js/components/lnbits-qrcode.js", "js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js", "js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-disclaimer.js", "js/components/lnbits-disclaimer.js",
+2
View File
@@ -94,6 +94,8 @@
{% endif %} {% endif %}
<!-- scripts from extensions --> <!-- scripts from extensions -->
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
<!-- modules js -->
<script src="/static/modules/components.umd.js"></script>
<!-- components js --> <!-- components js -->
{% for url in INCLUDED_COMPONENTS %} {% for url in INCLUDED_COMPONENTS %}
<script src="{{ static_url_for('static', url) }}"></script> <script src="{{ static_url_for('static', url) }}"></script>
+1 -2
View File
@@ -29,8 +29,7 @@ include('components/lnbits-wallet-api-docs.vue') %} {%
include('components/lnbits-wallet-share.vue') %} {% include('components/lnbits-wallet-share.vue') %} {%
include('components/lnbits-wallet-charts.vue') %} {% include('components/lnbits-wallet-charts.vue') %} {%
include('components/lnbits-wallet-paylinks.vue') %} {% include('components/lnbits-wallet-paylinks.vue') %} {%
include('components/lnbits-wallet-extra.vue') %} {% include('components/lnbits-wallet-extra.vue') %}
include('components/lnbits-error.vue') %}
<template id="lnbits-manage"> <template id="lnbits-manage">
<q-list v-if="g.user" dense class="lnbits-drawer__q-list"> <q-list v-if="g.user" dense class="lnbits-drawer__q-list">
@@ -35,7 +35,9 @@
> >
<div <div
class="row" class="row"
v-for="([key, prop], i) in Object.entries(fundingSources.get(fund))" v-for="([key, prop], i) in Object.entries(
fundingSources.get(fund)
).filter(([, p]) => !p.advanced)"
:key="i" :key="i"
> >
<div class="col-12"> <div class="col-12">
@@ -46,7 +48,7 @@
:type="hideInput ? 'password' : 'text'" :type="hideInput ? 'password' : 'text'"
:label="prop.label" :label="prop.label"
:hint="prop.hint" :hint="prop.hint"
:readonly="prop.readonly || false" :value="prop.value"
> >
<q-btn <q-btn
v-if="prop.copy" v-if="prop.copy"
@@ -69,6 +71,55 @@
</q-input> </q-input>
</div> </div>
</div> </div>
<q-expansion-item
v-if="
Object.entries(fundingSources.get(fund)).some(([, p]) => p.advanced)
"
dense
expand-separator
icon="tune"
label="Advanced"
class="q-mt-sm"
>
<div
class="row"
v-for="([key, prop], i) in Object.entries(
fundingSources.get(fund)
).filter(([, p]) => p.advanced)"
:key="`adv-${i}`"
>
<div class="col-12">
<q-input
v-model="formData[key]"
filled
class="q-mt-sm"
:type="hideInput ? 'password' : 'text'"
:label="prop.label"
:hint="prop.hint"
:readonly="prop.readonly || false"
>
<q-btn
v-if="prop.copy"
@click="utils.copyText(formData[key])"
icon="content_copy"
class="cursor-pointer"
color="grey"
flat
dense
></q-btn>
<q-btn
v-if="prop.qrcode"
@click="showQRValue(formData[key])"
icon="qr_code"
class="cursor-pointer"
color="grey"
flat
dense
></q-btn>
</q-input>
</div>
</div>
</q-expansion-item>
</div> </div>
</q-list> </q-list>
<q-dialog v-model="showQRDialog"> <q-dialog v-model="showQRDialog">
@@ -1,25 +0,0 @@
<template id="lnbits-error">
<div class="text-center q-pa-md flex flex-center">
<div>
<div class="error-code" v-text="code"></div>
<div class="error-message" v-text="message"></div>
<div class="q-mx-auto q-mt-lg justify-center" style="width: max-content">
<q-btn
v-if="isExtension"
color="primary"
@click="goToExtension()"
label="Go To Extension"
></q-btn>
<q-btn
v-else-if="g.isUserAuthorized"
color="primary"
@click="goToWallet()"
label="Go to Wallet"
></q-btn>
<q-btn v-else color="primary" @click="goBack()" label="Go Back"></q-btn>
<span class="q-mx-md">OR</span>
<q-btn color="secondary" @click="goHome()" label="Go Home"></q-btn>
</div>
</div>
</div>
</template>
+137 -59
View File
@@ -5,7 +5,7 @@ from bolt11.decode import decode
from grpc.aio import AioRpcError from grpc.aio import AioRpcError
from loguru import logger from loguru import logger
from lnbits.helpers import normalize_endpoint from lnbits.helpers import normalize_endpoint, sha256s
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.wallets.boltz_grpc_files import boltzrpc_pb2, boltzrpc_pb2_grpc from lnbits.wallets.boltz_grpc_files import boltzrpc_pb2, boltzrpc_pb2_grpc
from lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc import grpc from lnbits.wallets.lnd_grpc_files.lightning_pb2_grpc import grpc
@@ -38,21 +38,18 @@ class BoltzWallet(Wallet):
raise ValueError( raise ValueError(
"cannot initialize BoltzWallet: missing boltz_client_endpoint" "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( self.endpoint = normalize_endpoint(
settings.boltz_client_endpoint, add_proto=True settings.boltz_client_endpoint, add_proto=True
) )
self.metadata = None
if settings.boltz_client_macaroon: if settings.boltz_client_macaroon:
self.metadata = [ try:
("macaroon", load_macaroon(settings.boltz_client_macaroon)) macaroon = load_macaroon(settings.boltz_client_macaroon)
] self.metadata = [("macaroon", macaroon)]
else: except Exception as e:
self.metadata = None logger.error(f"BoltzWallet failed to load macaroon: {e}")
if settings.boltz_client_cert: if settings.boltz_client_cert:
cert = open(settings.boltz_client_cert, "rb").read() cert = open(settings.boltz_client_cert, "rb").read()
@@ -63,31 +60,21 @@ class BoltzWallet(Wallet):
self.rpc = boltzrpc_pb2_grpc.BoltzStub(channel) self.rpc = boltzrpc_pb2_grpc.BoltzStub(channel)
self.wallet_id = 0 self.wallet_id = 0
self.wallet_name = "lnbits"
# Auto-create wallet if running in Docker mode self.wallet_ready = False
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: async def status(self) -> StatusResponse:
if self.wallet_ready is False:
self.wallet_ready = True
if settings.boltz_mnemonic: # restore wallet from mnemonic
await self._restore_boltz_wallet(
settings.boltz_mnemonic,
settings.boltz_client_password,
)
else: # create new wallet
await self._create_boltz_wallet()
try: try:
request = boltzrpc_pb2.GetWalletRequest(name=settings.boltz_client_wallet) request = boltzrpc_pb2.GetWalletRequest(name=self.wallet_name)
response: boltzrpc_pb2.Wallet = await self.rpc.GetWallet( response: boltzrpc_pb2.Wallet = await self.rpc.GetWallet(
request, metadata=self.metadata request, metadata=self.metadata
) )
@@ -126,12 +113,18 @@ class BoltzWallet(Wallet):
try: try:
response = await self.rpc.CreateReverseSwap(request, metadata=self.metadata) response = await self.rpc.CreateReverseSwap(request, metadata=self.metadata)
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return InvoiceResponse(ok=False, error_message=exc.details()) return InvoiceResponse(ok=False, error_message=exc.details())
fee_msat = response.routing_fee_milli_sat
return InvoiceResponse( return InvoiceResponse(
ok=True, checking_id=response.id, payment_request=response.invoice ok=True,
checking_id=response.id,
payment_request=response.invoice,
fee_msat=fee_msat,
) )
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
pair = boltzrpc_pb2.Pair(**{"from": boltzrpc_pb2.LBTC}) pair = boltzrpc_pb2.Pair(**{"from": boltzrpc_pb2.LBTC})
try: try:
pair_info: boltzrpc_pb2.PairInfo pair_info: boltzrpc_pb2.PairInfo
@@ -143,8 +136,9 @@ class BoltzWallet(Wallet):
if not invoice.amount_msat: if not invoice.amount_msat:
raise ValueError("amountless invoice") raise ValueError("amountless invoice")
service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100 service_fee: float = invoice.amount_msat * pair_info.fees.percentage / 100
estimate = service_fee + pair_info.fees.miner_fees * 1000 estimate = int(service_fee + pair_info.fees.miner_fees * 1000)
if estimate > fee_limit_msat: if estimate > fee_limit_msat:
error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat" error = f"fee of {estimate} msat exceeds limit of {fee_limit_msat} msat"
@@ -166,11 +160,12 @@ class BoltzWallet(Wallet):
if response.id == "": if response.id == "":
# note that there is no way to provide a checking id here, # note that there is no way to provide a checking id here,
# but there is no need since it immediately is considered as successfull # but there is no need since it immediately is considered as successfull
return PaymentResponse( logger.warning(
ok=True, "Boltz invoice paid directly on liquid network using magic routing"
checking_id=response.id,
) )
return PaymentResponse(ok=True, checking_id=invoice.payment_hash)
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return PaymentResponse(ok=False, error_message=exc.details()) return PaymentResponse(ok=False, error_message=exc.details())
try: try:
@@ -180,10 +175,15 @@ class BoltzWallet(Wallet):
info_request, metadata=self.metadata info_request, metadata=self.metadata
): ):
if info.swap.state == boltzrpc_pb2.SUCCESSFUL: if info.swap.state == boltzrpc_pb2.SUCCESSFUL:
fee_msat = (info.swap.onchain_fee + info.swap.service_fee) * 1000
logger.debug(
f"Boltz swap successful, status: {info.swap.status}"
f"fee_msat: {fee_msat}"
)
return PaymentResponse( return PaymentResponse(
ok=True, ok=True,
checking_id=response.id, checking_id=invoice.payment_hash,
fee_msat=(info.swap.onchain_fee + info.swap.service_fee) * 1000, fee_msat=fee_msat,
preimage=info.swap.preimage, preimage=info.swap.preimage,
) )
elif info.swap.error != "": elif info.swap.error != "":
@@ -192,21 +192,29 @@ class BoltzWallet(Wallet):
ok=False, error_message="stream stopped unexpectedly" ok=False, error_message="stream stopped unexpectedly"
) )
except AioRpcError as exc: except AioRpcError as exc:
logger.warning(exc)
return PaymentResponse(ok=False, error_message=exc.details()) return PaymentResponse(ok=False, error_message=exc.details())
async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try: try:
request = boltzrpc_pb2.GetSwapInfoRequest(id=checking_id)
response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo( response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo(
boltzrpc_pb2.GetSwapInfoRequest(id=checking_id), metadata=self.metadata request,
metadata=self.metadata,
) )
swap = response.reverse_swap swap = response.reverse_swap
except AioRpcError: except AioRpcError as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL: if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL:
fee_msat = (
swap.service_fee + swap.onchain_fee
) * 1000 + swap.routing_fee_msat
logger.debug(
f"Boltz swap successful, status: {swap.status}, fee_msat: {fee_msat}"
)
return PaymentSuccessStatus( return PaymentSuccessStatus(
fee_msat=( fee_msat=fee_msat,
(swap.service_fee + swap.onchain_fee) * 1000 + swap.routing_fee_msat
),
preimage=swap.preimage, preimage=swap.preimage,
) )
elif swap.state == boltzrpc_pb2.SwapState.PENDING: elif swap.state == boltzrpc_pb2.SwapState.PENDING:
@@ -216,18 +224,23 @@ class BoltzWallet(Wallet):
async def get_payment_status(self, checking_id: str) -> PaymentStatus: async def get_payment_status(self, checking_id: str) -> PaymentStatus:
try: try:
checking_id_bytes = bytes.fromhex(checking_id)
request = boltzrpc_pb2.GetSwapInfoRequest(payment_hash=checking_id_bytes)
response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo( response: boltzrpc_pb2.GetSwapInfoResponse = await self.rpc.GetSwapInfo(
boltzrpc_pb2.GetSwapInfoRequest( request,
payment_hash=bytes.fromhex(checking_id)
),
metadata=self.metadata, metadata=self.metadata,
) )
swap = response.swap swap = response.swap
except AioRpcError: except AioRpcError as exc:
logger.warning(exc)
return PaymentPendingStatus() return PaymentPendingStatus()
if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL: if swap.state == boltzrpc_pb2.SwapState.SUCCESSFUL:
fee_msat = (swap.service_fee + swap.onchain_fee) * 1000
logger.debug(
f"Boltz swap successful, status: {swap.status}, fee_msat: {fee_msat}"
)
return PaymentSuccessStatus( return PaymentSuccessStatus(
fee_msat=(swap.service_fee + swap.onchain_fee) * 1000, fee_msat=fee_msat,
preimage=swap.preimage, preimage=swap.preimage,
) )
elif swap.state == boltzrpc_pb2.SwapState.PENDING: elif swap.state == boltzrpc_pb2.SwapState.PENDING:
@@ -244,7 +257,16 @@ class BoltzWallet(Wallet):
request, metadata=self.metadata request, metadata=self.metadata
): ):
reverse = info.reverse_swap reverse = info.reverse_swap
if reverse and reverse.state == boltzrpc_pb2.SUCCESSFUL: if (
reverse
and reverse.state == boltzrpc_pb2.SUCCESSFUL
and reverse.status == "invoice.settled"
):
fee_msat = ((reverse.service_fee + reverse.onchain_fee) * 1000,)
logger.debug(
f"Boltz reverse swap settled: {reverse.id}, "
f"fee_msat: {fee_msat}"
)
yield reverse.id yield reverse.id
except Exception as exc: except Exception as exc:
logger.error( logger.error(
@@ -253,24 +275,80 @@ class BoltzWallet(Wallet):
) )
await asyncio.sleep(5) await asyncio.sleep(5)
async def _fetch_wallet(self, wallet_name: str) -> str | None: async def _check_wallet_exists(
self, wallet_name: str
) -> boltzrpc_pb2.Wallet | None:
try: try:
request = boltzrpc_pb2.GetWalletRequest(name=wallet_name) request = boltzrpc_pb2.GetWalletRequest(name=wallet_name)
response = await self.rpc.GetWallet(request, metadata=self.metadata) 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:
except AioRpcError as exc: return None
details = exc.details() or "unknown error"
if exc.code() != grpc.StatusCode.NOT_FOUND:
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}'") logger.info(f"Creating new wallet '{wallet_name}'")
params = boltzrpc_pb2.WalletParams( params = boltzrpc_pb2.WalletParams(
name=wallet_name, name=wallet_name,
currency=boltzrpc_pb2.LBTC, currency=boltzrpc_pb2.LBTC,
password=settings.boltz_client_password, password=password,
) )
create_request = boltzrpc_pb2.CreateWalletRequest(params=params) create_request = boltzrpc_pb2.CreateWalletRequest(params=params)
response = await self.rpc.CreateWallet(create_request, metadata=self.metadata) response = await self.rpc.CreateWallet(create_request, metadata=self.metadata)
return response.mnemonic 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}")
+2899 -1
View File
File diff suppressed because it is too large Load Diff
+13 -3
View File
@@ -1,5 +1,6 @@
{ {
"name": "lnbits", "name": "lnbits",
"type": "module",
"scripts": { "scripts": {
"sass": "./node_modules/.bin/sass ./lnbits/static/scss/base.scss > ./lnbits/static/css/base.css", "sass": "./node_modules/.bin/sass ./lnbits/static/scss/base.scss > ./lnbits/static/css/base.css",
"vendor_copy": "node -e \"require('./package.json').vendor.forEach((file) => require('fs').copyFileSync(file, './lnbits/static/vendor/'+file.split('/').pop()))\"", "vendor_copy": "node -e \"require('./package.json').vendor.forEach((file) => require('fs').copyFileSync(file, './lnbits/static/vendor/'+file.split('/').pop()))\"",
@@ -10,15 +11,25 @@
"vendor_minify_css": "./node_modules/.bin/cleancss -o ./lnbits/static/bundle.min.css ./lnbits/static/bundle.css", "vendor_minify_css": "./node_modules/.bin/cleancss -o ./lnbits/static/bundle.min.css ./lnbits/static/bundle.css",
"vendor_minify_js": "./node_modules/.bin/terser ./lnbits/static/bundle.js -o ./lnbits/static/bundle.min.js --compress --mangle", "vendor_minify_js": "./node_modules/.bin/terser ./lnbits/static/bundle.js -o ./lnbits/static/bundle.min.js --compress --mangle",
"vendor_minify_components": "./node_modules/.bin/terser ./lnbits/static/bundle-components.js -o ./lnbits/static/bundle-components.min.js --compress --mangle", "vendor_minify_components": "./node_modules/.bin/terser ./lnbits/static/bundle-components.js -o ./lnbits/static/bundle-components.min.js --compress --mangle",
"bundle": "npm run sass && npm run vendor_copy && npm run vendor_json && npm run vendor_bundle_css && npm run vendor_bundle_js && npm run vendor_bundle_components && npm run vendor_minify_css && npm run vendor_minify_js && npm run vendor_minify_components" "bundle": "npm run sass && npm run vendor_copy && npm run vendor_json && npm run vendor_bundle_css && npm run vendor_bundle_js && npm run vendor_bundle_components && npm run vendor_minify_css && npm run vendor_minify_js && npm run vendor_minify_components",
"test": "npm run test:unit",
"test:unit": "vitest --watch=false tests/frontend/unit",
"test:unit:watch": "vitest tests/frontend/unit",
"build": "vite build",
"watch": "vite build --watch"
}, },
"devDependencies": { "devDependencies": {
"@quasar/quasar-app-extension-testing-unit-vitest": "^1.2.4",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/test-utils": "^2.4.6",
"clean-css-cli": "^5.6.3", "clean-css-cli": "^5.6.3",
"concat": "^1.0.3", "concat": "^1.0.3",
"jsdom": "^27.3.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"pyright": "1.1.289", "pyright": "1.1.289",
"sass": "^1.94.2", "sass": "^1.94.2",
"terser": "^5.44.1" "terser": "^5.44.1",
"vitest": "^3"
}, },
"dependencies": { "dependencies": {
"axios": "^1.13.2", "axios": "^1.13.2",
@@ -129,7 +140,6 @@
"js/components/lnbits-wallet-paylinks.js", "js/components/lnbits-wallet-paylinks.js",
"js/components/lnbits-wallet-extra.js", "js/components/lnbits-wallet-extra.js",
"js/components/lnbits-home-logos.js", "js/components/lnbits-home-logos.js",
"js/components/lnbits-error.js",
"js/components/lnbits-qrcode.js", "js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js", "js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-disclaimer.js", "js/components/lnbits-disclaimer.js",
Generated
+20 -13
View File
@@ -1314,14 +1314,14 @@ standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[stand
[[package]] [[package]]
name = "fastapi-sso" name = "fastapi-sso"
version = "0.18.0" version = "0.19.0"
description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)" description = "FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)"
optional = false optional = false
python-versions = "<4.0,>=3.9" python-versions = "<4.0,>=3.9"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "fastapi_sso-0.18.0-py3-none-any.whl", hash = "sha256:727754ad770b70690f1471f7b0a9e17c6dfd8ebd6e477616d3bde1eaf62e53dc"}, {file = "fastapi_sso-0.19.0-py3-none-any.whl", hash = "sha256:d958c46cd9996234c7b162e192168b4c0807a248224a55b0f877d3a82a16a930"},
{file = "fastapi_sso-0.18.0.tar.gz", hash = "sha256:d8df5a686af7a6a7be248817544b405cf77f7e9ffcd5d0d7d2a196fd071964bc"}, {file = "fastapi_sso-0.19.0.tar.gz", hash = "sha256:629f00581f72ea7e57f7b8775f8d2c425629c428c194359a2b4ebaa6bcb8e12b"},
] ]
[package.dependencies] [package.dependencies]
@@ -1333,21 +1333,16 @@ pyjwt = ">=2.10.1,<3.0.0"
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.18.0" version = "3.20.1"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.10"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a"},
{file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, {file = "filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c"},
] ]
[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]] [[package]]
name = "filetype" name = "filetype"
version = "1.2.0" version = "1.2.0"
@@ -1491,6 +1486,8 @@ files = [
{file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7"},
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8"},
{file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"},
{file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"},
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"},
@@ -1500,6 +1497,8 @@ files = [
{file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c"},
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5"},
{file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"},
{file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"},
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"},
@@ -1509,6 +1508,8 @@ files = [
{file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0"},
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d"},
{file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"},
{file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"},
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"},
@@ -1518,6 +1519,8 @@ files = [
{file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b"},
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929"},
{file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"},
{file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"},
@@ -1525,6 +1528,8 @@ files = [
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"},
{file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"},
{file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269"},
{file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681"},
{file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"},
{file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"},
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"},
@@ -1534,6 +1539,8 @@ files = [
{file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10"},
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be"},
{file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"},
{file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"},
{file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"},
@@ -4564,4 +4571,4 @@ migration = ["psycopg2-binary"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
content-hash = "13f4b6c9c33e051dba10dfa6833c11bb3aabdd4cce395fa8480d1fa4ee9ddb96" content-hash = "141fcae1bdf2b6356097afa0e2ccf970ad90e09e26a8361accd7e013e865b74c"
+2 -2
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "lnbits" name = "lnbits"
version = "1.4.0-rc2" version = "1.4.0"
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" }]
@@ -38,7 +38,7 @@ dependencies = [
"bolt11==2.1.1", "bolt11==2.1.1",
"pyjwt==2.10.1", "pyjwt==2.10.1",
"itsdangerous==2.2.0", "itsdangerous==2.2.0",
"fastapi-sso==0.18.0", "fastapi-sso==0.19.0",
# needed for boltz, lnurldevice, watchonly extensions # needed for boltz, lnurldevice, watchonly extensions
"embit==0.8.0", "embit==0.8.0",
# needed for scheduler extension # needed for scheduler extension
+2 -2
View File
@@ -331,8 +331,8 @@ def _settings_cleanup(settings: Settings):
settings.lnbits_allowed_users = [] settings.lnbits_allowed_users = []
settings.auth_allowed_methods = AuthMethods.all() settings.auth_allowed_methods = AuthMethods.all()
settings.auth_credetials_update_threshold = 120 settings.auth_credetials_update_threshold = 120
settings.lnbits_reserve_fee_percent = 1 settings.lnbits_reserve_fee_percent = 2
settings.lnbits_reserve_fee_min = 2000 settings.lnbits_reserve_fee_min = 20000
settings.lnbits_service_fee = 0 settings.lnbits_service_fee = 0
settings.lnbits_reserve_fee_percent = 0 settings.lnbits_reserve_fee_percent = 0
settings.lnbits_wallet_limit_daily_max_withdraw = 0 settings.lnbits_wallet_limit_daily_max_withdraw = 0
@@ -0,0 +1,38 @@
import {mount} from '@vue/test-utils'
import {expect, test} from 'vitest'
import LnbitsError from '../../../lnbits/static/components/lnbits-error.vue'
// installQuasarPlugin()
export const quasarMock = {
global: {
mocks: {
g: {
isUserAuthorized: true
},
$q: {
platform: {},
screen: {},
dark: false
}
},
stubs: {
QCard: true,
QCardSection: true,
QIcon: true,
QBtn: true
}
}
}
test('displays message and code', () => {
expect(LnbitsError).toBeTruthy()
const wrapper = mount(LnbitsError, {
props: {
message: 'Page not found!!!',
code: 404
},
...quasarMock
})
expect(wrapper.text()).toContain('Page not found!!!')
expect(wrapper.text()).toContain('404')
})
+22 -1
View File
@@ -6,6 +6,11 @@ from subprocess import PIPE, Popen, TimeoutExpired
from loguru import logger from loguru import logger
from lnbits.wallets import get_funding_source
funding_source = get_funding_source()
is_boltz_wallet = funding_source.__class__.__name__ == "BoltzWallet"
docker_lightning_cli = [ docker_lightning_cli = [
"docker", "docker",
"exec", "exec",
@@ -27,6 +32,16 @@ docker_bitcoin_cli = [
] ]
docker_elements_cli = [
"docker",
"exec",
"lnbits-elementsd-1",
"elements-cli",
"-rpcport=18884",
"-chain=liquidregtest",
]
docker_lightning_unconnected_cli = [ docker_lightning_unconnected_cli = [
"docker", "docker",
"exec", "exec",
@@ -50,7 +65,7 @@ docker_lightning_noroute_cli = [
def run_cmd(cmd: list) -> str: def run_cmd(cmd: list) -> str:
timeout = 10 timeout = 30
process = Popen(cmd, stdout=PIPE, stderr=PIPE) process = Popen(cmd, stdout=PIPE, stderr=PIPE)
logger.debug(f"running command: {cmd}") logger.debug(f"running command: {cmd}")
@@ -128,6 +143,12 @@ def mine_blocks(blocks: int = 1) -> str:
return run_cmd(cmd) return run_cmd(cmd)
def mine_blocks_liquid(blocks: int = 1) -> str:
cmd = docker_elements_cli.copy()
cmd.extend(["-generate", str(blocks)])
return run_cmd(cmd)
def get_unconnected_node_uri() -> str: def get_unconnected_node_uri() -> str:
cmd = docker_lightning_unconnected_cli.copy() cmd = docker_lightning_unconnected_cli.copy()
cmd.append("getinfo") cmd.append("getinfo")
+12 -4
View File
@@ -18,6 +18,7 @@ from ..helpers import FakeError, is_fake, is_regtest
from .helpers import ( from .helpers import (
cancel_invoice, cancel_invoice,
get_real_invoice, get_real_invoice,
mine_blocks_liquid,
pay_real_invoice, pay_real_invoice,
settle_invoice, settle_invoice,
) )
@@ -66,7 +67,7 @@ async def test_pay_real_invoice(
await asyncio.sleep(1) await asyncio.sleep(1)
balance = await get_node_balance_sats() balance = await get_node_balance_sats()
assert prev_balance - balance == 100 assert prev_balance - balance == 100 + abs(payment.fee // 1000)
@pytest.mark.anyio @pytest.mark.anyio
@@ -153,7 +154,8 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
async def on_paid(payment: Payment): async def on_paid(payment: Payment):
assert payment.checking_id == invoice["payment_hash"] assert payment.payment_hash == invoice["payment_hash"]
assert payment.checking_id == invoice["checking_id"]
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
@@ -164,7 +166,8 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
await asyncio.sleep(1) await asyncio.sleep(1)
balance = await get_node_balance_sats() balance = await get_node_balance_sats()
assert balance - prev_balance == create_invoice.amount fee = abs(payment_status.get("details", {}).get("fee", 0) // 1000)
assert balance - prev_balance == create_invoice.amount - fee
assert payment_status.get("preimage") is not None assert payment_status.get("preimage") is not None
@@ -200,6 +203,8 @@ async def test_pay_real_invoice_set_pending_and_check_state(
assert len(invoice["payment_hash"]) == 64 assert len(invoice["payment_hash"]) == 64
assert len(invoice["checking_id"]) > 0 assert len(invoice["checking_id"]) > 0
mine_blocks_liquid(1)
await asyncio.sleep(1)
# check the payment status # check the payment status
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
@@ -341,6 +346,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
assert preimage_hash == invoice_obj.payment_hash assert preimage_hash == invoice_obj.payment_hash
cancel_invoice(preimage_hash) cancel_invoice(preimage_hash)
mine_blocks_liquid(1)
# check if paid # check if paid
await asyncio.sleep(1) await asyncio.sleep(1)
@@ -382,7 +388,9 @@ async def test_receive_real_invoice_set_pending_and_check_state(
assert not payment_status["paid"] assert not payment_status["paid"]
async def on_paid(payment: Payment): async def on_paid(payment: Payment):
assert payment.checking_id == invoice["payment_hash"]
assert payment.payment_hash == invoice["payment_hash"]
assert payment.checking_id == invoice["checking_id"]
response = await client.get( response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
+10 -2
View File
@@ -7,6 +7,8 @@ from lnbits.core.services import (
from lnbits.wallets import get_funding_source from lnbits.wallets import get_funding_source
from lnbits.wallets.base import PaymentStatus from lnbits.wallets.base import PaymentStatus
from .helpers import is_boltz_wallet
description = "test create invoice" description = "test create invoice"
@@ -17,7 +19,10 @@ async def test_create_invoice(from_wallet):
amount=1000, amount=1000,
memo=description, memo=description,
) )
assert payment.preimage
# we cannot know the preimage of the swap yet
if not is_boltz_wallet:
assert payment.preimage
invoice = decode(payment.bolt11) invoice = decode(payment.bolt11)
assert invoice.payment_hash == payment.payment_hash assert invoice.payment_hash == payment.payment_hash
@@ -35,7 +40,10 @@ async def test_create_internal_invoice(from_wallet):
payment = await create_invoice( payment = await create_invoice(
wallet_id=from_wallet.id, amount=1000, memo=description, internal=True wallet_id=from_wallet.id, amount=1000, memo=description, internal=True
) )
assert payment.preimage
# we cannot know the preimage of the swap yet
if not is_boltz_wallet:
assert payment.preimage
invoice = decode(payment.bolt11) invoice = decode(payment.bolt11)
assert invoice.payment_hash == payment.payment_hash assert invoice.payment_hash == payment.payment_hash
+7 -2
View File
@@ -6,6 +6,8 @@ from lnbits.core.services import (
) )
from lnbits.exceptions import PaymentError from lnbits.exceptions import PaymentError
from .helpers import is_boltz_wallet
description = "test pay invoice" description = "test pay invoice"
@@ -17,9 +19,12 @@ async def test_services_pay_invoice(to_wallet, real_invoice):
description=description, description=description,
) )
assert payment assert payment
assert payment.status == PaymentState.SUCCESS
assert payment.memo == description assert payment.memo == description
assert payment.preimage if not is_boltz_wallet:
assert payment.status == PaymentState.SUCCESS
assert payment.preimage
else:
assert payment.status == PaymentState.PENDING
@pytest.mark.anyio @pytest.mark.anyio
Generated
+5 -5
View File
@@ -784,7 +784,7 @@ wheels = [
[[package]] [[package]]
name = "fastapi-sso" name = "fastapi-sso"
version = "0.18.0" version = "0.19.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "fastapi" }, { name = "fastapi" },
@@ -793,9 +793,9 @@ dependencies = [
{ name = "pydantic", extra = ["email"] }, { name = "pydantic", extra = ["email"] },
{ name = "pyjwt" }, { name = "pyjwt" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d2/57/cc971c018af5d09eb5f8d1cd12abdd99ab4c59ea5c0b0b1b96349ffe117d/fastapi_sso-0.18.0.tar.gz", hash = "sha256:d8df5a686af7a6a7be248817544b405cf77f7e9ffcd5d0d7d2a196fd071964bc", size = 16811, upload-time = "2025-03-20T17:09:09.958Z" } sdist = { url = "https://files.pythonhosted.org/packages/74/fc/644bc8f82fc887fffcf9a3eab8eb3dea06ee9ea160ef20441455ed7a0001/fastapi_sso-0.19.0.tar.gz", hash = "sha256:629f00581f72ea7e57f7b8775f8d2c425629c428c194359a2b4ebaa6bcb8e12b", size = 17278, upload-time = "2025-12-17T15:18:06.721Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/03/70ca13994f5569d343a9f99dba2930c8ae3471171f161b8887d44b6c526f/fastapi_sso-0.18.0-py3-none-any.whl", hash = "sha256:727754ad770b70690f1471f7b0a9e17c6dfd8ebd6e477616d3bde1eaf62e53dc", size = 26103, upload-time = "2025-03-20T17:09:08.656Z" }, { url = "https://files.pythonhosted.org/packages/d7/61/d2ac0800cb1f390ec2aad464ea4cc5944de285fabc8f987327da4633f17d/fastapi_sso-0.19.0-py3-none-any.whl", hash = "sha256:d958c46cd9996234c7b162e192168b4c0807a248224a55b0f877d3a82a16a930", size = 26419, upload-time = "2025-12-17T15:18:05.439Z" },
] ]
[[package]] [[package]]
@@ -1247,7 +1247,7 @@ wheels = [
[[package]] [[package]]
name = "lnbits" name = "lnbits"
version = "1.4.0rc2" version = "1.4.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiosqlite" }, { name = "aiosqlite" },
@@ -1342,7 +1342,7 @@ requires-dist = [
{ name = "ecdsa", specifier = "==0.19.1" }, { name = "ecdsa", specifier = "==0.19.1" },
{ name = "embit", specifier = "==0.8.0" }, { name = "embit", specifier = "==0.8.0" },
{ name = "fastapi", specifier = "==0.116.1" }, { name = "fastapi", specifier = "==0.116.1" },
{ name = "fastapi-sso", specifier = "==0.18.0" }, { name = "fastapi-sso", specifier = "==0.19.0" },
{ name = "filetype", specifier = "==1.2.0" }, { name = "filetype", specifier = "==1.2.0" },
{ name = "grpcio", specifier = "==1.69.0" }, { name = "grpcio", specifier = "==1.69.0" },
{ name = "httpx", specifier = "==0.27.0" }, { name = "httpx", specifier = "==0.27.0" },
+26
View File
@@ -0,0 +1,26 @@
import {defineConfig} from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom'
},
define: {
'process.env': {} // Replace process.env with an empty object for browser builds
},
build: {
lib: {
entry: 'lnbits/static/components/main.js',
name: 'LnbitsComponents',
fileName: 'components', // Output file name (build.js)
formats: ['umd']
},
minify: 'terser', // Optional: minify the output
rollupOptions: {
// If you have external dependencies, mark them here
external: []
},
outDir: 'lnbits/static/modules' // Output folder
}
})