Compare commits

..
Author SHA1 Message Date
dni ⚡ c820456500 feat: add openapi-generators for sdk's 2025-04-10 12:29:41 +02:00
dni ⚡andGitHub 5714848434 feat: install lnbits.sh bash script (#2684) (#3101) 2025-04-10 12:28:08 +02:00
dni ⚡andGitHub f87e5d4372 chore: update LNbits to version v1.0.0 (#3100) 2025-04-10 12:09:13 +02:00
Tiago VasconcelosandGitHub a0502f1676 [Fix] - Bug on switching wallets and payment details (#3094) 2025-04-10 10:02:35 +01:00
Vlad StanandGitHub f6c8a308dc feat: create new user from the command line (#3098) 2025-04-09 14:01:34 +02:00
dni ⚡andGitHub 6df3933c1b ci: trigger release on rc's > 9 (#3088) 2025-04-03 13:12:19 +02:00
dni ⚡andGitHub ffef085fa7 chore: update to v1.0.0-rc10 (#3087) 2025-04-03 12:55:30 +02:00
tlindiandGitHub 913991c72f Update fi.js-1.0.0-rc10-tobe (#3086) 2025-04-03 10:31:49 +03:00
dni ⚡andGitHub 6f611461f2 fix: FakeWallet, bolt11 payment_secret is not the preimage (#3084) 2025-04-02 18:46:24 +03:00
Tiago VasconcelosandGitHub 681385e2a2 fix dialog consistency (#3082) 2025-04-02 16:03:40 +02:00
dni ⚡andGitHub 883d52c303 fix: phoenixd uses sat instead of msat for pay_invoice (#3083) 2025-04-02 16:49:59 +03:00
Vlad StanandGitHub 1058461882 fix: include node balance in server status (#3079) 2025-04-02 15:31:47 +03:00
Vlad StanandGitHub 3a7a88857a fix: missing label (#3081) 2025-04-02 14:51:15 +03:00
Vlad StanandGitHub 6834b5e00f feat: do not allow regular admins to edit super user (#3077) 2025-04-01 14:01:02 +03:00
Vlad StanandGitHub 10093bb465 feat: show wallet payments on search (#3076) 2025-04-01 12:41:10 +02:00
dni ⚡andGitHub 1323a2005b feat: cleanup on library dir creation and upload endpoints (#3069) 2025-04-01 10:27:11 +03:00
Vlad StanandGitHub bafb4ddf75 fix: allow admin to view payments in deleted wallets (#3074) 2025-03-31 17:18:32 +03:00
Vlad StanandGitHub aa050eaf49 fix: make parallel calls for exchages (#3073) 2025-03-31 14:44:36 +03:00
ArcandGitHub 0c76efa2b9 Add ws link to curl examples (#3063) 2025-03-31 12:58:26 +03:00
PatMulliganandGitHub 681730a4af [CHORE] add responsive text scaling with clamp() for wallet balance display (#3071) 2025-03-31 11:59:58 +03:00
ArcandGitHub 63adcb6780 Adds image library for admins (#3066) 2025-03-27 11:16:20 +02:00
dni ⚡andGitHub 35f7821183 fix: make swagger understand the access token HTTPBearer (#3068) 2025-03-27 09:14:42 +00:00
Vlad StanandGitHub b185d9585c fix: add default extensions when a user is created (#3061) 2025-03-22 23:54:15 +00:00
ArcandGitHub 30a8d88ada Remove LNbits UI elements (#3064) 2025-03-22 21:08:26 +00:00
ArcandGitHub f7e984198b Revert "tweak: fix for ws payment" (#3060) 2025-03-21 14:03:52 +02:00
ArcandGitHub eb09e95e88 tweak: fix for ws payment (#3057) 2025-03-20 23:51:10 +00:00
Tiago VasconcelosandGitHub a883e2c7d8 hide 'Runs On' scroll if title is not LNbits (#3053) 2025-03-18 10:19:10 +01:00
Tiago VasconcelosandGitHub e06abb52ce [Fix] default to sats if wallet not fakewallet (#3055) 2025-03-18 10:17:47 +01:00
tlindiandGitHub 9f64b5349d Dev update fi.js again (#3052) 2025-03-18 11:07:37 +02:00
ArcandGitHub a6bbddce41 tweak: use image that works for fav and qr as default (#3051) 2025-03-14 10:48:58 +01:00
Tiago VasconcelosandGitHub adb9d24673 [Fix] Set default chart text color (#3048)
* fix: set default chart text
white when darkMode else black
2025-03-12 17:36:11 +01:00
Tiago VasconcelosandGitHub dc85f26964 fix: qr scanner button was missing the method (#3046) 2025-03-12 16:09:53 +01:00
Tiago VasconcelosandGitHub 14153b4a8f [Fix] Remove references and warnings for BETA (#3047)
* fix: remove BETA references/warnings
2025-03-12 16:09:18 +01:00
Vlad StanandGitHub b8a5f3942c [fix] limit the size of the invoice description to 640 characters (#3045) 2025-03-12 16:29:29 +02:00
dni ⚡andGitHub 475ae5736e chore: update to v1.0.0-rc9 (#3043) 2025-03-12 12:29:01 +01:00
PatMulliganandGitHub d7f0f4da0c create backwards compatibility for <=v1.0 (#3041)
in v0.12.12, the key is "payment_request" instead of the new "bolt11"
2025-03-12 12:27:24 +01:00
Tiago VasconcelosandGitHub f7b3444be4 fix: unfuck the swap mess (#3023) 2025-03-12 12:14:20 +01:00
ArcandGitHub 28e32a54a4 tweak: use qr code image for favicon as well (#3031)
use qr code image for favicon as well
2025-03-12 12:12:43 +01:00
ArcandGitHub 1833adc0a1 Tweak: moved custom denomination to fakewallet funding source (#3030) 2025-03-12 12:10:58 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dni ⚡
b759ec7468 chore(deps): bump axios from 1.7.7 to 1.8.2 (#3042)
* chore(deps): bump axios from 1.7.7 to 1.8.2

Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.7...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* bundle

---------

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-03-12 12:10:27 +01:00
2661d12ff1 tweak: fixes service fee badge (#3029)
* tweak: fixes service fee badge

---------

Co-authored-by: dni  <office@dnilabs.com>
2025-03-12 12:08:08 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dni ⚡
ed419f27ac chore(deps): bump vue-i18n from 10.0.5 to 10.0.6 (#3022)
* chore(deps): bump vue-i18n from 10.0.5 to 10.0.6

Bumps [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n) from 10.0.5 to 10.0.6.
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v10.0.6/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: vue-i18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* bundle

---------

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-03-12 12:01:18 +01:00
dni ⚡andGitHub c3efd48108 chore: use latest 1.X poetry version (#3039) 2025-03-11 09:54:36 +01:00
ArcandGitHub 4a28c22d79 tweak: fix 'test email' btn (#3026) 2025-03-10 18:37:11 +00:00
ArcandGitHub 0d0eb36de4 tweak: fix funding source hide (#3027)
added to title rather than all the inputs
2025-03-10 18:36:48 +00:00
ArcandGitHub 2aa73bfe75 tweak: punchy bitcoin theme (#3028) 2025-03-10 18:36:23 +00:00
Vlad StanandGitHub ebd080192c feat: return payment status in the websocket response (#3025) 2025-03-10 12:48:40 +02:00
Tiago VasconcelosandGitHub 41abe63f18 hide text on small screen (#3021) 2025-03-07 14:23:46 +00:00
dni ⚡andGitHub f61471b0f2 feat: add test email and improve on email notifications (#3018) 2025-03-05 09:53:40 +02:00
Vlad StanandGitHub 9bd037b6e7 [fix] settings cleanup (#3013) 2025-03-04 16:30:54 +02:00
a4ca88b6a4 tweak: dont show full id on wallet select box (#3017)
* tweak: dont show full id on wallet select box
* chore bundle

---------

Co-authored-by: dni  <office@dnilabs.com>
2025-03-04 11:23:02 +01:00
ArcandGitHub f2b9aafc51 Tweak: Changed naming for password area (#3016) 2025-03-04 11:13:41 +01:00
Vlad StanandGitHub ec77a00f49 feat: only check the current page (#3015) 2025-03-03 16:28:55 +02:00
991e0db50b Feat: email notifications (#3007)
* Feat: email notifications / email db backup

Because not using a simple smtp connection is silly

* Added form

* broken, and annoying bug

Why wont the input display?!?!?

* make

* Encourage protonmail use, because its great.

* feat: small UI polishing

* chore: make bundle

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-03-03 12:59:08 +00:00
fe9b62e8a8 Fix: don't show register if new users not allowed (#3010)
* enforce allow new user flag on UI
* chore: bundle
* ensure we get a boolean

---------

Co-authored-by: dni  <office@dnilabs.com>
2025-03-03 12:53:58 +01:00
dni ⚡andGitHub c5964436b5 fix: python versions were defined incorrectly and remove 3.9 (#3006)
* chore: update requirements to python3.10
* chore: format RUF007 rule for py310
* wrongly specified python version
python3.13 there is a greenlet build error
2025-03-03 12:53:30 +01:00
Vlad StanandGitHub 4ce14e2312 fix: rate lmitter init (#3011) 2025-02-28 12:50:50 +01:00
PatMulliganandGitHub 7021add193 Feat (crypto): Add URL-safe encoding option to AESCipher (#2984) 2025-02-28 11:17:53 +02:00
Tiago VasconcelosandGitHub 4be01a405c compare not assign (#3009)
fix missing = sign
2025-02-28 09:31:49 +02:00
Vlad StanandGitHub ebe9ff700f [fix] admin errors (#3004) 2025-02-27 11:00:15 +02:00
dni ⚡andGitHub a3619d40c6 fix: paying an invoice does not send a webhook. #2472 (#2999)
* fix: paying an invoice does not send a webhook. #2472
closes #2472
2025-02-27 09:23:49 +01:00
Vlad StanandGitHub 048aff3db4 [fix]: various issues discovered during testing (#3002)
* fix: prevent invalid calls to exchange APIs
* fix: audit conditions
* fix: audit filtering
2025-02-27 07:45:29 +01:00
Tiago VasconcelosandGitHub 89d85a0d7e [Fix] Denomination 3 characters (#3001)
* fix: denomination must be 3 chars ISO
2025-02-26 16:34:45 +01:00
5aa1f9b0f8 [feat] filter payments in wallet (#2997)
* fix: search payments
* fix rogue button
* feat: UI polishing
* feat: better charts

---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
2025-02-26 11:34:57 +01:00
Tiago VasconcelosandGitHub 5dc1705fa6 Fix a typo in reset password logic (#2998) 2025-02-25 13:52:03 +02:00
ArcandGitHub 0fc657c55c tweak: embit needs to be declared (#2996) 2025-02-24 15:05:20 +00:00
dni ⚡andGitHub 8641b34a3d chore: update lnbits version to 1.0.0-rc8 (#2995) 2025-02-24 14:53:52 +01:00
Vlad StanandGitHub 30cf6913af [feat]: invoice amount settings (#2990) 2025-02-24 14:45:17 +02:00
ArcandGitHub 10682cf736 tweak: checkout main not needed (#2993) 2025-02-24 12:26:42 +00:00
ArcandGitHub ee725846c5 tweak: just to make it obv port can be chnaged (#2992) 2025-02-24 12:19:42 +00:00
ArcandGitHub 69b31b1480 added libfuse2 to guide for fresh vps folks (#2991) 2025-02-24 12:15:29 +00:00
Tiago VasconcelosandGitHub 73a57ecf97 fix: payments filter (#2989) 2025-02-24 13:53:18 +02:00
dni ⚡andGitHub ab0f52d811 feat: add label for wallet fiat tracking (#2988) 2025-02-24 12:20:40 +01:00
dni ⚡andGitHub 8c59f3f4ae feat: add python-multipart for uploads (#2873) 2025-02-24 12:20:34 +02:00
ArcandGitHub 1bfb09ddab fix: default hide funding inputs (#2987)
* fix: default hide funding inputs

To protect admins from over the shoulder attackers, and the attackers from chipping away at their own morality.

* Remove need for function
2025-02-23 19:28:23 +00:00
ArcandGitHub 2af5ac9678 fix: wallet balance format (#2985) 2025-02-23 01:09:28 +00:00
4511891297 feat: modernize error page (#2949)
* can't figure this out!

* raise here so it gets picked up by exceptions

* add few more info for error rendering

* add a few more checks for browser

not fail safe just one more layer

* cleaner error display

... hopefully

* add go to extension

* keep buttons add go to extension

* feat: identify extensions that are not installed

* fix: status code

* fix: full path

* add account/logout button if 401

prevent getting stuck

* fix: ext access

* fix user button

* fix: 404 page

* fix: json 404 response

* fix: dumb rendering

* fix: `/api` request always json

* fix: extension api path

* test: check regtest

* test: investgate

* something made ws slower?

* fix: change error code

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2025-02-23 00:54:43 +00:00
eeca7de10d Fake wallet fixes (#2983)
* Fake wallet fixes

LNBITS_DENOMINATION and LNBITS_DENOMINATION == 'sats' everywhere!

* added exception to "/api/v1/rate/{currency}"

* fix: create invoice dialog

* cleaning expansion items in wallet

* Revert "fix: create invoice dialog"

This reverts commit 2b550392ba080cad708519fede897891e5b9e686.

* fix fakewallet mask break

* Fix formatted amount for fakewallet

* fixed wallet balance for fakewallet

* Wow, actually cleaning code, win!

* more cleaning

* For Vlad

* more cleaning

* make

* fix for sats

* bundle issue

* fix overflow in drawer for fat wallets and stupid long names

* make

* fixed adds

* fixed ad scale

* fundle

* fix

---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
2025-02-23 00:23:36 +00:00
b06c16ed57 fix: pay_invoice timeout to prevent blocking (#2875)
* fix: make invoice non blocking

* fix with vlad

* wallet.js take pending into account

* fixup!

* add check payment to outgoing

* revert

* bundle

* fix: label

* fix: rebase change

* feat: configure pay invoice wait time

* fix: check payment button

* bundle

* fix: task in async context

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2025-02-23 00:02:39 +00:00
dni ⚡andGitHub 2f2a3b1092 bug: settings fundingtab errors (#2982)
funding tab was not working if you had node ui enabled
2025-02-21 13:46:53 +02:00
Vlad StanandGitHub 9fe63b49d6 feat: add normalize_path helper (#2981) 2025-02-21 11:04:19 +02:00
Tiago VasconcelosandGitHub 6754ffb9dc change icon for pending (#2980) 2025-02-21 09:58:38 +02:00
Tiago VasconcelosandGitHub 4afe6dbd6d Change UI for Pay Invoice Dialog (#2979) 2025-02-18 18:42:46 +02:00
Tiago VasconcelosandGitHub 0cd94e654e adds a blur to the transparent bg (#2978) 2025-02-18 16:44:59 +02:00
Vlad StanandGitHub d1692a18dd fix: extract tag from extra (#2977) 2025-02-18 10:11:56 +02:00
Vlad Stan 0305d740ee feat: update LNBITS_SITE_TAGLINE 2025-02-18 09:35:28 +02:00
Tiago VasconcelosandGitHub 05777ca953 add blink logo (#2976) 2025-02-18 09:01:31 +02:00
Vlad StanandGitHub 1e083856ed fix: nostr login (#2975) 2025-02-17 16:18:04 +02:00
Vlad StanandGitHub c08623277e Chart polish (#2973) 2025-02-17 14:30:16 +02:00
Vlad StanandGitHub 60de308c8f fix: logout on safari (#2971) 2025-02-17 12:07:02 +02:00
ArcandGitHub 2aebe72df8 Quasarfy drawer scroll (#2965) 2025-02-16 02:00:33 +00:00
ArcandGitHub 2bd7600bcf Tweak so long wallet names/balances do not break styling (#2964) 2025-02-15 20:34:14 +00:00
tlindiandGitHub e318f7679e Update fi.js (#2963) 2025-02-14 19:44:08 +02:00
Vlad StanandGitHub 0912d7d1c8 fix: wallet url (#2961) 2025-02-13 22:54:28 +02:00
Vlad StanandGitHub bfa23568e3 [fix] callback url validation (#2959) 2025-02-13 15:11:46 +02:00
Vlad StanandGitHub b76d8b5458 fix: keep upgrades out of codebase (#2957)
* fix: keep upgrades out of codebase

* chore: code format
2025-02-12 13:31:53 +00:00
ArcandGitHub 1cef614349 Revert "Another small one (#2954)" (#2958)
This reverts commit 15ba3c33a3.
2025-02-12 12:06:09 +00:00
Vlad StanandGitHub 16fbfd1c6f Fix: date grouping for older SQLite versions (#2955) 2025-02-12 13:23:38 +02:00
Tiago VasconcelosandGitHub 15ba3c33a3 Another small one (#2954) 2025-02-12 10:56:22 +02:00
Tiago VasconcelosandGitHub b33a1903e8 check for g.user (#2953)
only render this if user is set
2025-02-11 17:25:42 +02:00
Vlad StanandGitHub 2d41a1bed3 Wallet polish (#2942) 2025-02-11 15:19:41 +02:00
ArcandGitHub 4d490506f3 Payments added to chart (#2952) 2025-02-11 12:26:04 +00:00
ArcandGitHub ed3a54e57a tweak: fun themes (#2951) 2025-02-10 23:20:30 +00:00
ArcandGitHub 61e8d0059b BG image/gradient to light fix (#2945) 2025-02-10 20:37:08 +00:00
ArcandGitHub 4a640dc123 Removes profile dropdown for public pages (#2943)
Global theme works for public pages.
2025-02-10 20:34:44 +00:00
ArcandGitHub 1f4160fda5 Adds extension#<ext name> option to extensions url (#2947) 2025-02-10 20:33:22 +00:00
ArcandGitHub dc91fbea9b Tweak: Updated Poetry install to include headers and make copy/paste easier (#2948)
* Tweak: Adds $USER to poetry install guide

Means a copy/paste will work for most

* auto agree for easy copy/paste

* poetry install and export

* ensure correct headers

* whoops

* typo
2025-02-10 20:32:33 +00:00
ArcandGitHub 7eb021f43c Adds no-border to fix the install card (#2950) 2025-02-10 20:31:41 +00:00
Vlad StanandGitHub 69350849b1 [fix] small fixes for ext upgrades (#2941) 2025-02-10 10:52:22 +02:00
Vlad StanandGitHub 828261d696 [fix]: small fixes v1 on demo DB (#2940) 2025-02-10 10:25:33 +02:00
ArcandGitHub 90dd3cc73a Fixes setting theme from account (#2939)
* getting there

* working

* make
2025-02-08 00:01:14 +00:00
ArcandGitHub 10c60291d2 white theme that doesnt hurt eyes (#2938) 2025-02-07 18:24:33 +00:00
ArcandGitHub 9d2e52f694 Fixes switching back to wallet (#2937)
* tals fix

* switched border back

* make

* bundle
2025-02-07 18:13:17 +00:00
Tiago VasconcelosandGitHub 503a599341 Fix login with oauth (#2935) 2025-02-07 16:48:22 +02:00
Vlad StanandGitHub a8809ed5a4 feat: migrate in chunks (#2936) 2025-02-07 14:16:53 +02:00
Tiago VasconcelosandGitHub e134c5c7b9 Fixes claiming LNURL (#2924) 2025-02-07 11:41:06 +02:00
ArcandGitHub 0efb52664e Feat: Creates .AppImage a̶n̶d̶ .d̶m̶g̶ on release (#2920) 2025-02-07 11:17:55 +02:00
Tiago VasconcelosandGitHub 53b00c6d41 UI improvements (#2926) 2025-02-07 10:09:15 +02:00
Tiago VasconcelosandGitHub 9a1d707f54 Revamp the UI on Login / Register page (#2919) 2025-02-07 09:43:19 +02:00
Tiago VasconcelosandGitHub 736699af94 . (#2925) 2025-02-06 15:10:16 +02:00
Tiago VasconcelosandGitHub 432b3a0fe0 Reset wallet keys (#2929) 2025-02-06 15:07:36 +02:00
Tiago VasconcelosandGitHub 34a959f0bc Add a payments page for admin (#2910) 2025-02-06 14:48:54 +02:00
Vlad Stan c1d26bb274 fix: email check 2025-02-06 11:57:01 +02:00
Vlad Stan b24a65cf2a doc: move security config at the top 2025-02-04 15:32:37 +02:00
ArcandGitHub 960c58db87 Feat: Adds wallet icon/color select (#2917) 2025-02-04 15:03:35 +02:00
ArcandGitHub dd9b217fdf Fix index.html error (#2923)
* Fix index.html error

* first install
2025-02-04 11:53:08 +00:00
Vlad StanandGitHub 7af9da4a58 fix: convert BigDecimal object to int (postgres only) (#2918) 2025-01-30 10:09:13 +02:00
ArcandGitHub c23eae9e08 Some menu naming changes (#2911) 2025-01-28 14:55:41 +02:00
ArcandGitHub 66b2ca2ac4 fixes wallet ws (#2912) 2025-01-28 14:14:12 +02:00
Tiago VasconcelosandGitHub 25e5423511 Add bar spinner (#2915) 2025-01-28 14:00:11 +02:00
Tiago VasconcelosandGitHub 4c7567a552 fix: router force update (#2914) 2025-01-28 13:44:37 +02:00
blackcoffeexbtandGitHub 73e716085b Translate expiry to expirySeconds for Phoenixd (#2901) 2025-01-28 11:54:33 +02:00
ArcandGitHub cc33a49b12 feature: Default theme for new users (#2898) 2025-01-28 11:29:38 +02:00
ArcandGitHub eeebaffbed feature: admin toggle to disable outgoing payments (#2909) 2025-01-28 10:27:24 +02:00
8633891485 fix: Add Breez SDK trampoline payments support (#2905)
* fix: add support for trampoline payments in Breez integration

- Introduced `BREEZ_USE_TRAMPOLINE` configuration in `.env.example`.
- Updated `BreezSdkFundingSource` to include `breez_use_trampoline` setting.
- Modified payment request in `breez.py` to utilize the trampoline setting when sending payments.

* Update .env.example

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* Update lnbits/settings.py

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* refactor: streamline payment request formatting in Breez integration

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2025-01-25 22:21:55 +01:00
Pavol RusnakandGitHub cb5cc1e5e8 make things work with Python 3.13 + fix Nix (#2908)
* update pyproject to make things work with python 3.13

* fix nix build
2025-01-23 23:11:45 +02:00
Vlad StanandGitHub f845bfe651 feat: do not allow user_id_only login for admins (#2904) 2025-01-23 15:01:54 +02:00
Vlad StanandGitHub b6bdf50ed7 [feat] Watchdog and notifications (#2895) 2025-01-23 13:23:09 +02:00
Vlad StanandGitHub 56a4b702f3 fix: multiple ws connections (#2907) 2025-01-23 13:05:35 +02:00
ArcandGitHub 273ab9781f fix: fiat tracking / updating wallet without reloads (#2891) 2025-01-22 10:27:05 +02:00
ArcandGitHub 8a759c8fc1 feature: Optional bg image and neon borders (#2897)
* init

* working
2025-01-18 19:27:50 +00:00
Tiago VasconcelosandGitHub c7ebe09463 Fix payment table refresh (#2894) 2025-01-17 09:49:14 +02:00
ArcandGitHub 5d4d22dcdd fix: for g.user error on public pages (#2892)
* fix for g.user error

* dont even mount
2025-01-16 16:18:15 +00:00
Vlad StanandGitHub b164317121 [feat] access control lists (with access tokens) (#2864) 2025-01-16 17:25:27 +02:00
Tiago VasconcelosandGitHub f415a92914 feat: harmonize the UI for payment dialog (#2882) 2025-01-16 14:31:33 +02:00
Vlad Stan 16c78f1901 fix: websocket prefix 2025-01-16 14:19:26 +02:00
ArcandGitHub 6a08d20fe8 v1 feat: Vue Routes (#2872) 2025-01-16 13:23:34 +02:00
dni ⚡andGitHub 5abfbdd07a fix: lnbits/upgrades where not ignore (#2889)
after updating extension i saw a dirty repo with the upgrades, this one
ignores them for sure
2025-01-15 18:32:53 +01:00
Riccardo BalboandGitHub bba7614012 fix: Add timeout to wait_for_connection (#2885) 2025-01-14 11:31:12 +02:00
dni ⚡andGitHub 2ee7180aa0 fix: cln nodemanager errors on failed transactions (#2865) 2025-01-13 17:58:37 +02:00
Pavol RusnakandGitHub 982732cbd4 i18n ai (#2881) 2025-01-11 12:53:52 +01:00
dfdb34852c chore: update i18n labels (#2871)
---------

Co-authored-by: dni  <office@dnilabs.com>
2025-01-10 12:15:51 +01:00
8fa5c81088 chore: update to breez-sdk 0.6.6 (#2878)
* chore: update to breez-sdk 0.6.6

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2025-01-10 11:56:10 +01:00
91748fc891 fix: restore user type badge (#2861)
---------

Co-authored-by: dni  <office@dnilabs.com>
2025-01-10 11:55:05 +01:00
Riccardo BalboandGitHub 0574837950 fix: NWC mark and remove subs before sending close request (#2870)
* mark and remove subscription even if close event fails to send

* catch CLOSE subscription exceptions

* format
2025-01-10 12:41:16 +02:00
dni ⚡andGitHub 7e27b2c6f0 fix: lndrest nodemanager get_channel (flaky ci decoy) (#2877) 2025-01-09 17:04:32 +01:00
6f3d0edfbd fix: ci with docker image built (downgrade poetry) (#2876)
* fix: ci with docker image built (downgrade poetry)

* use latest 1.8.x poetry

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2025-01-09 11:21:41 +02:00
dni ⚡andGitHub 3900d2871d feat: nodemanager, view and edit channels fees (#2818) 2024-12-20 09:48:09 +01:00
ArcandGitHub 0c5b909c7a Added settings links and link for theme (#2860) 2024-12-20 10:07:50 +02:00
ArcandGitHub 78437eaf94 feat: moved search inside the transaction table (#2859) 2024-12-20 07:44:07 +01:00
ArcandGitHub 8d1542d982 Added top cards (#2858)
* refactor old atempt

* Added watcher for smaller screens to stop bugs switching

* syntax error

* format
2024-12-19 16:16:09 +00:00
ArcandGitHub 146a74761a feat: standardises top spacing of page layout (#2848) 2024-12-19 15:14:30 +01:00
dni ⚡andGitHub 31664a4ba0 chore: update to v1.0.0-rc7 (#2856) 2024-12-19 12:00:40 +01:00
dni ⚡andGitHub f985a1eb08 test: use regtest main again (#2855) 2024-12-19 11:32:00 +01:00
4e5b001901 docs: updated install guide (#2846)
* Updated install guide
* Needed for secp256k1
* added lnbits-cli
* heading missing
* prettier

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-12-19 10:23:49 +01:00
dni ⚡andGitHub 3a2cf12052 fix: get_payments where clause does query wrong payments including tests (#2844)
* fix: get_payments where clause does query wrong payments (#2841)
2024-12-19 10:22:37 +01:00
Vlad StanandGitHub f17ff3d12b fix: set default values (#2854) 2024-12-19 10:08:34 +02:00
Vlad StanandGitHub 32c042741d fix: backwards compatible (#2853) 2024-12-19 09:50:52 +02:00
dni ⚡andGitHub d234ab12d8 ci: docker image and test with python3.12 (#2842) 2024-12-18 10:37:29 +02:00
37187bfc2c feat: add negative topups (#2835)
* feat: add negative topups
* remove topup dialog

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-12-17 21:06:58 +01:00
dni ⚡andGitHub 368da935db chore: update to corelightning 24.11 (#2836) 2024-12-17 10:02:34 +02:00
Tiago VasconcelosandGitHub 81aef06f75 make dialogs position at top (#2837) 2024-12-17 10:01:57 +02:00
Vlad StanandGitHub 27241e01f3 [feat] introduce max_lnbits_version for extensions (#2834)
* feat: add `max_lnbits_version`

* fix: min/max lables

* chore: `make bundle`

* refactor: extract `is_version_compatible_with_lnbits`

* refactor: rename method
2024-12-16 17:41:36 +02:00
Tiago VasconcelosandGitHub 3ff85363f2 hotfix the hotfix (#2832) 2024-12-16 16:38:22 +01:00
Vlad StanandGitHub b81b30c896 [feat] update multiple extensions from the UI (#2833)
* feat: add `Update` button
* feat: select all updatable extensions
* feat: dialog improvements
* fix: bad logging and permissions
2024-12-16 13:54:49 +01:00
jackstar12andGitHub 25d59e4142 feat: emulate behaviour of paid_invoices_stream for wallets that dont support it (#2071) 2024-12-16 13:37:28 +02:00
dni ⚡andGitHub 5f8ccee5b8 feat: websocket for payments events, remove /payments/sse and longpolling endpoint (#2704) 2024-12-16 11:10:25 +02:00
Tiago VasconcelosandGitHub 740180d332 fix: don't show when custom badge is not set (#2827) 2024-12-16 10:50:19 +02:00
dni ⚡andGitHub b66a8b3de9 fix: make startup extension check sync (#2819) 2024-12-16 10:45:39 +02:00
dni ⚡andGitHub b3351f2b17 fix: remove queue limit for invoice listners (#2829) 2024-12-13 17:12:35 +02:00
Vlad StanandGitHub 36dbdaa159 feat: return the price with the fiat rate (#2823)
* feat: return the price with the fiat rate
* fix: not supported currencies
2024-12-13 13:42:39 +01:00
Vlad StanandGitHub 524a4c9213 [feat] custom exchange providers (#2797) 2024-12-13 14:01:54 +02:00
dni ⚡andGitHub 200b9b127c test: fix jmeter for ci (#2825)
* print lnbits log
* fix java version for jmeter tests
2024-12-13 09:36:12 +01:00
dni ⚡andGitHub 646c341840 fix const req (#2824) 2024-12-13 08:25:44 +01:00
Vlad StanandGitHub 826f8ad2ac fix: ext upgrade from lnbits-cli (#2822) 2024-12-12 15:18:03 +02:00
dni ⚡andGitHub 1b9bb32ca0 test: fix lndrest on regtest (#2811)
* fix: lndrest on regtest
* fix some race conditions on lndrest
2024-12-12 07:54:43 +01:00
Tiago VasconcelosandGitHub 61c71660b8 fix: error on custom badge (#2814) 2024-12-11 12:50:35 +01:00
Vlad StanandGitHub 80ac158ca0 fix: extension init (#2815) 2024-12-11 13:49:12 +02:00
dni ⚡andGitHub 291c69e470 chore: update to latest pytest (#2800) 2024-12-11 11:39:28 +02:00
dni ⚡andGitHub 8ff4962e86 test: update ci from ubuntu-latest to ubuntu-24.04 (#2809) 2024-12-11 11:38:06 +02:00
dni ⚡andGitHub 57e9d2271d test: update python to 3.10 and 3.11 inside CI (#2803)
* test: update python to 3.10 and 3.11 inside CI

* fixup!
2024-12-11 10:39:25 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c18816bf1b chore(deps): bump nanoid from 3.3.7 to 3.3.8 (#2798)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 07:29:00 +01:00
dni ⚡andGitHub 8f290a647d chore: clean up javascript functions and var/let into const (#2791)
* chore: clean up javascript functions and var/let into const
2024-12-10 13:42:01 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>dni ⚡
d1cc317044 chore(deps): bump @intlify/core-base and vue-i18n (#2790)
* chore(deps): bump @intlify/core-base and vue-i18n

Bumps [@intlify/core-base](https://github.com/intlify/vue-i18n/tree/HEAD/packages/core) to 10.0.5 and updates ancestor dependency [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n). These dependencies need to be updated together.


Updates `@intlify/core-base` from 10.0.3 to 10.0.5
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v10.0.5/packages/core)

Updates `vue-i18n` from 10.0.3 to 10.0.5
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v10.0.5/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: "@intlify/core-base"
  dependency-type: indirect
- dependency-name: vue-i18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update vue-i18n

---------

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>
2024-12-10 12:00:55 +01:00
dni ⚡andGitHub 5a1d660c7b fix: last_payment on userlist was broken (#2796)
* fix: last_payment on userlist was broken
2024-12-10 11:48:43 +01:00
7ee78248b7 feat: add failed payments toggle to wallet page (#2794)
* feat: add failed payments toggle to wallet page

---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
2024-12-10 11:47:09 +01:00
Pavol RusnakandGitHub 5104cbb285 chore: use HTTPStatus where possible (#2795) 2024-12-09 13:50:33 +01:00
Vlad StanandGitHub fa71219ce7 [fix] wallet search on postgres (#2780)
* fix: wallet search for postgres
* reformat: make it easier to read
2024-12-09 12:16:34 +01:00
dni ⚡andGitHub f5f43e7668 chore: use HTTPStatus for ip middleware (#2792) 2024-12-09 12:44:01 +02:00
dni ⚡andGitHub 4b63d0924e chore: remove ignores from crypto.py (#2787) 2024-12-02 11:47:45 +01:00
dni ⚡andGitHub dc22088dcd chore: add type to migration (#2788) 2024-12-02 11:46:41 +01:00
dni ⚡andGitHub aa63879ff5 chore: update to v1.0.0-rc6 (#2784) 2024-11-28 18:01:08 +01:00
Vlad StanandGitHub 0547abe54a [feat] install extensions from dir (#2781)
* feat: search ext dir and install
* fix: `upgrade_hash` logic
* chore: clean-up `upgrade_hash` logic
* fix: screen refresh
* fix: ignore non-ext dirs
* fix: ext migration
2024-11-28 11:51:29 +01:00
dni ⚡andGitHub f5ccf5c157 fix: stuck pay_invoice (#2783)
db connection wasnt passed to `get_user_active_extensions_id`. and in
some contexts nested db connection will stuck the server
2024-11-28 11:50:15 +01:00
e3d6b6befa fix: some ui bugs (#2782)
remove splitter constrain

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
2024-11-28 10:43:31 +01:00
Vlad StanandGitHub fa8d7c665b Feat: audit (#2779) 2024-11-27 13:06:35 +02:00
f97f27121a feat: lnd_rest allow self payments (#2760)
* Update lndrest.py

- added the ability to receive self payments on the node

[issue] You have to enable circular payment on lnd.conf. Note that lnd.conf doesn't allow allow_self_payment parameter, that is why is necessary to define that on transaction.

* feat: add LND rest `"allow_self_payment"` option

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-11-26 13:32:44 +01:00
Vlad StanandGitHub af568d0f31 feat: improve user admin (#2777) 2024-11-19 10:33:57 +02:00
Vlad StanandGitHub 8c5c455f1c fix: ext upgrade (#2776) 2024-11-13 13:00:05 +02:00
Vlad Stan bbae4a01c1 fix: remove release candidate suffix 2024-11-13 10:59:27 +02:00
Vlad StanandGitHub edfd297bf3 refactor: simplify condition (#2775) 2024-11-13 10:28:46 +02:00
Vlad StanandGitHub 09b1623bb0 feat: let user filter its own extensions (#2774) 2024-11-12 15:17:05 +02:00
Vlad StanandGitHub fa18170ed7 fix: extension name and icon (#2771) 2024-11-12 10:34:45 +02:00
Vlad StanandGitHub 3eece03715 fix: vue3 changes (#2770) 2024-11-12 10:01:33 +02:00
Vlad StanandGitHub dd972e2aaa [ui] update settings UI (#2767) 2024-11-08 10:15:56 +02:00
Vlad StanandGitHub ec9ad9f940 refactor: make settings key-value in DB (#2766) 2024-11-08 10:06:21 +02:00
Vlad StanandGitHub aced333c0b fix: check the settings for extension installed (#2764) 2024-11-06 12:07:12 +02:00
Vlad StanandGitHub ba5f79da2d refactor: extract models (#2759) 2024-11-05 13:26:12 +02:00
dni ⚡andGitHub acb1b1ed91 fix: add list handling (#2758) 2024-11-01 11:12:18 +02:00
Vlad StanandGitHub 2fa0a3c995 fix: v1 left over (#2757) 2024-10-31 15:49:55 +02:00
Vlad StanandGitHub 1dddc9e7f0 fix: handle list models (#2756) 2024-10-31 14:21:23 +02:00
Osvaldo RosalesandGitHub 581f98b3a3 Add NFC Payment Support and Display Receive Amount in Receive Dialog (#2747)
* feat: add readNfcTag to core wallet

* feat: added payments/ endpoint to pay invoice with lnurlw from nfc tag

* feat: add notifications to nfc read and payment process

* feat: display sat and fiat amount on receive invoice

* feat: add notifications for non-lnurl nfc tags

* removed unnecesary payment updates

* fix: case when lnurlw was already used. lnurl_req status error

* fix: lnurl response status error

* fix: abort nfc reading on receive dialog hid

* feat: dismiss tap suggestion when nfc tag read successfully

* update: NFC supported chip

* remove console.log

* add: function return type

* test: happy path for api_payment_pay_with_nfc

* feat: follow LUD-17, no support for lightning: url schema

* explicit lnurl withdraw for payment

* test: add parametrized tests for all cases of api_payment_pay_with_nfc endpoint

* fix: payment.amount in response comes already in milisats
2024-10-29 23:50:48 +01:00
ArcandGitHub 89a75ab641 Hide admin keys in curl example (#2753) 2024-10-29 23:49:46 +01:00
dni ⚡andGitHub f228198c20 fix: default timestamp on models (#2755)
closes #2754
2024-10-29 15:39:06 +01:00
2940cf97c5 feat: parse nested pydantic models fetchone and fetchall + add shortcuts for insert_query and update_query into Database (#2714)
* feat: add shortcuts for insert_query and update_query into `Database`
example: await db.insert("table_name", base_model)
* remove where from argument
* chore: code clean-up
* extension manager
* lnbits-qrcode  components
* parse date from dict
* refactor: make `settings` a fixture
* chore: remove verbose key names
* fix: time column
* fix: cast balance to `int`
* extension toggle vue3
* vue3 @input migration
* fix: payment extra and payment hash
* fix dynamic fields and ext db migration
* remove shadow on cards in dark theme
* screwed up and made more css pushes to this branch
* attempt to make chip component in settings dynamic fields
* dynamic chips
* qrscanner
* clean init admin settings
* make get_user better
* add dbversion model
* remove update_payment_status/extra/details
* traces for value and assertion errors
* refactor services
* add PaymentFiatAmount
* return Payment on api endpoints
* rename to get_user_from_account
* refactor: just refactor (#2740)
* rc5
* Fix db cache (#2741)
* [refactor] split services.py (#2742)
* refactor: spit `core.py` (#2743)
* refactor: make QR more customizable
* fix: print.html
* fix: qrcode options
* fix: white shadow on dark theme
* fix: datetime wasnt parsed in dict_to_model
* add timezone for conversion
* only parse timestamp for sqlite, postgres does it
* log internal payment success
* fix: export wallet to phone QR
* Adding a customisable border theme, like gradient (#2746)
* fixed mobile scan btn
* fix test websocket
* fix get_payments tests
* dict_to_model skip none values
* preimage none instead of defaulting to 0000...
* fixup test real invoice tests
* fixed pheonixd for wss
* fix nodemanager test settings
* fix lnbits funding
* only insert extension when they dont exist

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
Co-authored-by: Arc <ben@arc.wales>
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2024-10-29 09:58:22 +01:00
Vlad StanandGitHub ae4eda04ba test: more payment tests (#2738)
* test: pay_invoice
2024-10-17 10:27:36 +02:00
Vlad StanandGitHub 13f2dd732f fix: await retry (#2739) 2024-10-16 11:09:45 +02:00
Pavol RusnakandGitHub 80ec9e1307 update starlette and fastapi (#2737) 2024-10-16 07:00:12 +02:00
Vlad StanandGitHub 3b503eaa8a feat: add server status endpoin (#2736) 2024-10-15 14:53:49 +03:00
dni ⚡andGitHub ecc62b0011 feat: explicitly export for extensions from __init__.py (#2669)
* feat: explicitly export for extensions from `__init__.py`

makes it clear what extensions are expected to use and also makes future
changes to the structure of core safer because extension can just depend
on ```from lnbits import require_admin_key``` for example
2024-10-14 10:35:41 +03:00
b83c2e9368 docs: fix installation guide (#2734)
Co-authored-by: Gálli Zoltán <gallizoltan@gmail.com>
2024-10-10 21:22:15 +02:00
jackstar12andGitHub 512c85592f fix(boltz): sanitize invoice description (#2731)
Boltz rejects nbsp char (produced by JS Intl.NumberFormat api), so simply replace it with normal space
2024-10-09 19:32:56 +02:00
jackstar12andGitHub 76b01695a0 fix(boltz): check for direct onchain payments (#2732) 2024-10-09 18:52:20 +02:00
jackstar12andGitHub d7065392d1 chore: add .idea folder to gitignore (#2730) 2024-10-09 19:31:50 +03:00
Vlad StanandGitHub a58deff70c feat: block pay invoice (#2727) 2024-10-01 17:17:42 +03:00
dni ⚡andGitHub e85a78854e feat: make LNBITS_ADMIN_UI the default (#2726)
* feat: make LNBITS_ADMIN_UI the default
* fix create fake admin
2024-10-01 11:36:22 +02:00
dni ⚡andGitHub a28e334c5f test: fix test warning (#2725) 2024-10-01 11:07:13 +02:00
a4c000d7dc feat: add password reset for usermanager (#2688)
* feat: add password reset for usermanager
- add a reset_key to account table
- add ?reset_key= GET arguments to index.html and show reset form if provided
- superuser can generate and copy reset url with key to share
future ideas:
- could add send forgot password email if user fill out email address
* feat: simplify reset key
* test: use reset key
* test: add more tests
* test: reset passwords do not match
* test: `reset_password_auth_threshold_expired`

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-10-01 10:59:57 +02:00
dni ⚡andGitHub 3a64cf5245 feat: add currency options to LNbits.api (#2713)
* feat: add currency options to LNbits.api
2024-09-30 14:01:30 +02:00
0b8da2b524 [feat] Nostr Login (#2703)
---------

Co-authored-by: dni  <office@dnilabs.com>
2024-09-30 13:53:38 +02:00
dni ⚡andGitHub f062b3d5e5 ci: do not create gh release on rc's (#2716)
* ci: do not create gh release on rc's
2024-09-30 08:51:19 +02:00
dni ⚡andGitHub b9e9da5da7 fix: fundingsources template id was wrong (#2723) 2024-09-29 22:59:13 +02:00
dni ⚡andGitHub 953a466119 fix: more vue3 q-tables and v-model:value (#2717) 2024-09-27 15:18:56 +02:00
dni ⚡andGitHub 7ad360d176 fix: vue3 i18n changed it replacements markers (#2722)
* fix: vue3 i18n changed it replacements markers
* chore: bundle
2024-09-27 14:59:06 +02:00
dni ⚡andGitHub 898f264951 refactor: use <template> for components (#2715)
* refactor: use <template> for components
* fix paymentchart
2024-09-27 12:09:47 +02:00
dni ⚡andGitHub 75bcbdc18d chore: remove used crud for webpush settings (#2719) 2024-09-27 09:35:33 +03:00
dni ⚡andGitHub 3ac6bc3c3b fix: vue3 language switcher (#2712) 2024-09-25 12:09:00 +02:00
Vlad StanandGitHub 04882e05a3 chore: update some log messages (#2710) 2024-09-25 11:22:00 +03:00
dni ⚡andGitHub cf2aca3327 fix: forgotten Vue inits (#2708) 2024-09-25 08:01:11 +03:00
dni ⚡andGitHub 1d0547ea97 fix: use Quasar.notify (#2707)
* fix: use Quasar.Notify.create
instead of this.$q, had issues when pasting invoice, using the `Quasar`
is more consistent
2024-09-24 16:18:56 +02:00
dni ⚡andGitHub 99c5f00c91 fix: extensions tab for vue3 (#2706) 2024-09-24 13:39:31 +03:00
dni ⚡andGitHub 723d8ba98f chore: update to v1.0.0-rc2 (#2705) 2024-09-24 11:48:28 +02:00
daee2b3418 Check for theme params on the URL (#2678)
---------

Co-authored-by: dni  <office@dnilabs.com>
2024-09-24 11:44:07 +02:00
dni ⚡andGitHub 9d7e54f6b2 refactor: use CreatePayment model instead of a lot of kwargs (#2667)
- refactoring create_payment a bit to use a model instead of 10 kwargs
2024-09-24 11:13:30 +02:00
053ea20508 feat: update to Vue3 (#2677)
* update packages for vue3
* fix make bundle and make checkbundle to include bundle-components
* add lnbits/static/bundle-components.js

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-09-24 11:06:27 +02:00
dni ⚡andGitHub 04aefc8077 refactor: remove get_key_type decorator (#2676)
* refactor: remove `get_key_type` decorator
breaking change for 1.0.0
2024-09-24 10:56:34 +02:00
21d87adc52 mega chore: update sqlalchemy (#2611)
* update sqlalchemy to 1.4
* async postgres

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-09-24 10:56:03 +02:00
dni ⚡andGitHub c637e8d31e fix: internal payment can still be pending (#2686)
bug introduced last commit
2024-09-16 20:43:17 +02:00
dni ⚡andGitHub d26e50ec9a refactor: rename is_uncheckable (#2670)
it actually means is_internal and internal payments cant fail so we return success status
2024-09-16 17:34:30 +02:00
ArcandGitHub d229b7a765 fix: bash failed using after install (#2685) 2024-09-14 11:08:25 +02:00
ceb43f384e feat: install lnbits.sh bash script (#2684)
Co-authored-by: arcbtc <ben@arc.wales>
2024-09-12 08:02:47 +02:00
dni ⚡andGitHub 22e6326bce fix: gitignore extensions (#2682) 2024-09-11 19:34:35 +02:00
Vlad StanandGitHub 5f4f1288d7 Fix overlapping redirect paths (#2671) 2024-09-11 12:41:37 +03:00
blackcoffeexbtandGitHub 7a5e7fbd8c feat: UI / UX improvements to Users balance / tx chart (#2672)
* Updates to user manager chart to add axis label, bubble radius depending on balance and bubble labels with wallet info

* Fixed bg colour missing on toggle admin on user manager table
2024-09-11 09:40:41 +02:00
dni ⚡andGitHub 6c8d56e40c chore: update to 1.0.0-rc1 (#2675)
* chore: update to 1.0.0-rc1
2024-09-05 12:28:40 +02:00
Tiago VasconcelosandGitHub 8aa1716e32 Add detailed CSV export option (#2618) 2024-09-04 13:04:55 +03:00
fbd22c1a22 fix: QR request case (#2668)
* fix the hotfix to fix a fuckup
* fixup!

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-09-03 16:05:55 +02:00
dni ⚡andGitHub e8a6870d7a fix: update lnurl for backwards compatibility (#2666) 2024-09-03 12:03:26 +02:00
Tiago VasconcelosandGitHub 937936ff33 fix fuck up on refactor (#2665)
Bug introduced in #2656
2024-09-03 12:03:09 +02:00
Pavol RusnakandGitHub ab3eb967b4 chore: update pyproject dependencies (#2619) 2024-09-03 11:09:36 +02:00
dni ⚡andGitHub 6341e1edaf feat: add baseurl to admin ui settings (#2644)
* feat: add baseurl to admin ui settings

server tab with a hint that it is currently not used.

i ran into an issue developing an extension where i needed to know the
url inside a task, where i cannot pass the `Request` object.
so i depend on `settings.lnbits_baseurl` there
2024-09-03 07:08:56 +02:00
69d518bac0 Uncomment LNBITS_ALLOWED_FUNDING_SOURCES (#2663)
* Uncomment LNBITS_ALLOWED_FUNDING_SOURCES

I don't see why this exists

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-08-30 18:51:28 +02:00
dni ⚡andGitHub 9db2429a45 refactor: move migrate_databases to core helpers (#2636)
commands.py should be used for cli commands
2024-08-30 18:48:50 +02:00
dd90dec768 feat: frontend gradient option (#2561)
---------

Co-authored-by: dni  <office@dnilabs.com>
2024-08-30 18:16:24 +02:00
dni ⚡andGitHub eb37a064ad feat: vue components lnbits-dynamic-fields validation (#2645)
* feat: vue components lnbits-dynamic-fields validation

- add validation to fields if require = true
- add type hidden field (can be useful for create/update dialog with
passing item_id into update hidden field)
2024-08-30 18:06:55 +02:00
Tiago VasconcelosandGitHub 28df100d9a Fix lnurl fallback (#2656)
* Fix lnurl fallback

Wrong string case search

Closes #1599

* optimize the code

* one more clean up
2024-08-30 12:48:24 +01:00
6e6b387b7d feat: log with console.error on notifyApiError (#2646)
* feat: log with `console.error` on `notifyApiError`

usually why have code like that
```
    getAudit() {
      LNbits.api
        .request('GET', '/admin/api/v1/audit',
this.g.user.wallets[0].adminkey)
        .then(response => {
          this.auditData = response.data
          xonst myerror = isSwallowed()
        })
        .catch(function (error) {
          LNbits.utils.notifyApiError(error)
        })
    },

```
which if you make a mistake hides your error. logging console.error here
again makes it easier to see what is happening while developing and
doesnt hurt for the user aswell

* chore: bundle

* fixup!

---------

Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2024-08-30 12:23:11 +01:00
Tiago VasconcelosandGitHub 209cf7fbe0 fix chips on server page (#2654)
Fix chips not having the remove functionality

Closes #2471
2024-08-30 12:19:22 +01:00
Pavol RusnakandGitHub 596167f443 chore: update install instructions for developers (#2652) 2024-08-30 12:18:42 +01:00
Gonçalo ValérioandGitHub 4732c4b296 add proper validation for the unit field when creating an invoice (#2647) 2024-08-30 12:17:52 +01:00
Tiago VasconcelosandGitHub 405a2f0776 fix decode info (#2655)
Closes #1837
2024-08-30 12:15:29 +01:00
dni ⚡andGitHub 304ad3035b feat: add generic lnurl error response handler (#2638)
* feat: add generic lnurl error response handler

this is used multiple times in extensions to safeguard `views_lnurl.py`
endpoint to not return a wrong lnurl error response.

you use it by just setting following on your lnurl router/view

```
withdraw_ext_lnurl = APIRouter(prefix="/api/v1/lnurl")
withdraw_ext_lnurl.route_class = LNURLErrorResponseHandler
```
2024-08-30 13:12:55 +02:00
Pavol RusnakandGitHub 8cffda5a55 chore: update package.json (#2635)
* chore: update package.json

* chore: make bundle
2024-08-29 22:51:41 +02:00
Tiago VasconcelosandGitHub cbe858b385 show wallet names in dropdown (#2653)
Extensions will need it also

Fix #2517
2024-08-29 21:43:00 +01:00
dni ⚡andGitHub 65ecca2507 feat: add rate endpoint per currency (#2641)
this is already used and implemented by tpos, lnurlp and i probably need
it for satspay aswell
2024-08-20 10:52:39 +01:00
1900cf9aa4 feat: add boltz client fundingsource (#2358)
* feat: add boltz client standalone fundingsource
WIP.
https://docs.boltz.exchange/v/boltz-client

this fundingsource utilizing the boltz client standalone function: https://github.com/BoltzExchange/boltz-client/pull/123
this makes him act like a lightning node while submarine swapping everything on liquid network. like aqua does in its wallet.

* feat: paid_invoices_stream

* feat: proper invoice and payment status check

* feat: authenticate over insecure channel aswell

* chore: lint

* docs: add more setup instructions

* chore: add `boltz_client_cert` in frontend

* feat: populate fee_msat in get_payment_status and get_invoice_status

* fixup!

* chore: bundle

* added boltz logo

* add BoltzWallet to __all__

* chore: bump grpcio and protobuf deps and add grpcio-tools as dev dependency

* chore: update protos

* feat: pass description when creating swap

* fixup!

* chore: bundle

---------

Co-authored-by: jackstar12 <jkranawetter05@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-08-20 10:38:30 +01:00
Tiago VasconcelosandGitHub 296b1dfa9a add copy buttons (#2643) 2024-08-19 15:23:03 +02:00
dni ⚡andGitHub b14e0e4cc6 feat: add label to lnbits-dynamic-fields (#2637)
* feat: add label to `lnbits-dynamic-fields`

* chore: bundle
2024-08-14 16:52:19 +02:00
Ross SavageandGitHub fb585402dd update breez-sdk to 0.5.2 (#2639) 2024-08-14 15:42:12 +01:00
dni ⚡andGitHub 63f246908e fix: add back lnurl wallet (#2625)
* fix: add back lnurl wallet
* add comment from withdraw ext
* fixup, when you use unique links
2024-08-08 11:09:01 +02:00
dni ⚡andGitHub 8ac827f5a4 fix: export nwc wallet (#2632)
quick fix, #2630
2024-08-08 07:34:31 +02:00
dni ⚡andGitHub 74d4ddd312 feat: use __all__ to export deps from __init__.py (#2630)
* feat: export wallets
* remove linting exception
2024-08-08 07:29:21 +02:00
dni ⚡andGitHub 40ffa7dea0 test: refactor to not use paid_invoices stream for real invoice tests (#2628) 2024-08-07 16:19:53 +02:00
dni ⚡andGitHub ddb8fcb986 feat: add typing for tasks (#2629)
* feat: add typing for tasks

* fixup!
2024-08-07 09:57:15 +02:00
27b9e8254c feat: NWC Funding source #2579 (#2631)
* feat: nwc funding source

* implement paid_invoices_stream, fix for unsettled invoices where settled_at is present but None

* cancel pending_payments_lookup_task on cleanup

* Rename subscription_timeout_task to timeout_task

* ensure preimage is not None

* Improve readability, return failed status on expiration in get_payment_status, ensure result_type is checked after error (some implementations might not set a result_type on error)

* fetch account info when possible

* workaround possible race condition on some nwc service providers, improve performance of fallback by using payment_hash from bolt11 invoice

* fundle

* make format

* fix formatting

* fix C901 `_on_message` is too complex (21 > 16)

* format

* fix lint

* format

* fix tests/wallets/test_nwc_wallets.py:80:11: C901 `run` is too complex (17 > 16)

* fix padding

* fix documentation for _verify_event method

* refactoring and fixes

* Split NWCWallet - NWCConnection

* refactor class methods into helpers

* update bundle

* format

* catch NWCError failure codes

* format and fix

* chore: bundle

* add example

* typos

---------

Co-authored-by: Riccardo Balbo <riccardo0blb@gmail.com>
Co-authored-by: benarc <ben@arc.wales>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-08-07 09:56:53 +02:00
daa4b92331 fix: db helpers to be used with timestamps (#2627)
* fix: db helpers to be used with timestamps

those helpers are used in boltz extension and they did not take dates
into consideration yet

* vlad picks
* refactor get_placeholder

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-08-06 12:43:44 +02:00
0015314e11 feat: add Breez SDK wallet (#1897)
* add Breez SDK wallet
* use more description status classes
* fix: add try-except

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-08-06 10:06:21 +02:00
blackcoffeexbtandGitHub 235f8a6c19 Invoice creation UI: Replace input mask with pattern and inputmode (#2623)
* Invoice creation UI. Replace input mask with pattern and inputmode
2024-08-02 09:26:10 +03:00
Pavol RusnakandGitHub bab399f825 fix: run cachix nix action for all branches (#2624) 2024-08-01 21:54:15 +02:00
dni ⚡andGitHub 646a604221 feat: add release flow for release candidates (#2620)
- pushes docker tag
- pushes to pypi for extensions to update
- generates a prerelease on github
2024-08-01 13:05:02 +02:00
7d8fad267a fix: set a maximium sleep time when retrying to connect to the funding source (#2622)
---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-08-01 13:02:55 +02:00
dni ⚡andGitHub ce0aff206d fix: skip pending payment check on void (#2610)
skips payment check if voidwallet is active
2024-07-31 15:06:31 +03:00
80e7b9639d feat: filter response fields for /api/v1/payments/decode (#2612)
* feat: filter response fields

* chore: `make format`

* chore: comment

* Update lnbits/helpers.py

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* Update lnbits/helpers.py

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* chore: code format

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-07-31 14:36:42 +03:00
dni ⚡andGitHub 94caa2e1ba fix: still flaky regtest (#2617)
increase wait
2024-07-31 12:05:07 +02:00
dni ⚡andGitHub b0a66e8cf5 fix: random exceptions inside logs in middleware (#2608)
closes #2599

special thanks to bitkarrot for figuring that out, it was actually
initialized twice, the decorator alone is enough!

also was issue for first_install middleware
2024-07-31 12:00:40 +02:00
dni ⚡andGitHub ffba71c0ce test: fix flaky regtest (#2616)
* add log for invoice success
* add internal flag
* sleeping inside the tasks to not block
* sleep was wrong decrease wait time
2024-07-31 11:41:19 +02:00
b41705167f feat: extra log for tests phases (#2604)
* fix: set `corelightning_rest_cert`
* feat: extra log for tests phases

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-07-31 11:40:25 +02:00
blackcoffeexbtandGitHub 0387db3b55 Update installation.md (#2615)
mkdir data no longer needed for standard installation
2024-07-31 10:40:17 +03:00
Tiago VasconcelosandGitHub 38ef1b0061 feat: add a copy wallet button (#2613)
* feat: add a copy wallet button
* fix: make button consistent
* feat: hide API keys
Closes #2462
* fix: consistency in icons
* fix: missing end tag
2024-07-31 08:51:17 +02:00
Pavol RusnakandGitHub 19c231a2f4 chore: update pyln-client to 24.5 (#2614) 2024-07-30 18:04:28 +02:00
BitkarrotandGitHub 026c9b5155 Fix for LNURLp with ZBD wallet (#2609) 2024-07-30 18:04:07 +02:00
Pavol RusnakandGitHub c834929f8b add bitpay and yadio fiat rate providers + increase precision of blockchain.info fiat rate provider (#2605)
* feat: increase precision of blockchain.info fiat rate provider
* feat: add bitpay and yadio fiat rate providers
2024-07-30 13:44:08 +02:00
dni ⚡andGitHub 97de4eda18 feat: add exclude_to list for btcprice providers (#2602) 2024-07-26 11:31:24 +02:00
dni ⚡andGitHub a844711530 bug: removing admin user was not persistent (#2603)
thanks for reporting, that was a serious bug. the indentation was wrong
2024-07-26 11:15:34 +02:00
dni ⚡andGitHub e2522b358a chore: bump version to 0.12.11 (#2601)
this is needed for testing the new lndhub version
2024-07-25 10:19:18 +02:00
dni ⚡andGitHub 8f761dfd0f refactor: add status column to apipayments (#2537)
* refactor: add status column to apipayments

keep track of the payment status with an enum and persist it as string
to db. `pending`, `success`, `failed`.

- database migration
- remove deleting of payments, failed payments stay
2024-07-24 16:47:26 +03:00
Pavol RusnakandGitHub b14d36a0aa chore(deps): replace python-jose with pyjwt (#2591)
python-jose had no release since 3 years
2024-07-24 10:42:47 +02:00
dni ⚡andGitHub dbb689c5c5 chore: update version to 0.12.10 (#2597) 2024-07-23 14:06:55 +02:00
dni ⚡andGitHub 2167aa398f fix: annotations for models.py (#2595) 2024-07-23 14:03:27 +02:00
dni ⚡andGitHub eb8d2f312f fix: install extensions async (#2596)
so it does not block webserver start on saas instances and comes up
faster if extensions are reinstalled
2024-07-23 14:01:34 +02:00
jackstar12andGitHub f9133760fc fix: proper status check in invoice paid callback (#2592)
status fields like preimage and fee_msat are never updated otherwise
2024-07-22 16:59:26 +02:00
7298c4664b feat: Blink funding source (#2477)
* feat: Blink funding source

* chore: make bundle

* Blink review 01 (#2575)

* refactor: mark `graphql_query` as private (`_` prefix)

* feat: set default value for `blink_api_endpoint`

* fix: raise if HTTP call failed

* refactor: move private method to the bottom

* refactor: make `wallet_id` a property

* fix: key mapping for attribute

* chore: fix `mypy`

* chore: fix `make check`

* refactor: extract query strings

* refactor: extract `BlinkGrafqlQueries` class

* chore: code clean-up

* chore: add `try-catch`

* refactor: extract `tx_query`

* chore: format grapfhql queries

* fix: set funding source class

* chore: `make format`

* fix: test by following the other patterns

* Update docs/guide/wallets.md

Co-authored-by: openoms <43343391+openoms@users.noreply.github.com>

* feat: add websocket connection to blink (#2577)

* feat: add websocket connection to blink

* feat: close websocket on shutdown

* feat: add `blink_ws_endpoint` to the UI

* fix: use `SEND` tx for `settlementFee`

* refactor: remove `else` when `if` has `return`

* fix: remove test env file

---------

Co-authored-by: bitkarrot <73979971+bitkarrot@users.noreply.github.com>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: openoms <43343391+openoms@users.noreply.github.com>
2024-07-19 21:32:02 +01:00
Pavol RusnakandGitHub eda7e35c61 chore: update python deps (#2586) 2024-07-16 07:59:04 +02:00
7d1e22c7de fix: always create default wallet for user (#2580)
* fix: always create default wallet for user

* no assert in api

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-07-15 13:34:26 +03:00
michael1011andGitHub 1bee84d419 chore: optimize Dockerfile (#2576)
By doing a multi-stage Docker build, the size
of the final image is reduced by ~45%.
2024-07-10 14:22:44 +02:00
a00292544f fix: lib secp256k fails building (#2572)
Co-authored-by: michael1011 <me@michael1011.at>
2024-07-09 15:51:40 +02:00
ArcandGitHub fe14c2cd83 Fixed ugly adv description card (#2570) 2024-07-09 14:03:11 +01:00
schneimiandGitHub 760f11f1ce Update wallet.js (#2569)
enable 'Read' button after 'Paste from clipboard' click
2024-07-09 13:57:03 +01:00
0e1090b717 docs: nginx websocket support for reverse proxy (#2564)
* docs: nginx websocket support for reverse proxy

websocket weren't working with that conf

* cleanup

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-07-09 13:56:31 +01:00
Vlad StanandGitHub b2564154cd [feat] add default_user_extensions setting (#2571)
* feat: add `lnbits_user_default_extensions` to `settings`

* refactor: extract `create_user_account` in services

* feat: auto enable user extensions
2024-07-09 13:55:05 +01:00
Vlad StanandGitHub fb17611207 [feat] add authenticated_user_id decorator (#2566)
* feat: add `optional_user_id` decorator
2024-07-04 17:24:20 +03:00
ArcandGitHub 7f628948c9 bug: carousel and adv desc fix (#2562)
* Carousel image fix
* pushed carousel nav to top as well so yt controls are accessible
* Added some extra breathing room for the adv description
2024-06-26 13:21:34 +02:00
dni ⚡andGitHub cbe0861439 feat: improve on extension page layout (#2558)
* feat: improve on extension page layout
give it more luft and simplify it a bit. also improved responsiveness
* show description now, ben
2024-06-26 13:07:13 +02:00
dni ⚡andGitHub e9d6160f4d chore: prepare for 0.12.9 (#2555) 2024-06-19 12:56:34 +02:00
Vlad StanandGitHub eacdd432b2 [feat] Extension details page (#2544)
* feat: add empty dialog

* feat: add `details_link` field for extension

* feat: show info icon if `details_link` present

* feat: add extension details endpoint

* feat: first details page

* feat: carousel working

* feat: full screen

* fix: layout

* fix: repo site

* fix: release icon

* fix: repo link

* feat: terms and conditions partial

* chore: fix typing

* fix: info icon layout

* chore: add try-catch

* feat: layout improvements

* feat: add video link

* fix: show terms and conditions

* chore: code format

* feat: add `details_link`

* fix: github release details

* feat: add close button

* chore: code clean-up

* chore: revert some changes

* feat: i18n

* chore: `make bundle`

* chore: make bundle

* feat: terms and conditions is a link now
2024-06-19 11:52:18 +01:00
Pavol RusnakandGitHub 76e8d72d0d LNBits -> LNbits typo (#2552)
* LNBits -> LNbits
2024-06-19 09:27:26 +03:00
dbacf7e8c1 "Failed to connect to https://ws:" crash (#2548)
* Update base.py

fixing bug in normalize_enpoints

* Update lnbits/wallets/base.py

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

---------

Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-06-17 17:36:15 +02:00
dni ⚡andGitHub b6d99b09cf fix: npm packages with high severity issue (#2551)
updating `braces` npm package
2024-06-17 17:17:53 +02:00
5c21e7f9ed Update installation.md (#2549)
Minor rewording

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-06-17 11:24:31 +02:00
14e9c7d9dc Fix typo of "LNbits" in list of funding sources. (#2546)
* Fix typo of "LNbits" in list of funding sources.


---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-06-17 09:08:22 +02:00
blackcoffeexbtandGitHub b3368d89f4 Nice formatting of funding source titles in Server admin (#2543)
* Display friendly funding source titles in funding sources list

* Sort funding options select alphabetically

* Run make bundle
2024-06-10 23:37:09 +02:00
83b89851a5 fix: phoenixd wallet description field supports lnurlp (#2514)
* Fix for phoenixd and lnurlp nostr usage

- Support description, restricted to 128 characters
- Support descriptionHash

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: Vic <vic@example.com>
2024-05-29 13:47:10 +03:00
dni ⚡andGitHub 2db5a83f4e chore: update LNbits to 0.12.8 (#2539) 2024-05-28 13:59:13 +02:00
d72cf40439 [feat] Pay to enable extension (#2516)
* feat: add payment tab

* feat: add buttons

* feat: persist `pay to enable` changes

* fix: do not disable extension on upgrade

* fix: show releases tab first

* feat: extract `enableExtension` logic

* refactor: rename routes

* feat: show dialog for paying extension

* feat: create invoice to enable

* refactor: extract enable/disable extension logic

* feat: add extra info to UserExtensions

* feat: check payment for extension enable

* fix: parsing

* feat: admins must not pay

* fix: code checks

* fix: test

* refactor: extract extension activate/deactivate to the `api` side

* feat: add `get_user_extensions `

* feat: return explicit `requiresPayment`

* feat: add `isPaymentRequired` to extension list

* fix: `paid_to_enable` status

* fix: ui layout

* feat: show QR Code

* feat: wait for invoice to be paid

* test: removed deprecated test and dead code

* feat: add re-check button

* refactor: rename paths for endpoints

* feat: i18n

* feat: add `{"success": True}`

* test: fix listener

* fix: rebase errors

* chore: update bundle

* fix: return error status code for the HTML error pages

* fix: active extension loading from file system

* chore: temp commit

* fix: premature optimisation

* chore: make check

* refactor: remove extracted logic

* chore: code format

* fix: enable by default after install

* fix: use `discard` instead of `remove` for `set`

* chore: code format

* fix: better error code

* fix: check for stop function before invoking

* feat: check if the wallet belongs to the admin user

* refactor: return 402 Requires Payment

* chore: more typing

* chore: temp checkout different branch for tests

* fix: too much typing

* fix: remove try-except

* fix: typo

* fix: manual format

* fix: merge issue

* remove this line

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-05-28 12:07:33 +01:00
Vlad StanandGitHub 7c68a02eee [feat] Check payment tag (#2522)
* feat: check if the payment is made for an extension that the user disabed
2024-05-24 17:24:59 +03:00
Vlad StanandGitHub 93965bc5b6 [test] webpush_api endpoints (#2534)
* test: webpush_api endpoints

* fix: SQL quote for `user`
2024-05-23 23:23:32 +02:00
Vlad StanandGitHub ae60b4517c [fix] SQL error for create webpush notification (#2533)
* fix: replace all SQL `user = ?` with `"user"" = ?`
* fix: surround with try-catch
* fix: bad double quote
2024-05-23 10:16:00 +02:00
dni ⚡andGitHub b15596d045 fix-fiat-balance (#2515) 2024-05-23 07:47:56 +02:00
Vlad StanandGitHub 07f0dc80f8 [fix] editable fields with default=None must be Optional (#2530)
* fix: optional fields

* fix: bad uppercase field
2024-05-22 14:03:52 +01:00
Pavol RusnakandGitHub 5f64c298c9 chore(i18n-ai-tool): use gpt-4o model + chore(i18n): update strings using the AI tool (#2511)
* chore(i18n-ai-tool): use gpt-4o model

* chore(i18n): update strings using the AI tool
2024-05-22 14:18:23 +03:00
dni ⚡andGitHub 5b056ce07e feat: update latest docker tag on release (#2528) 2024-05-22 13:47:41 +03:00
Vlad Stananddni ⚡ 44b458ebb8 [fix] check user extension access (#2519)
* feat: check user extension access
* fix: handle upgraded extensions
2024-05-22 11:10:35 +02:00
Vlad Stan d4da96597e chore: project version bump 2024-05-22 12:04:56 +03:00
Vlad StanandGitHub 6a0b645316 hotfix: check for lnbits_site_description setting (#2527)
* fix: make `lnbits_site_description` optional again
2024-05-22 10:53:34 +02:00
dni ⚡andGitHub 35bb3cc94b fix: topup wallet was showing NaN (#2504)
* fix: topup wallet was showing NaN
2024-05-16 15:31:24 +02:00
Gonçalo ValérioandGitHub 7a6c3646fb Fix: Wrong expiration date in invoice details (#2506)
* invoices without the x tag, should be assumed to take 3600 seconds to expire

* update bolt11 package and use built in method to calculate the invoice expiry date

* fix linter errors
2024-05-16 13:48:58 +01:00
b84161c49d [chore] Update legend to demo (#2505)
* fix: update `legend.lnbits.com` to `demo.lnbits.com`
* Update docs/guide/admin_ui.md
* fix: docker image name
* fix: donation links

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-05-16 10:05:22 +02:00
dni ⚡andGitHub 63ce506d29 fix: add cln unspecified error code bolt11 error to errorcodes (#2503)
{'code': -32602, 'message': 'Invalid bolt11: Prefix bc is not for regtest'}```
2024-05-16 09:59:54 +02:00
Vlad StanandGitHub 5114bd4a47 fix: link to demo server 2024-05-15 11:57:24 +03:00
dni ⚡andGitHub 05a244d8fd fix: refresh payments on payment success (#2502)
* fix: refresh payments on payment
2024-05-14 19:22:14 +03:00
dni ⚡andGitHub 019995078c fix: init wallet balance (#2501) 2024-05-14 18:56:57 +03:00
Vlad StanandGitHub f37cb6481c fix: copy invoice (#2500)
* fix: copy invoice
* chore: add mixins
* chore: make bundle
2024-05-14 17:55:45 +02:00
dni ⚡andGitHub 365f9a3923 fix: mobileSimple in paymentlist (#2498) 2024-05-14 13:57:04 +02:00
dni ⚡andGitHub bb4dd4fe35 fix: payment list currency (#2496)
* fix: payment list currency
2024-05-14 13:49:00 +02:00
Vlad StanandGitHub d1ae531750 fix: add methods back (#2495) 2024-05-14 13:48:33 +02:00
8ee2948f71 fix: payment list updates (#2493)
* fix: payment list updates

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-05-14 13:06:44 +02:00
ArcandGitHub ab3fe79a7e typo: fix opensats badge (#2494) 2024-05-14 12:35:39 +02:00
dni ⚡andGitHub 32596758cc fix: show proper total balances fix cleanups (#2490)
payments are not deleted oif we delete a wallets, so to get a accurate
total representation of the lnbits balance we need to create the
balances view based on the wallets table, not payments, else deleted
balances will still show up.

2nd, delete_unused_wallets and delete_accounts was never working if
because they never got an updated_at time, so i just check if its null
else i check to timedelta on created_at
2024-05-13 18:01:53 +01:00
dni ⚡andGitHub a5623ef7c3 feat: add payments table to user manager (#2491)
* feat: add payments table to user manager

refactor payments table and payment chart into components and add them
to usermanager

* bundle
2024-05-13 18:01:01 +01:00
9933484558 refactor: get_balance_delta and use pydantic model for openapi docs (#2492)
* refactor: `get_balance_delta` and use pydantic model for openapi docs

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-05-13 17:59:29 +02:00
dni ⚡andGitHub 78fc28558c refactor: catch payment and invoice error at faspi exceptionhandler level (#2484)
refactor exceptionhandlers into `exception.py` also now always throw
payment error when pay_invoice and invoice errors when create_invoice.

return a status flag with the detailed error message. with a 520
response
2024-05-13 17:58:48 +02:00
dni ⚡andGitHub 1e752dc3d2 test: services create and pay invoice (#2452)
* test: services create and pay invoice
* add more tests
* check with fundingsource
* check status
2024-05-13 16:55:38 +02:00
dni ⚡andGitHub 6730c6ed67 refactor: fix duplicate keychecker (#2339)
* refactor: fix duplicate keychecker

- refactor KeyChecker to be more approachable
- only 1 sql query needed even if you use `get_key_type`
- rename `WalletType` to `KeyType` wallet type was misleading

fix test

sorting

* fixup!

* revert 404
2024-05-13 15:26:25 +01:00
Vlad StanandGitHub 9f8942a921 chore: sort funding sources (#2489) 2024-05-10 17:45:51 +02:00
dni ⚡andGitHub 33c68065d5 fix: usermanager visible for everyone (#2488)
* fix: usermanager sidemenu
was missed by merge
2024-05-10 13:24:51 +02:00
9ca14f200d feat: usermanager (#2139)
* feat: usermanager

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-05-10 12:06:46 +02:00
eae5002b69 fix: pay invoice status (#2481)
* fix: rest `pay_invoice` pending instead of failed
* fix: rpc `pay_invoice` pending instead of failed
* fix: return "failed" value for payment
* fix: handle failed status for LNbits funding source
* chore: `phoenixd` todo
* test: fix condition
* fix: wait for payment status to be updated
* fix: fail payment when explicit status provided

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-05-10 11:49:50 +02:00
dni ⚡andGitHub b9e62bfceb refactor: move logger function from app.py to utils/logger.py (#2454)
* refactor: move logger function from `app.py` to `utils/logger.py`

just some simply refactoring to clean up app.py

* while true
2024-05-09 17:51:18 +01:00
dni ⚡andGitHub f60122c64a feat: update docker image to debian 12 (bookwork) (#2486)
postgres client install was failing for bullseye, i think its time
2024-05-09 18:26:45 +02:00
dni ⚡andGitHub c030ccc4f8 fix: no need to create fake admin (#2485)
already is done inside regtest
2024-05-09 16:56:11 +02:00
0076a85fdb fix: typo in phoenixd wallet (#2473)
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-05-03 18:49:09 +02:00
c3d37a460c Improved customisable homepage and added badge (#2474)
* Improved customisable homepage and added badge

* Added filled to styling of drop down

* format

* Wrong model

* lint hack

* Update .env.example

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>

* reverted

* Spelling

* More explicit

* format

* Added if for badge

* spellling

* Fix for None

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-04-30 08:08:57 +01:00
c04c13b2f8 feat: phoenixd wallet integration (#2362)
* phoenixd integration
---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-04-26 10:18:38 +02:00
dni ⚡andGitHub 4b4bed59cd feat: corelightning maxfee and custom pay command (#2464)
* feat: corelightning `maxfee` and custom pay command
we should use maxfee instead of calculating the ratio and pass it
through.
also make it possible to run a custom pay command
* change for cln rest aswell
2024-04-25 12:54:00 +02:00
dni ⚡andGitHub 4a0fb59461 chore: use prettier for all of the codebase (#2466)
* chore: use prettier for all of the codebase
we only checked `lnbits` dir before
2024-04-25 11:13:08 +02:00
Pavol RusnakandGitHub f5293ca645 fix: compatibility with Python 3.12 (#2463)
by updating grpc a uvloop dependencies to latest
2024-04-24 08:39:41 +02:00
Vlad StanandGitHub b2ff2d8cee [test] add tests for lnbits funding source (#2460) 2024-04-24 09:31:23 +03:00
Vlad StanandGitHub 8d3b156738 [test] add tests for eclair funding source (#2459)
* fix: test data

* test: add `status` tests

* refactor: try-catch

* test: create invoice tests

* test: add first `payinvoice` test

* test: add pay_invoice error test

* feat: allow more test options

* test: add pending tests

* fix: make check

* test: add, pending no fee

* fix: make consistent with other API calls

* test: more assertions

* test: add pending

* test: first payment status test

* test: pending status

* refactor: remove duplicate code

* refactor: rename field

* chore: code format

* chore: uniform
2024-04-23 16:18:52 +01:00
dni ⚡andPavol Rusnak 00f39a2007 test: add unit for fee_reserve and service_fee
:)

sorting
2024-04-22 11:38:24 +02:00
Vlad StanandGitHub 4ac30116a9 feat: add settings.lnbits_running (#2450)
* feat: add `settings.lnbits_runing `
2024-04-22 12:33:53 +03:00
dni ⚡andGitHub e91096c535 feat: remove magic argument parser from lnbits command (#2448)
got the idea from: https://github.com/lnbits/lnbits/issues/2447
arguments it should be explicity allowed with `click` and a description
should be added like here.
2024-04-19 13:23:56 +02:00
dni ⚡andGitHub e607ab7a3e test: restructure tests (#2444)
unit, api, wallets
* only run test-api for migration
2024-04-19 13:22:06 +02:00
Vlad StanandGitHub 67fdb77339 test: unit tests for lndrpc (#2442) 2024-04-19 13:21:21 +02:00
Pavol Rusnak 4f118c5f98 chore: make bundle 2024-04-18 15:44:17 +02:00
Pavol Rusnak 33ace85f7d i18n: refresh translations using AI script 2024-04-18 15:44:17 +02:00
Pavol Rusnak 33c50100ab fix: small update to i18n-ai-tool system prompt 2024-04-18 15:44:17 +02:00
782cbfc77f fix: balances view on use non deleted wallets (#2385)
* fix: balances view on use non deleted wallets

closes #2224

* fixup! fix: balances view on use non deleted wallets

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-04-18 11:49:40 +01:00
dni ⚡andGitHub bbfc301440 fix: broken lnurl_callback (#2445)
* fix: broken lnurl_callback
2024-04-18 12:16:00 +02:00
dni ⚡andGitHub 98ec59df96 feat: adhere to ruff's B rules (#2423)
* feat: adhere to ruff's `B` rules
last of the ruff checks.
closes #2308
* B904
* B008
* B005
* B025
* cleanup on fake
2024-04-17 13:11:51 +02:00
dni ⚡andGitHub e13a37c193 FEAT: add PYPI python package release workflow on tag (#1628)
* add pypi worflow, pyproject types, package metadata, ignore python package build, docker build fails if poetry uses readme for python package

* add pypi to release
2024-04-17 10:53:57 +02:00
dni ⚡andGitHub 0c3aabf77a feat: catch_everything_and_restart print name of the task (#2417)
remove type from `Coroutine` from the create_tasks
2024-04-17 10:51:07 +02:00
d9880c4de8 Clarified top-up success msg (#2381)
* Update admin_api.py
Common misconception is that the top up related to the funds on the funding source. 
Success msg  extended with info that correlated funds on funding source are needed and the amount is virtual until fitting.

* chore: code format
* feat: customise top-up message
* refactor: move the `Quasar.Notify` to `components.js`
* refactor: use `this.$q.notify` instead of `Quasar.Notify.create`

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-04-17 09:55:57 +02:00
ArcandGitHub 0a4eb78ef0 Fixes ad issue on homepage + readme badges (#2422)
Fixes broken ad layout on homepage
* Removed telegram link
2024-04-17 08:54:48 +02:00
dni ⚡andGitHub daa1b5a313 chore: adhere to ruff's RUF rules, 2nd try (#2420)
* chore: adhere to ruff's `RUF` rules, 2nd try
closes #2382
2024-04-17 07:36:22 +02:00
ArcandGitHub d78f6a1f9e Added extra default ads to support project (#2421) 2024-04-16 16:26:04 +02:00
dni ⚡andGitHub 839fe8b96d feat: add PEP561 marker for types (#2415)
tasks.py:8: error: Skipping analyzing "lnbits.tasks": module is
installed, but missing library stubs or py.typed marker
[import-untyped]

https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
2024-04-16 16:16:45 +02:00
25661ddff5 chore: remove lnurl wallet and withdraw feature (#2293)
* chore: remove lnurl wallet and withdraw feature
this feature is undocumented and the code is very outdated. i don't think it is worth to keep.
looking at the `/lnurlwallet` endpoint for example, it creates a new user and wallet without even checking if the creation of users is allowed
* remove lnurl callback

---------

Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2024-04-16 14:10:32 +02:00
dni ⚡andGitHub 55eb3be5d5 feat: make workflow reuseable for external repo (#2419)
* feat: make workflow reuseable for external repo

* fixup!
2024-04-16 10:43:53 +01:00
dni ⚡andGitHub 0714570242 fix: ruff linting broke (#2418)
fix linting
2024-04-16 08:34:33 +02:00
69ce0e565b [test] create unit-test framework for RPC wallets (#2396)
---------

Co-authored-by: dni  <office@dnilabs.com>
2024-04-15 17:24:28 +02:00
dni ⚡andPavol Rusnak b145bff566 chore: adhere to ruff's UP
basically use `list` and `type` instead of `List` and `Type`

this is save to use for python3.9 and has been deprecated. also has some
performance drawbacks.
read more here: https://docs.astral.sh/ruff/rules/non-pep585-annotation/
2024-04-15 13:38:04 +02:00
dni ⚡andGitHub a158056b99 chore: enable migration tests again (#2414)
PR has been merged. https://github.com/lnbits/lnbits-extensions/pull/300
2024-04-15 11:12:03 +02:00
Vlad StanandGitHub e8479941c8 fix: check installed extensions, not available ones (#2413) 2024-04-15 10:21:15 +03:00
6d5ad9e229 chore: adhere to ruff's "N" rules (#2377)
* chore: adhere to ruff's "N" rules

WARN: reinstall failing extensions!

bunch of more consistent variable naming. inspired by this issue.
https://github.com/lnbits/lnbits/issues/2308

* fixup! chore: adhere to ruff's "N" rules
* rename to funding_source
* skip jmeter

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-04-15 09:02:21 +02:00
dni ⚡andGitHub 055426ab53 chore: prepare version update to 0.12.6 (#2407)
needed in preparation of https://github.com/lnbits/lnbits/pull/2377
2024-04-12 17:29:08 +02:00
dni ⚡andGitHub 4bafe97167 fix: fastapi status import (#2408)
should use `from http import HTTPStatus`
2024-04-12 15:56:54 +02:00
dni ⚡andGitHub 8aef6cd416 chore: update prettier (#2405)
update to new prettier version with some new formatting
2024-04-12 09:00:31 +02:00
adb8f9bdec feat: add funding_source_max_retries env setting (#2404)
* feat: add `funding_source_max_retries` env setting

* feat: default to zero retries

* feat: exponential retry time increase

* chore: Let's use the same value as the default

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* feat: using 0.25 leads to less awkward numbers

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-04-11 17:29:25 +01:00
dni ⚡andGitHub e9e69d9d17 chore: update lnbits to 0.12.5 (#2401)
update!!!
2024-04-11 14:20:25 +02:00
dni ⚡andGitHub 2c5ccc0b1b fix: check_fundingsource retries (#2400)
should fix SaaS crashing when retrying
2024-04-11 14:19:09 +02:00
Vlad StanandGitHub ea58b51619 test: add tests for alby (#2390)
* test: initial commit

* chore: code format

* fix: comment out bad `status.pending` (to be fixed in core)

* fix: 404 tests

* test: extract first `create_invoice` test

* chore: reminder

* add: error test

* chore: experiment

* feat: adapt parsing

* refactor: data structure

* fix: some tests

* fix: make response uniform

* fix: test data

* chore: clean-up

* fix: uniform responses

* fix: user agent

* fix: user agent

* fix: user-agent again

* test: add `with error` test

* feat: customize test name

* fix: better exception handling for `status`

* fix: add `try-catch` for `raise_for_status`

* test: with no mocks

* chore: clean-up generalized tests

* chore: code format

* chore: code format

* chore: remove extracted tests

* test: add `create_invoice`: error test

* add: test for `create_invoice` with http 404

* test: extract `test_pay_invoice_ok`

* test: extract `test_pay_invoice_error_response`

* test: extract `test_pay_invoice_http_404`

* test: add "missing data"

* test: add `bad-json`

* test: add `no mocks` for `create_invoice`

* test: add `no mocks` for `pay_invoice`

* test: add `bad json` tests

* chore: re-order tests

* test: add `missing data` test for `pay_imvoice`

* chore: re-order tests

* test: add `success` test for `get_invoice_status `

* feat: update test structure

* test: new status

* test: add more test

* chore: code clean-up

* test: add success test for `get_payment_status `

* test: add `pending` tests for `check_payment_status`

* chore: remove extracted tests

* test: add more tests

* test: add `no mocks` test

* fix: funding source loading

* refactor: start to extract data model

* chore: final clean-up

* chore: rename file

* test: add tests for alby

* refactor: `KeyError` handling

* chore: log error

* chore: skip the negative fee test

* fix: error message fetching
2024-04-08 13:26:00 +03:00
Vlad StanandGitHub bfda0b62da test: add unit tests for wallets (funding sources) (#2363)
* test: initial commit

* feat: allow external label for `create_invoice` (useful for testing)

* chore: code format

* fix: ignore temp coverage files

* feat: add properties to the Status classes for a better readability

* fix: add extra validation for data

* fix: comment out bad `status.pending` (to be fixed in core)

* fix: 404 tests

* test: first draft of generic rest wallet tests

* test: migrate two more tests

* feat: add response type

* feat: test exceptions

* test: extract first `create_invoice` test

* chore: reminder

* add: error test

* chore: code format

* chore: experiment

* feat: adapt parsing

* refactor: data structure

* fix: some tests

* refactor: extract methods

* fix: make response uniform

* fix: test data

* chore: clean-up

* fix: uniform responses

* fix: user agent

* fix: user agent

* fix: user-agent again

* test: add `with error` test

* feat: customize test name

* fix: better exception handling for `status`

* fix: add `try-catch` for `raise_for_status`

* test: with no mocks

* chore: clean-up generalized tests

* chore: code format

* chore: code format

* chore: remove extracted tests

* test: add `create_invoice`: error test

* add: test for `create_invoice` with http 404

* test: extract `test_pay_invoice_ok`

* test: extract `test_pay_invoice_error_response`

* test: extract `test_pay_invoice_http_404`

* test: add "missing data"

* test: add `bad-json`

* test: add `no mocks` for `create_invoice`

* test: add `no mocks` for `pay_invoice`

* test: add `bad json` tests

* chore: re-order tests

* fix: response type

* test: add `missing data` test for `pay_imvoice`

* chore: re-order tests

* test: add `success` test for `get_invoice_status `

* feat: update test structure

* test: new status

* test: add more test

* fix: error handling

* chore: code clean-up

* test: add success test for `get_payment_status `

* test: add `pending` tests for `check_payment_status`

* chore: remove extracted tests

* test: add more tests

* test: add `no mocks` test

* fix: funding source loading

* refactor: extract `rest_wallet_fixtures_from_json` function

* chore: update comment

* feat: cover `cleanup` call also

* chore: code format

* refactor: start to extract data model

* refactor: extract mock class

* fix: typings

* refactor: improve typings

* chore: add some documentation

* chore: final clean-up

* chore: rename file

* chore: `poetry add --dev pytest_httpserver` (after rebase)
2024-04-08 12:18:21 +03:00
dni ⚡andGitHub b0a8e0d942 doc: add hint to configure reverse proxy in .env.template (#2393)
closes #2134

suggestion from issue
2024-04-05 15:55:26 +03:00
dni ⚡andPavol Rusnak 2161b2511c bundle 2024-04-05 11:00:45 +02:00
dni ⚡andPavol Rusnak 1b2a9c54b6 feat: active state for manage menu items
closes #2190
2024-04-05 11:00:45 +02:00
dni ⚡andGitHub 209e2c5bff test: make nice pytest reports on github (#2376)
* test: make nice pytest reports on github
utilizing this action: https://github.com/pavelzw/pytest-action/
2024-04-05 08:35:51 +02:00
820882db28 refactor: use new fastapi lifespan instead of startup/shutdown events (#2294)
* refactor: use new fastapi lifespan instead of events
recommended use: https://fastapi.tiangolo.com/advanced/events/?h=lifespan
threw warnings in pytest
* make startup and shutdown functions
* nix: add override for asgi-lifespan

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-04-05 07:05:26 +02:00
dni ⚡andPavol Rusnak d64239f1ad fix: remove trailing slash from admin.js
fixing 307 requests making a request to `/settings/`
2024-04-04 08:55:18 +02:00
dni ⚡andPavol Rusnak 2066fff994 docs: improve installation for none ubuntu
closes #1878
2024-04-04 00:23:21 +02:00
dni ⚡andGitHub e0b7d2f739 chore: adhere to ruff's C (#2379) 2024-04-03 17:56:05 +02:00
dni ⚡andPavol Rusnak cd66b7d70c chore: fix pycryptodomex security issue
https://github.com/lnbits/lnbits/security/dependabot/41
2024-04-03 17:43:28 +02:00
dni ⚡andPavol Rusnak dc74f5816f chore: update black, security warning
https://github.com/lnbits/lnbits/security/dependabot/52
2024-04-03 11:39:40 +02:00
dni ⚡andPavol Rusnak c67fcf0e45 chore: adhere to ruff's A
dont use pythjons builts in functions name as variable
2024-04-02 17:11:06 +02:00
redistayandGitHub 073b117528 chore: fix some typos for docs (#2384)
Signed-off-by: redistay <wujunjing@outlook.com>
2024-04-02 16:08:31 +02:00
dni ⚡andGitHub ef6bc4c3ae chore: typo in function name nofiy_upgrade (#2383)
small typo in ext manager
2024-04-02 08:38:12 +02:00
ArcandGitHub 468121a453 docs: improves readme (#2367)
* Improves readme
* Added light/dark logo
2024-03-29 15:29:37 +01:00
dni ⚡andGitHub 4679d321de test: remove warnings for TemplateResponse (#2368)
* test: remove warnings for `TemplateResponse`
removes warnings
```
tests/core/views/test_generic.py::test_core_views_generic
tests/core/views/test_generic.py::test_get_wallet_with_user_and_wallet
tests/core/views/test_generic.py::test_get_extensions
tests/core/views/test_public_api.py::test_core_views_generic
  /home/dni/.cache/pypoetry/virtualenvs/lnbits-XeqO4Z-j-py3.10/lib/python3.10/site-packages/starlette/templating.py:178: DeprecationWarning: The `name` is not the first para
meter anymore. The first parameter should be the `Request` instance.
  Replace `TemplateResponse(name, {"request": request})` by `TemplateResponse(request, name)`.
    warnings.warn(
```
2024-03-29 12:22:14 +01:00
dni ⚡andGitHub c9af15b320 test: run jmeter test from lnbits-extensions repo (#2374)
* test: run jmeter test from `lnbits-extensions` repo
moves the integration tests for extensions into the `lnbits-extensions` repo.
more info here: https://github.com/lnbits/lnbits-extensions/pull/283
2024-03-29 11:18:49 +01:00
Vlad StanandGitHub 9b5bb1b07a [test] add jmeter tests to install and enable all vetted extensions (#2371)
* test: initial commit
* feat: allow excluding of certain releases
* test: activate and enable
* fix: title indexes
* chore: remove disabled element
* test: skip `cashu` too many DB errors
* chore: improve logs
* chore: test logs
* chore: logs
* fix: condition
* fix: condition harder
* test: disable some listeners
* fix: remove hanging element
* fix: multiple reports with: `-m 1, --max-count, stop reading a file after 1 matching line`
* chore: test that github actions still detect failures
* Revert "chore: test that github actions still detect failures"

This reverts commit a6769c5fd1.
2024-03-28 10:22:39 +01:00
aaa6573272 simplify description in i18n (#2356)
* simplify description in i18n
so we do not have to always edit description when a new funding source is added
* chore: make bundle

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-03-28 09:06:47 +01:00
dni ⚡andGitHub 741ecac78b feat: improve on api structure, add openapi tags (#2295)
this logically groups api endpoints and gioves them specific openapi tags. which makes them nice on the `/docs` endpoint and makes the `api.py` more approachable
* add wallets list endpoint
* remove trailing slashes from endpoints
* fixup topup url
* fix trailing slash on auth
* backwards compatibility
2024-03-28 08:59:28 +01:00
Tiago VasconcelosandPavol Rusnak 1dd096213e v-text missing 2024-03-27 17:41:08 +01:00
dni ⚡andGitHub ed44244291 bug: show extensions in frontend had wrong boolean (#2370)
bugfix
2024-03-27 12:00:50 +01:00
dni ⚡andGitHub 0b22c41d55 ci: run jmeter only if linting passes to save resources (#2366)
also uses `prepare` action to initialize python env
2024-03-26 20:27:12 +01:00
dni ⚡andGitHub 25c6ee40f7 bug: releasing docker image had invalid credentials (#2365)
* bug: releasing docker image had invalid credentials

closes #2345

also removed the duplicate dockerhub upload for latest. that was kinda dump (i introduced it), because you could also manually tag a special release which would than always push to latest tag aswell which makes no sense.
2024-03-26 20:26:31 +01:00
dni ⚡andGitHub cdc102af07 chore: update to node 20.x on workflows (#2364)
gets rid of deprecating warnings in actions
* checkout@v4
* cache@v4
* setup-python@v5
* setup-node@v4
* codecov-action@v4
* codecov pass token from ci
2024-03-26 14:18:58 +01:00
dni ⚡andGitHub b9f0af0e79 feat: complete use of LNBITS_EXTENSIONS_DEACTIVATE_ALL (#2341)
- add to admin ui
- move to `ExtensionsSettings`
- hide left navbar when disabled
- do not register extension routes on startup
- do not check for installed extensions on startup
- reafctor into register_all_ext_routes
2024-03-26 11:44:33 +01:00
Gonçalo ValérioandGitHub 86821f4606 bug: When "expiry" is null, do not display "expiry" in payment details (#2349)
* Do not display expiry in payment details, when expiry is null
2024-03-26 10:46:30 +01:00
dni ⚡andGitHub 0ef9c4db10 hotfix: initial currency conversion on balance (#2346)
* hotfix: initial currency conversion on balance
fiat amount not shown in balance on initial pageload, lets reintroduce the first fetch_balance to fix it.
maybe in the future balance call and wallet itself should give the current fiat value if the wallet uses fiat tracking
2024-03-26 10:41:36 +01:00
Vlad Stan c101b85054 chore: add extra log to check-payments command 2024-03-25 13:14:35 +02:00
Vlad StanandGitHub fe3b00292a feat: extra log (#2360) 2024-03-25 12:58:49 +02:00
Vlad StanandGitHub 10fe113099 feat: remove autofix (#2359) 2024-03-25 11:22:16 +01:00
Vlad Stan 1bf5f10f53 fix: exclude internal invoices from being settled 2024-03-25 11:35:21 +02:00
Vlad StanandGitHub e2bbcaabbd feat: add mark-payment-pending command (#2355)
* feat: add `mark-payment-pending` command; add `--auto-fix` to `check-payments`

* chore: code format

* chore: code clean-up
2024-03-24 23:46:52 +02:00
Vlad StanandGitHub 4265915201 feat: add commands delete-walle, delete-wallet-payment and verbose option, (#2354)
* feat: add `verbose` option

* feat: add `database_delete_wallet `

* feat: add `database_delete_wallet_payment`
2024-03-24 21:51:59 +02:00
Vlad StanandGitHub 7e3c511027 feat: add check_invalid_payments command (#2353)
* feat: add `check_invalid_payments` command

* fix: str options to int
2024-03-24 19:11:30 +02:00
ArcandGitHub 7783f34998 bug: wrong pending status (#2352) 2024-03-24 13:58:15 +01:00
dni ⚡ 7dcb2fcdd5 chore: release 0.12.4 2024-03-24 13:44:47 +01:00
dni ⚡andGitHub 033a57cad4 chore: update to 0.12.3 (#2344) 2024-03-22 13:03:00 +01:00
dni ⚡andGitHub 299228b7b5 feat: reduce initial requests on wallet page (#2335)
* feat: reduce initial request on wallet page
- refactor allowed_currencies into function to use in generic and api.
- remove currencies request in frontend move it to generic
- dont request balance on first payments fetch
2024-03-22 12:59:49 +01:00
dni ⚡andGitHub 5b022e2ef3 feat: make route hints configurable for lndrest (#2304)
* feat: make route hints configurable for lndrest
boolean if route hints for private channels will be included from `LndRest` inside bolt11 invoice.
2024-03-22 12:45:39 +01:00
Pavol RusnakandGitHub c8818f5774 remove CACHE_VERSION from service-worker.js (#2342)
use server_startup_time as the version
(the same trick we use for static files)
2024-03-22 12:33:42 +01:00
dni ⚡andGitHub 1398857688 refactor: move registering of tasks into app.py (#2290)
* refactor: move registering of tasks into `app.py`

let the app have the responsibility to create the tasks not a helper function inside tasks.py.
makes it way easier to follow on how those background tasks are ran.
2024-03-22 12:29:05 +01:00
Gonçalo ValérioandGitHub 2a1505bc0d bug: Fix regression. Admin-users and allowed-users can now be removed through the web interface (#2331)
* Fix regression. Admin-users and allowed-users can now be removed through the web interface
* remove raw usage from user tab on server admin
2024-03-22 07:26:25 +01:00
dni ⚡andGitHub 4bba6ecc82 hotfix: qrcode broke wallet.html (#2340)
introduced by 43a79744
2024-03-21 17:39:12 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
522c4df44c chore(deps): bump follow-redirects from 1.15.5 to 1.15.6 (#2326)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 16:43:48 +01:00
dni ⚡andGitHub c03b81d2ea refactor: tiny url to use require decorators and wallet.id (#2338)
- also use `wallet.wallet.id` as key instead of `wallet.wallet.inkey`
2024-03-21 13:32:55 +01:00
arbadacarbaandPavol Rusnak 43a797444a recover user for export-to-phone 2024-03-21 13:14:28 +01:00
dni ⚡andPavol Rusnak d0cb961b49 bug: wallet page fetchbalance ran twice
there is a listener on payments which executes fetchbalance automatically if payments change. line 822
2024-03-21 13:13:33 +01:00
ArcandGitHub d208e68885 feat: added home button on error page (#2330)
* Added redirect on error page if user id not present as users keep getting stuck
2024-03-21 12:24:38 +01:00
dni ⚡andGitHub 8dcb53aea0 chore: keychecker todo remove type ignores (#2337)
fixes a comment `TODO`
2024-03-21 11:36:01 +01:00
dni ⚡andGitHub 241b286e21 chore: update ruff and black in precommit config (#2336)
ran `poetry lock --no-update`
2024-03-21 11:05:01 +01:00
dni ⚡andGitHub 17839f6a25 feat: add database debug env var (#2333)
* feat: add database debug env var
makes it visible what db actions happen
2024-03-21 09:59:17 +01:00
dni ⚡andGitHub e4d3faefa0 feat: create_unique_task helper (#2292)
this is especially useful for extension.

with this an extension can only call `create_permanent_unique_task("my-ext", task)` and it will restarted when it exists and cancels when lnbits is stopped
2024-03-18 13:02:04 +00:00
116ca7f011 Remove all {% raw %} usage (#2318)
* Remove raw from templates 
* Remove v-text from q-tooltip
* extension buttons
* Move more to spans
* Reformatted

---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
2024-03-15 11:10:52 +01:00
Vlad StanandGitHub d44339b018 fix: pending property for PaymentStatus (#2324)
* fix: pending property for PaymentStatus

* fix: invoice status

* fix: check pending status from the payment details

* refactor: make condition more explicit
2024-03-14 14:38:10 +02:00
Vlad StanandGitHub 352fd23c0b refactor: dev-friendly payment status name (#2322)
* refactor: dev-friendly payment status name
* refactor: change `PaymentStatus(True, ...)` to `PaymentSuccessStatus(...)`
2024-03-13 16:17:33 +01:00
Vlad StanandGitHub 65b8868c36 fix: enforce order of payments (#2313)
* fix: enforce order of payments

* fix: do not return wallet by key if the wallet is deleted
2024-03-12 15:31:40 +02:00
dni ⚡andGitHub 54dec171f9 fix: raise failed request to not run mark_webhook_sent (#2289)
* fix: webhook sent raise a failed request

is not raised even failed webhook would be marked is sent

* add warn log

* fix error

* improve exceptions

* fixup!

* ConnectError is already captured by RequestError

https://www.python-httpx.org/exceptions/

* log exception
2024-03-12 15:12:24 +02:00
Vlad StanandGitHub 5b4398911a fix: check if key present in settings before updating (#2306) 2024-03-12 13:03:11 +00:00
4c0bd132b1 bug: cln paymentstate check on error (#2151)
* CLN: paymentstate check on error

---------

Co-authored-by: dni  <office@dnilabs.com>
2024-03-12 13:56:36 +01:00
7ce4eddb0e feat: add group_by to fetch_page (#2140)
---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-03-12 13:55:38 +01:00
callebtcandGitHub 14519135d8 Fix: LNbits backend payent state (#2187) 2024-03-12 13:43:41 +01:00
dni ⚡andGitHub 60839481ad feat: add tool preimage generator (#2321)
`poetry run python tools/preimage.py`
2024-03-12 11:46:46 +01:00
dni ⚡andGitHub 3ef1941fc0 test: fix TestModel warning (#2317)
PytestCollectionWarning: cannot collect test class 'TestModel' because it has a __init__ constructor (from: tests/core/test_helpers_query.py)
2024-03-12 11:32:10 +01:00
dni ⚡andGitHub 16cb1a8026 fix: lnbits funding source (#2319)
fix a bug where payments are deleted
2024-03-12 11:28:42 +01:00
dni ⚡andGitHub e3b9bd6a70 fix: update pre-commit ruff (#2316)
wasnt working after latest ruff update
2024-03-12 08:47:13 +01:00
Vlad StanandGitHub a1ea04acf8 Jmeter take 3 (#2284)
* chore: add tests

* chore: add reference to jmter job

* chore: add jmeter job

* chore: touch tests

* chorE: disable other workflows

* chore: do not set `LNBITS_ADMIN_UI: true`

* chore: extra log file

* chore: rollback

* chore: testing stuff

* feat: top-up wallet

* chore: clean-up usr

* chore: clean-up user query param

* chore: remove hardcoded usr

* chore: clean-up

* chore: clean-up

* chore: different improvements

* chore: clean-up

* chore: remove hardcoded values

* refactor: extract `init-server` fragment

* chore: move fragment

* feat: use test frgment

* chore: clean-up hardcoded values

* chore: clean-up

* fix: get lnurl working

* chore: re-arange elements

* chore: re-arange elements

* fix: add `lnurlp, withdraw` extensions

* chore: rename file

* fix: login with user id

* refactor: extract init account

* fix: tips wallet

* fix: assertion

* refactor: use fragments; group loops

* chore: re-enable the other CI jobs

* fix: separate wallet for receive

* feat: check receive wallet balance

* feat: increase wait time
2024-03-08 16:40:23 +02:00
3e341a3555 update black to 24.2.0 and ruff to 0.3.0 (#2307)
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2024-03-07 09:55:37 +00:00
Vlad StanandGitHub cb5c9b03bf Fix login logout on safari (#2309)
* fix: relax security level for public cookies

* fix: corner case for logout
2024-03-07 09:22:42 +00:00
dni ⚡andGitHub 884a1b9d6f test: lint tests and tools aswell (#2296)
* test: lint `tests` and `tools` aswell

more linting :)

* fix linting issues in tests and tools

* fixup!

* how is this working?
2024-02-27 13:30:52 +00:00
Vlad StanandGitHub e8aa498683 feat: add get endpoint for user wallets (#2299)
* feat: add get endpoint for user wallets

* feat: only expose relevant fields

* refactor: extract `BaseWallet`

* doc: add open api doc
2024-02-27 10:08:10 +02:00
Pavol Rusnak 1b7efd854a chore: make bundle 2024-02-23 16:11:27 -03:00
Pavol Rusnak 8b32c3dcb6 remove webhook_listener
following commit e419c74ebb
2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak 0821b28eac rename self.auth to headers 2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak 4c71d5ac42 format file 2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak a8e0b5a5ac use bolt11.decoder from lnbits instead of zbd remote 2024-02-23 16:11:27 -03:00
BitkarrotandPavol Rusnak 9b9014a042 Update zbd.py
set timeout on pay_invoice to 40
2024-02-23 16:11:27 -03:00
Pavol Rusnak 96b42b1784 add ZBD to homepage 2024-02-23 16:11:27 -03:00
BloggingBitcoinandPavol Rusnak 26d854ebe8 added ZBD logos 2024-02-23 16:11:27 -03:00
Pavol Rusnak ed36d01b40 fix typing issues 2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak 646556a457 add zbd to translation files 2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak d20a35eddc add zbd to settings
update api methods

add zbd as funding source to settings and js

fix statuses

cast str into int for status method

fix outbound payment hash issue

restore create_invoice

remove print stmts
2024-02-23 16:11:27 -03:00
bitkarrotandPavol Rusnak d69946db8a initial scaffolding for zbd wallet 2024-02-23 16:11:27 -03:00
dni ⚡andPavol Rusnak ddab4075d2 fix: raise proper exception in services pay_invoice
also values where checked twice
2024-02-23 00:39:00 -03:00
d6c8ad1d0d Paid extensions (#2229)
* fix: download archive file `async`

* feat: add `pay_link` property

* feat: basic install using internal wallet for payment

* fix: pop-up issues

* chore: refactor

* feat: detect paid extensions

* fix: payment check

* feat: small stuff

* feat: show external invoice

* fix: regression for extension install

* feat: store previos successful payments

* refactor: simplify, almost works

* chore: gugu gaga

* fix: pay and install

* fix: do not pay invoice on the back-end

* chore: code clean-up

* feat: basic websocker listener

* feat: use websocket to watch for invoice payment

* feat: remember hanging invoices

* refactor: extract `localStorage` methods

* chore: code format

* chore: code clean-up after test

* feat: remember previous payment_hashes

* chore: code format

* refactor: rename `ExtensionPaymentInfo` to `ReleasePaymentInfo`

* refactor: method rename

* fix: release version matters now

* chore: code format

* refactor: method rename

* refactor: extract method `_restore_payment_info`

* refactor: extract method

* chore: rollback `CACHE_VERSION`

* chore: code format

* feat: i18n

* chore: update bundle

* refactor: public method name

* chore: code format

* fix: websocket connection

* Update installation.md (#2259)

* Update installation.md (#2260)

* fix: try to fix `openapi` error

* chore: bundle

* chore:bundle

---------

Co-authored-by: benarc <ben@arc.wales>
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2024-02-22 13:16:41 +00:00
dni ⚡andPavol Rusnak 54c6faa4b6 refactor: move mark_webhook_sent into crud.py
database functions should be in crud
2024-02-21 11:14:04 -03:00
dni ⚡andPavol Rusnak c88f05c5c0 refactor: tasks should be run with internal wrapper
listeners used did not use the helper function
2024-02-21 10:56:31 -03:00
dni ⚡andPavol Rusnak e419c74ebb refactor: remove unused webhook handler for wallets
it was only used in lnpay, opennode and alby which all had it disabled anayways with a pretty old TODO, so i guess the feature is disabled for a y ear now and nobody used it, so i just removed it
2024-02-21 10:55:57 -03:00
dni ⚡andGitHub b6dc66b070 chore: remove dead code send_push_promise (#2285)
dead code removal :)
2024-02-21 14:24:06 +02:00
dni ⚡andGitHub 7b4d9bf5db chore: update to 0.12.2 (#2282)
update!!!
2024-02-21 11:09:43 +01:00
Vlad StanandGitHub c51e7351e8 fix: extension stop background work (#2281)
* feat: add helper methods

* fix: do not try to stop background work on first install

* fix: first stop via function call then try REST API

* fix: `make check`

* fix: prepare for `{ext_id}_stop`
2024-02-21 12:08:37 +02:00
fe12eccc42 Updated new wallet warning modal text to clarify importance of backing up login creds (#2273)
* Updated new wallet warning modal text to clarify importance of backing up creds


Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-02-20 14:36:39 +02:00
dni ⚡andGitHub 17b7826753 feat: unique invoice listeners for extensions (#2250)
changes the `register_invoice_listener` to use a non unique key so you its not possible to have a listeners running twice and throw a warning when trying to.
2024-02-20 14:03:29 +02:00
14ae6d8b1a feat: add node url to api keys & docs (#2283)
* feat: add node url to api keys & docs

closes #2277

* add finnish to tools/i18n-ai-tool.py

* regenerate localization with i18n-ai-tool

* chore: make bundle

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-02-20 13:32:49 +02:00
dni ⚡andGitHub 7b559991c7 fix: docker image invoice listener were not stopping (#2278)
because it could not resolve localhost
2024-02-16 15:24:50 +02:00
dni ⚡andGitHub decd4cdf0a chore: remove listener dict (#2279) 2024-02-16 14:47:52 +02:00
dni ⚡andGitHub dc7a6551c5 chore: deprecate get_current_extension_name (#2280) 2024-02-16 13:48:50 +02:00
526467747e Add Keycloak SSO (#2272)
* feat: add `keycloak` SSO

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-02-14 09:23:37 +01:00
Vlad StanandGitHub b8d295a5b7 refactor: generalize SSO auth (#2263)
* refactor: first extraction of providers
* refactor: remove unused property
* refactor: extract `_find_auth_provider_class`
* fix: return type
* feat: prepare for `keycloak`
2024-02-14 07:57:50 +01:00
jackstar12andGitHub 293b6267be fix: correct invoice expiry for lndrest (#2271) 2024-02-13 15:14:09 +02:00
Vlad StanandGitHub 20e70854a1 fix: add installed extensions to the ext list if missing (#2261) 2024-02-12 15:33:28 +01:00
dni ⚡andGitHub 11173e6460 fix: github release missing checkout (#2264)
actions/checkout was missing so it could not release
2024-02-12 13:00:03 +01:00
dni ⚡andGitHub 069a7922fc chore: update to 0.12.1 (#2262) 2024-02-12 12:43:20 +01:00
98338ffb00 Improves payment reactions (#2251)
* updated to select

* updating

* working

* bundle

* updated to select

* updating

* working

* bundle

* Make room for everything apart from sending

* Update lnbits/static/js/base.js

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>

* prettier

* bundled

* fundle

* changed to window[]

* vlads suggestion

* added stars

* vlads suggestion

* fundle

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-02-12 10:48:07 +00:00
Pavol Rusnak 62dec118af chore: make bundle 2024-02-09 12:36:07 -03:00
tlindiandPavol Rusnak 84b406534d Update fi.js 2024-02-09 12:36:07 -03:00
d9d2d59b73 Wallet limits: max balance, daily max withdraw, transactions per sec (#2223)
Co-authored-by: benarc <ben@arc.wales>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-02-09 17:25:53 +02:00
20d4b954c0 fix: custom site desc (#2254)
* custom site desc

* fiddled formatting

---------

Co-authored-by: arcbtc <ben@arc.wales>
2024-02-09 15:08:02 +00:00
dni ⚡andGitHub 5edb845d8f feat: add setting lnbits_allow_new_accounts to admin ui (#2252)
added to users tab of admin ui
2024-02-09 14:54:30 +02:00
cfe2b43d2a Improved env layout (#2226)
* Improved env layout

* Update .env.example

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-02-09 14:34:10 +02:00
Vlad StanandGitHub a0ac41a9ce fix: use get_dbversions (#2255) 2024-02-09 14:10:51 +02:00
dni ⚡andGitHub 194a527736 fix: regtest use new docker hub address (#2248)
* fix: regtest use new docker hub address
2024-02-09 08:46:33 +01:00
dni ⚡andGitHub 6f135c0696 feat: add monitor admin endpoint for listeners (#2242)
this can be useful when debugging invoice listeners. in the future there coud be some monitor dashboard on the admin ui.
2024-02-09 08:08:37 +01:00
dni ⚡andGitHub 9a767b627f chore: dead code perform_balance_check (#2244)
this code is not used anywhere on core nor on extensions
2024-02-08 15:01:23 +01:00
c1c4eda69d Adds optional confetti to incoming payments in wallet (#2231)
* initial

* Working with toggle

* Prettier and bundle

* removed console.log

* bundle

* fix: i18n

* refactor: simplify logic

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-02-08 13:34:03 +00:00
dni ⚡andGitHub 6c46867698 chore: update readme to use new docker alias (#2246)
`docker pull lnbits/lnbits`
2024-02-08 13:24:55 +00:00
dni ⚡andGitHub a73eb569ae bug: docker upload workflow (#2245)
something changed in the docker-build-push action.

added `workflow_dispatch` to be able to manually run those docker releases via github
2024-02-08 11:30:38 +01:00
ArcandGitHub f07b40735e Fixes annoying qr popup on parsing LNURL withdraw in wallet (#2230) 2024-02-07 12:08:49 +00:00
Pavol Rusnak 94f74d3f56 chore: make bundle 2024-02-06 14:45:13 -03:00
tlindiandPavol Rusnak d9a5e63e51 fixes to Finnish translation 2024-02-06 14:45:13 -03:00
dni ⚡andGitHub c8e5c23ac2 chore: update to latest fastapi (#2240)
security alert https://github.com/lnbits/lnbits/security/dependabot/46
2024-02-06 11:47:36 -03:00
Vlad StanandGitHub 59968e38d1 fix: check for username before allowing password change (#2239) 2024-02-06 11:48:13 +02:00
Pavol RusnakandGitHub 4f45781319 alby: fix balance reporting and error handling + opennode: fix error handling (#2221)
* opennode: fix error handling

* alby: fix balance reporting and error handling
2024-02-06 10:26:50 +02:00
Vlad StanandGitHub 22ea83e843 Fix extension navigation (#2236)
* fix: avoid `path` clash with other routes (`/withdraw` in this case)

* fix: refresh user state after extension action

* chore: fix `mypy`
2024-02-05 11:39:35 +00:00
dni ⚡andGitHub 55fed49be1 feat: use github release draft (#2237)
create a draft release on tagging
2024-02-05 12:01:11 +01:00
Pavol Rusnak 311d9a668b chore: make bundle 2024-02-02 17:25:13 -03:00
tlindiandPavol Rusnak 165f5093f4 add Finnish translation 2024-02-02 17:25:13 -03:00
43d92ddb51 [FEAT] release / docker ci pipeline (#1907)
* [FEAT] docker ci pipeline

* add release workflow

* depend on release

* move tag

* remove push to main

* fixup release workflow

* add latest tag on dockerhub

* cleanup

* Update .github/workflows/docker.yml

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>

* use latest versions of actions

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-01-30 13:28:34 +00:00
dni ⚡andGitHub 10944bf100 refactor: add prepareFilterQuery to utils (#2219)
* refactor: add `prepareFilterQuery` to utils
factored out the preparation of the url params for a paginated table and its requests.
usermanager will also use that.
2024-01-30 08:05:39 +01:00
dni ⚡andGitHub ce9b2c3b77 refactor: move admin ui tabs around (#2218)
move authentication into security in preparation of user manager pr where users tab will be removed.
move service fee into server tab, i think its more fitting there.
2024-01-30 07:49:19 +01:00
dni ⚡andGitHub 4c102a08e8 refactor: topup wallet + into component (#2217)
* refactor: topup wallet `+` into component
this will also be used in the usermanager
* bundle
2024-01-30 07:48:59 +01:00
1d2b939e06 feat: cleanup-wallets cli command (#2176)
* feat: `cleanup-wallet` cli command
`lnbits-cli db cleanup-wallets` removes all wallets that never had an transaction.
this helps against spammers creating random wallets, eventually slowing down the db.
* add commands
* add to example env
* db_versions was used in app
* add delta as cli argument
* use days unit
* simplify cli argument name (cleanup_wallet_days -> days)!

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-01-30 07:47:15 +01:00
e1bb2113ed feat: install wizard on first launch (#1977)
* Login form loading

* add first install middleware and settings

* updates

* Login form loading

* add first install middleware and settings

* updates

* only set first install when superuser is created

* refactor first install

* only show if first install

* cleanup

* set password

* update calls

* login superuser on first install

* fix

* fixup!

* fixup!

* fixup!

* fixup!

* fixup!

* last fixup!

* fix mypy and prettier CI errors

* disable first install

* add random super user

* set first install after startup

* remove user id from form

* Update lnbits/core/views/auth_api.py

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>

* Update lnbits/core/views/auth_api.py

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>

* Update lnbits/middleware.py

Co-authored-by: dni  <office@dnilabs.com>

* addressing Vlad's comments

* remove super user

* move to transient settings

* fix: show `first_install` page even after a server restart

* fix: do not add `user_id` in the auth token

* fix: `make check` errors

* fix: `username` is not optional for `UpdateSuperuserPassword`

* feat: nicer error message

---------

Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-01-25 13:33:40 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b71113700a chore(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#2203)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 08:17:12 +01:00
mrbppandGitHub ebfcc46c9e feat: improve PWA configuration (#2214)
---
PWA Errors:
---
- fix logo size
- add shortcut icon
- add maskable icons
- add desktop and phone screenshots (wide and narrow)
2024-01-23 08:14:37 +01:00
Pavol Rusnak c047ae6808 chore: make bundle 2024-01-22 10:23:14 -06:00
dni ⚡andPavol Rusnak 023a1a088e feat: add currency amount to lnurl/lnaddress payments
closes #2135
2024-01-22 10:23:14 -06:00
Pavol Rusnak 8eabf53642 introduce Wallet.normalize_endpoint to remove code duplication 2024-01-22 10:06:22 -06:00
Pavol Rusnak 824a8fa7c8 refactor __init__ functions of Wallets (funding sources)
- better error messages when a conf variable is not provided
- unify approaches among different source files
2024-01-22 10:06:22 -06:00
Pavol RusnakandGitHub a3b7c76523 sort CSV export of payments (#2208) 2024-01-22 08:03:31 -06:00
Vlad StanandGitHub 26ca8c71d7 Deactivate all extensions flag (#2206)
* feat: allow all extension deactivation

* doc: updated comment

* fix: make sure `register_routes` executes after installed extensions are checked

* chore: code format

* fix: do not run migration on deactivated extensions

* fix: make sure the deactivated extension list is loaded in time

* feat: register extension routes if extension never loaded before

* fix: move `load_disabled_extension_list`

* doc: disable by default
2024-01-22 12:18:12 +02:00
Vlad StanandGitHub 0d2447faf3 refactor: extract encrypt_internal_message and decrypt_internal_message (#2210) 2024-01-16 15:33:11 +00:00
Vlad StanandGitHub 4cea06c5a5 Move UI configs to profile (#2201)
* feat: add tabs

* feat: move buttons to user profile

* feat: i18n

* refactor: move ui methods to `account` component
2024-01-15 11:51:34 +02:00
Vlad StanandGitHub 031ce14857 refactor: extract AESCipher to crypto.py (#2202) 2024-01-15 11:51:15 +02:00
bd143f5c14 added directions for cache (#2197)
---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2024-01-10 20:09:08 -05:00
dni ⚡andGitHub 4e9c992a91 revert: migration (#2198)
took to long on legend to run, now taking current date as default
2024-01-09 11:34:53 +00:00
fusion44andPavol Rusnak d6ac3d2672 fix: remove custom devshells from the flake
The definition of the dev shell in the flake file had several
issues when opening a development shell. However, this is
unnecessary as mkPoetryApplication already creates a
default working dev shell.
2023-12-28 13:25:34 +01:00
dni ⚡andPavol Rusnak 1933d896cb chore: update to version 0.12.0 2023-12-23 12:15:03 +01:00
Pavol Rusnak 168a3bcddb chore: make bundle 2023-12-22 20:10:30 +01:00
Pavol Rusnak adc342ab9a reword API docs to API keys and API docs in the wallet accordeon 2023-12-22 20:10:30 +01:00
Pavol Rusnak bbf3cd96f1 move API docs as last item of wallet actions 2023-12-22 20:10:30 +01:00
e8c9304519 Simplified mobile wallet (#2175)
---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-12-22 17:32:01 +01:00
815c3e61e4 feat: add created_at and updated_at to wallets and accounts (#2177)
* feat: add `created_at` and `updated_at` to wallets and accounts

the title says it all :)

* fixup!

* nitpicks :)

* fixup!

* sqlite fix

* sqlite compat

* fixup!

* mypy

* revert db py

* motorinas suggestions

* int(time()) proper default values in migration

* uncomment migration

* use now = int(time()) idiom to make code more readable

also this fixes the issue where time() is called multiple times
providing different return values for multiple invocations

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-12-21 12:37:56 +00:00
Vlad StanandGitHub 4e55ea18e5 feat: remember last active wallet (#2189)
* feat: store last active wallet as a cookie

* fix: param check
2023-12-21 14:19:58 +02:00
Vlad StanandGitHub 81b70acf5f fix: position of method invocation (#2188)
* fix: move call to `checkUsrInUrl` outside the `if` branch

* chore: bundle update
2023-12-20 13:16:35 +00:00
callebtcandGitHub 165d7499ea LndResWallet: catch http errors (#2133) 2023-12-19 15:33:18 +00:00
Kalle RosenbaumandGitHub abbcfbeb6a cln: allow unpaid amountless invoices (#2138) 2023-12-19 12:12:52 +01:00
Pavol RusnakandGitHub bff44b7097 update nix ci action to use cachix to cache results (#2170) 2023-12-19 11:54:14 +01:00
Pavol RusnakandGitHub 95f6ae2bb6 chore: new translations (#2183)
* new translations
* chore: make bundle
2023-12-19 11:22:37 +01:00
Pavol RusnakandGitHub a15af87f9b fix make checkbundle which should not bump the serviceworker version (#2184) 2023-12-19 11:22:05 +01:00
97f8a4ab38 fix: urls for includes on print.html (#2185)
* fix the url for js libs
* fix the url for css

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-12-18 09:30:38 +01:00
HaosGamesandGitHub 7b9d370452 fix nixos module getExe warning (#2182) 2023-12-15 07:43:43 +01:00
Vlad StanandGitHub db72953310 Login polish (#2180)
* feat: set `samesite="none", secure=True` for `is_lnbits_user_authorized` (required in iframe)
* feat: add icons for User ID actions
* feat: set `samesite="none", secure=True
2023-12-14 15:07:22 +01:00
Vlad StanandGitHub bb918a8523 Login improve UI config (#2171)
* feat: show auth configs on the admin UI
* fix: do not access settings on load
* fix: redirect for click on item (not only on text)
* fix: remove `Display Name`
* fix: do not show `Verify email with` if no auth option is available
* feat: show warning before logout
* feat: i18n of account page
* fix: show account icon for user ID login
* fix: always check `isUserAuthorized`
* fix: update the `disclaimer_dialog` message
* feat: hide user ID by default
* fix: redirect from login page when user authorized
* feat: update logout message
* fix: do not translate company names
2023-12-14 11:34:23 +01:00
Tiago VasconcelosandGitHub 24b02cc656 fix not showing account for existing users (#2173) 2023-12-12 17:25:35 +00:00
Pavol RusnakandGitHub 9877ca602f use _blank for external links on main (#2172) 2023-12-12 15:08:30 +01:00
c9093715b7 [FEAT] Auth, Login, OAuth, create account with username and password #1653 (#2092)
no more superuser url!
delete cookie on logout
add usr login feature
fix node management
* Cleaned up login form
* CreateUser
* information leak
* cleaner parsing usr from url
* rename decorators
* login secret
* fix: add back `superuser` command
* chore: remove `fastapi_login`
* fix: extract `token` from cookie
* chore: prepare to extract user
* feat: check user
* chore: code clean-up
* feat: happy flow working
* fix: usr only login
* fix: user already logged in
* feat: check user in URL
* fix: verify password at DB level
* fix: do not show `Login` controls if user already logged in
* fix: separate login endpoints
* fix: remove `usr` param
* chore: update error message
* refactor: register method
* feat: logout
* chore: move comments
* fix: remove user auth check from API
* fix: user check unnecessary
* fix: redirect after logout
* chore: remove garbage files
* refactor: simplify constructor call
* fix: hide user icon if not authorized
* refactor: rename auth env vars
* chore: code clean-up
* fix: add types for `python-jose`
* fix: add types for `passlib`
* fix: return type
* feat: set default value for `auth_secret_key` to hash of super user
* fix: default value
* feat: rework login page
* feat: ui polishing
* feat: google auth
* feat: add google auth
* chore: remove `authlib` dependency
* refactor: extract `_handle_sso_login` method
* refactor: convert methods to `properties`
* refactor: rename: `user_api` to `auth_api`
* feat: store user info from SSO
* chore: re-arange the buttons
* feat: conditional rendering of login options
* feat: correctly render buttons
* fix: re-add `Claim Bitcoin` from the main page
* fix: create wallet must send new user
* fix:  no `username-password` auth method
* refactor: rename auth method
* fix: do not force API level UUID4 validation
* feat: add validation for username
* feat: add account page
* feat: update account
* feat: add `has_password` for user
* fix: email not editable
* feat: validate email for existing account
* fix: register check
* feat: reset password
* chore: code clean-up
* feat: handle token expired
* fix: only redirect if `text/html`
* refactor: remove `OAuth2PasswordRequestForm`
* chore: remove `python-multipart` dependency
* fix: handle no headers for exception
* feat: add back button on error screen
* feat: show user profile image
* fix: check account creation permissions
* fix: auth for internal api call
* chore: add some docs
* chore: code clean-up
* fix: rebase stuff
* fix: default value types
* refactor: customize error messages
* fix: move types libs to dev dependencies
* doc: specify the `Authorization callback URL`
* fix: pass missing superuser id in node ui test
* fix: keep usr param on wallet redirect
removing usr param causes an issue if the browser doesnt yet have an access token.
* fix: do not redirect if `wal` query param not present
* fix: add nativeBuildInputs and buildInputs overrides to flake.nix
* bump fastapi-sso to 0.9.0 which fixes some security issues
* refactor: move the `lnbits_admin_extensions` to decorators
* chore: bring package config from `dev`
* chore: re-add dependencies
* chore: re-add cev dependencies
* chore: re-add mypy ignores
* feat: i18n
* refactor: move admin ext check to decorator (fix after rebase)
* fix: label mapping
* fix: re-fetch user after first wallet was created
* fix: unlikely case that `user` is not found
* refactor translations (move '*' to code)
* reorganize deps in pyproject.toml, add comment
* update flake.lock and simplify flake.nix after upstreaming
overrides for fastapi-sso, types-passlib, types-pyasn1, types-python-jose
were upstreamed in https://github.com/nix-community/poetry2nix/pull/1463
* fix: more relaxed email verification (by @prusnak)
* fix: remove `\b` (boundaries) since we re using `fullmatch`
* chore: `make bundle`

---------

Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Arc <ben@arc.wales>
Co-authored-by: jackstar12 <jkranawetter05@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-12-12 11:38:19 +01:00
Pavol RusnakandGitHub e76ba62b46 add vars related to service fee to .env.example (#2143) 2023-12-12 11:17:36 +01:00
Pavol RusnakandGitHub 2d00f4c41c add checkbundle to make check (#2169) 2023-12-12 11:13:58 +01:00
Pavol RusnakandGitHub 3ad18ca365 chore: update to version 0.11.3 (#2168) 2023-12-12 11:13:43 +01:00
Pavol RusnakandGitHub cf4d575062 add i18n checker and autogenerated AI translations (#2160)
* use translated string

* fix typos in lnbits/static/i18n/{it,jp,nl,we}.js

* add missing strings to cs,en,sk translations

* remove duplicates from lnbits/static/i18n/{cs,en,kr,sk}.js

* add i18n checker

* add i18n ai tool

* add autogenerated AI translations

* add i18n-ai-tool check whether variables in formatted strings are not broken

* fix issues with variables found by the script

* chore: make bundle
2023-12-12 10:10:51 +00:00
saunterandGitHub 9f835f8350 swap Alby logos (#2148) 2023-12-11 20:40:57 +01:00
297c42371d feat: handle intents (#2161)
if lightning/lnurl param is present in /wallet, it will pass to the decode dialogue

* chore: code format
* fix: call to `decodeRequest`
* feat: set `position="top"` for all dialogs (avoid mobile keyboard overlap)

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2023-12-11 20:40:22 +01:00
dni ⚡andGitHub 8352223704 fix: lnurl should follow redirect (#2167)
`r.raise_for_status` raises an proper exception now
2023-12-11 11:57:36 +01:00
Pavol Rusnak f9c9c52d75 use _blank + noopener + noreferrer consistently 2023-12-11 10:51:21 +01:00
Haos GamesandPavol Rusnak 7704029e59 add option to pass additional variables 2023-12-11 08:33:16 +01:00
Tiago VasconcelosandGitHub a83fb13335 add fiat balance to wallet (#2157)
also fixed payments table fiat denomination for consistency
2023-12-06 17:54:58 +01:00
Pavol Rusnak b8e6e80bdf fix: nix build 2023-12-06 12:55:01 +01:00
dni ⚡andPavol Rusnak ba90bc5fea feat: lnurl library update
follow up on https://github.com/lnbits/lnbits/pull/1864/files
2023-12-06 12:55:01 +01:00
dni ⚡andPavol Rusnak d7257af7a0 bundle 2023-12-06 11:56:26 +01:00
dni ⚡andPavol Rusnak f36771db9d vlad nitpick 2023-12-06 11:56:26 +01:00
dni ⚡andPavol Rusnak aa68d67b5e feat: add password to <lnbits-dynamic-fields> 2023-12-06 11:56:26 +01:00
dni ⚡andPavol Rusnak 5fc472a8d9 refactor: move webpush to it own router
refactor to make `views/api.py` cleaner
2023-12-06 11:13:24 +01:00
dni ⚡andGitHub fa49133bd7 refactor: move tinyurl to its own router (#2154)
small little refactor getting to a cleaner `views/api.py`
2023-12-06 10:54:40 +01:00
47bd96246f chore: update lock file and poetry 1.7.0 in CI (#2156)
* chore: update lock file and poetry 1.7.0 in CI
* mypy found an issue! :)

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-12-06 10:36:10 +01:00
dni ⚡andGitHub cae39bc19f fix: branch out delete payment (#2152) 2023-12-05 12:40:45 +01:00
callebtcandGitHub 4839cdf2f8 fix: fee reserve for internal (#2150) 2023-12-05 10:31:53 +00:00
Tiago VasconcelosandPavol Rusnak 08571368c0 restore ad space title 2023-12-02 23:15:42 +01:00
dni ⚡andGitHub 9fcf566540 fix: time margin for deleting failed payments (#2147)
this pr adds a 15 minutes margin before deleting failed outgoing payments.
2023-12-02 00:41:09 +01:00
Pavol Rusnak 5f36800100 use awk to bump CACHE_VERSION (drop sed usage) 2023-12-01 10:27:41 +01:00
e46b12d267 fix issue with displaying languages (#2130)
* fix issue with displaying languages

* readd tooltip

* remove all raw stuff

* fix api docs tooltip

* fix remaining {% raw %} + $t() usage

---------

Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-11-30 12:55:46 +00:00
Pavol RusnakandGitHub 62b0e3fe89 set User-Agent when accessing external resources (#2100)
* set User-Agent when accessing external resources

* refactor User-Agent into settings.user_agent
2023-11-30 12:54:07 +00:00
Pavol RusnakandGitHub 992e3bfb9a optimize PNG images in lnbits/static/images for size (#2131)
* optimize PNG images in lnbits/static/images for size

* remove lnbits/static/extension.png which is not used anywhere

* add tools/optipng.sh
2023-11-30 12:50:19 +00:00
BitkarrotandGitHub 43b8c744e6 add Alby logo to front index page (#2128) 2023-11-28 12:01:46 +01:00
d0107f7830 ci: workflow to check bundle files (#2126)
fail if `bundle.min.css` or `bundle.min.js` differes from a `make bundle` output

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-11-28 10:39:21 +01:00
b66396857b feat: add libwally for liquid support (#2117)
* feat: add libwally for liquid support
this is needed for liquid support in the new boltz extension, sadly i could not get it to work with embit library, liquid support is not quite ready there and now documentation or tests available
* fix: prefer wheels for wallycore on nix
* feat: make wallycore dep optional, introduce "liquid" poetry extra
for Liquid support install deps via `poetry install -E liquid`

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-11-27 10:45:16 +01:00
9c85e1156c feat: manage extensions navbar (#2121)
* feat: manage extensions navbar
create now `Manage` spacer in sidebar. and add manage extensions to it
closes #2088
* do not show when no exts
* update cn and jp i18n strings
* rename lnbits-admin-ui component to lnbits-manage, fix its behaviour
* i18n: add Korean language

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: psychoet <125599543+psychoet@users.noreply.github.com>
2023-11-27 10:40:36 +01:00
Vlad StanandGitHub 3b0024bcf2 fix: redirect for all paths of the extension (not only for /api) (#2120)
* fix: redirect for all paths of the extension (not only for `/api`)
* refactor: rename variable
* fix: corner case for root path
* refactor: rename var
* doc: update comment
* fix: do not redirect for static resources
2023-11-24 21:13:19 +01:00
Sebastian HagensandGitHub 4c4f0922a4 change path to super_user file located in the data directory (#2115) 2023-11-22 17:30:30 +01:00
Pavol RusnakandGitHub 8e63756c61 docs: small cleanup (#2107) 2023-11-21 22:30:52 +01:00
callebtcandGitHub 0eb74c86ba CLN: return payment failure if no payment is found (#2113)
* CLN: return payment failure if no payment is found
2023-11-21 12:54:55 +01:00
ArcandGitHub e65ec56276 Organised env (#2110)
* Shuffled env around

Env has become bigger, and is a bit disorientating for users

* removed whitespace

* shuffle

* optimised for smaller screen

* Made ADMIN UI stuff more prominent
2023-11-21 11:26:53 +00:00
Tiago VasconcelosandGitHub 90c54f2c30 pass custom extra on payment out (#2089) 2023-11-21 12:20:08 +01:00
6a27b91fcb Add service fee to specific wallet (#2050)
* add service fee to specific wallet

* add to .env.example

* Added service fee wallet to manage server

* cleaned

* prettier

* Added badge for service fee

* Added tooltip

* Added service fee max

* allow ignoring service fee for internal transactions

* add fee_reserve_total helper funciton that includes service_fee

* html for admin ui

* typo

* Update .env.example

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* fix .env.template comment

* bundle

* WIP: expose fee reserve endpoint

---------

Co-authored-by: Arc <ben@arc.wales>
Co-authored-by: dni  <office@dnilabs.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-11-21 11:11:21 +00:00
callebtcandGitHub 4dcf26bcb3 Update to c-lightning-REST v0.10.5 (#2109)
* Update to c-lightning-REST v0.10.5
* update invoice stream
2023-11-18 12:14:29 +01:00
d5ae1e3d6a feat: add getalby wallet as funding source (#2086)
* initial scaffolding, methods for getalby wallet
* Add getAlby to docs and i18n
* update alby wallet methods
* change names from GetAlbyWallet to AlbyWallet
* remove unused variables in AlbyFundingSource in settings.py
* rename getalby.py to alby.py
* fix method auth and status
* resolve rolznz commented issues
* rename ALBY_API_KEY to ALBY_ACCESS_TOKEN
* fix desc hash in create_invoice

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: dni  <office@dnilabs.com>
2023-11-14 20:28:25 +01:00
dni ⚡andGitHub 6f0d911a08 fix: dont release docker image for release candidate (#2101)
- do not release docker image on `0.11.2-rc1`
- only release on `0.11.x` or `v0.11.x`
- also remove invalid version `0.0.0.0`
2023-11-14 18:59:21 +01:00
callebtcandPavol Rusnak 3e91200d3f FakeWallet: provide payment preimage 2023-11-13 15:42:57 +01:00
fiatjafandPavol Rusnak f989eba2c9 Api -> API 2023-11-13 15:42:57 +01:00
11d57e9d52 hotfix toolbar title (#2096)
fix button wrapping on toolbar

Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
2023-11-13 14:42:01 +00:00
Pavol Rusnak 62fa22098e change color theme icons 2023-11-13 15:41:11 +01:00
ArcandGitHub 4417d53562 Updated nix install guide (#2099)
* Updated nix install guide

* typo
2023-11-13 14:28:52 +00:00
Vlad StanandGitHub b380108a4f doc: add SUPER_USER field (#2097) 2023-11-13 14:10:53 +02:00
dni ⚡andPavol Rusnak 87a42087de bundle 2023-11-13 11:21:29 +01:00
dependabot[bot]andPavol Rusnak bee857c2af Bump axios from 1.3.4 to 1.6.0
Bumps [axios](https://github.com/axios/axios) from 1.3.4 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.3.4...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 11:21:29 +01:00
Sébastien KrafftandPavol Rusnak 31782040c6 formatted code 2023-11-12 22:53:03 +01:00
Sébastien KrafftandPavol Rusnak 1195dd3eb3 Fix according to suggestion from prusnak
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-11-12 22:53:03 +01:00
Sébastien KrafftandPavol Rusnak a61d74deff Fixed payment isn't initiated on lndrest 2023-11-12 22:53:03 +01:00
Pavol RusnakandGitHub d1b89bc68a dotenv is not required directly, only indirectly (#2087)
via environs and pydantic (both already present in pyproject.toml)
2023-11-08 08:54:46 +01:00
dni ⚡andGitHub 5f8faf237c chore: remove boltz settings (#2019)
from `settings.py`
2023-11-08 08:53:53 +01:00
Pavol Rusnak 5b62f1c586 make example paths in .env.example less confusing 2023-11-07 18:25:01 +01:00
Pavol Rusnak 7306df3445 handle correctly the case with no active channels 2023-11-07 13:03:16 +01:00
Pavol Rusnak 10fb35b47f make bundle 2023-11-06 15:17:47 +01:00
Pavol Rusnak 0ffba99eaf i18n: add flags to languages 2023-11-06 15:17:47 +01:00
Pavol Rusnak 18ebe010bc i18n: add Czech (cs) and Slovak (sk) localization 2023-11-06 15:17:47 +01:00
Pavol RusnakandGitHub 4817a62a66 i18n: remove empty lines (#2079) 2023-11-06 10:54:44 +01:00
Pavol RusnakandGitHub 5473d9e940 add comment why CLightningWallet alias should be kept (#2065) 2023-11-06 10:20:26 +01:00
Pavol RusnakandGitHub 1ab68e80b3 use floor() instead of round() to convert msat to sat in frontend (#2070)
* use floor() instead of round() to convert msat to sat in frontend
2023-11-06 07:49:47 +01:00
dni ⚡andPavol Rusnak 3e142fab0f feat: logger success when wallet is connected
this should be a success log, see screenshot
2023-11-01 20:32:59 +01:00
460ceeba1d feat: add extension-settings vue component (#2004)
- adds lnbits-extension-settings-form. which is just the plain form of the settings component.
- adds lnbits-extension-settings-btn-dialog. which is the form wrapped into a dialog with a cog button.

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2023-10-30 08:09:53 +01:00
jackstar12andGitHub 14e678f661 Merge pull request #2068 from TrezorHannes/dev
Enable Node Management via .env without activating admin-UI
2023-10-28 22:07:51 +02:00
a2469abebd Enable Node Management via .env
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>
2023-10-28 18:53:55 +02:00
Pavol Rusnak 3b01af242c remove unnecessary str() in f-string 2023-10-27 14:05:16 +02:00
4d1c4f6348 [FEAT] add cache busting via static_url_for and settings.cache_version (#1964)
closes #1954
this PR add cache busting to `/static`
additionally i combined `lnbits/core/static` with `lnbits/static`, it was not necessary and added a lot of duplicate code for cache busting. now you have to include all static files inside the html files with `{{ static_url_for("static", "app.css" ) }}`

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-10-27 13:50:49 +02:00
Kalle RosenbaumandGitHub fed2d41139 CLN: Handle optional invoice fields (#2056) 2023-10-26 00:47:35 +02:00
c8147bd83f [FEAT] add extension functionality to lnbits-cli (#1963)
* [FEAT] add extension functionality to lnbits-cli

WIP

draft cli commands for vlad :)

* add extension list command

* [feat] lnbits-cli add install, uninstall and upgrade

* feat: load settings from DB

* refactor: simplify settings loading

* feat: show current version if installed

* feat: add mor emessages

* feat: basic DB install

* feat: add extension

* feat: do not install if the server is up

* feat: add logic for uninstall

* refactor: prepare for upgrade

* feat: check extension before upgrade

* refactor: stuff

* fix: have a default value

* feat: use the API logic

* feat: use pi methods for un-install

* refactor: extract _select_release

* feat: add flags

* feat: check if extension already up to date

* refactor: use `_run_async`

* feat: install all extensions

* feat: install online

* fix: api install

* fix: API upgrade & install

* feat: add API uninstall

* failed typo

* typo running

* url duplication

* [fix] provide short-options too (same as upgrade command)

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>

* make black

* fix: fail if .superuser file not found; add `--admin-user` option

* fix: ambiguous use of `logger.debug`

- register_new_ext_routes must not be None
- `logger.debug` was used because it allowed any arguments, but that was a bad idea
- now an explicit empty `_do_nothing(*_)` function is used

* fix: load settings

* doc: updated `--source-repo`

* chore: rename `upgrade` to `update`

* refactor: use `@annotation` for making commands async

* fix: code checks

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-10-25 14:03:00 +02:00
callebtcandGitHub 29e779d09d Merge pull request #2061 from lnbits/main
Sync main->dev
2023-10-25 10:03:58 +02:00
Pavol RusnakandGitHub 3a134672e9 Update docs/guide/installation.md (#2059) 2023-10-25 08:28:42 +02:00
callebtcandGitHub 6b52c42b2b Revert "Update installation.md (#2052)" (#2060)
This reverts commit ddd27fe426.
2023-10-24 12:39:07 +02:00
arbadacarbaandPavol Rusnak 811292529b Update api.md
Swappger link wrong
2023-10-23 17:18:09 +02:00
Vlad StanandGitHub 5262f56aaf fix: emit event for nested lnbits-dynamic-fields components (#2053)
* fix: emit event for nested components

* chore: update bundle
2023-10-20 14:14:10 +03:00
MaxBipalandGitHub ddd27fe426 Update installation.md (#2052)
fixed cli error
2023-10-20 13:13:42 +02:00
dni ⚡andPavol Rusnak c15beaa014 chore: bump version to 0.11.2
in preparation of the next release
2023-10-18 17:21:31 +02:00
Vlad StanandGitHub 1c38b129c0 [BUG] Installation of new extensions via LNBITS_EXTENSIONS_DEFAULT_INSTALL does not select the right version (#2046)
* fix: guard against bad versions (eg: `v0.2-ui-api-updates`)

* fix: use the latest release on auto-install
2023-10-18 14:11:58 +03:00
ArcandPavol Rusnak a411de1d11 Update installation.md (#1997) 2023-10-17 11:24:06 +02:00
e64810d96a fix: closed channels affecting the stats (#1998)
Co-authored-by: jackstar12 <jkranawetter05@gmail.com>
2023-10-17 11:23:57 +02:00
Vlad StanandGitHub d64e2f42f5 fix: explicitly specify the DB name when migrating (#2039) 2023-10-17 10:56:38 +02:00
dni ⚡andPavol Rusnak e522a53340 formatting 2023-10-16 15:49:56 +02:00
Pavol Rusnak c02e0618af add "nix build" to GitHub CI
run only when poetry/flake files change
2023-10-16 15:49:56 +02:00
Pavol Rusnak 1226d2e6d8 update poetry.lock 2023-10-16 15:49:25 +02:00
bitkarrotandPavol Rusnak 613c86f5e2 add python-crontab for scheduler extension 2023-10-16 15:49:25 +02:00
Pavol Rusnak 018d2fe800 run optipng -o 7 on PNG logos again 2023-10-16 14:53:56 +02:00
dni ⚡andPavol Rusnak 033c5f30aa optimize SVG
update PNG
2023-10-16 14:53:56 +02:00
Pavol Rusnak 647f53821b run optipng -o 7 on PNG logos to reduce their size 2023-10-16 14:53:56 +02:00
Pavol Rusnak 3bac759cab remove old logo from docs 2023-10-16 14:53:56 +02:00
dni ⚡andGitHub dbf687c47a feat: add useful query helpers for insert and update (#2020)
* feat: add useful query helpers for insert and update

i saw this bits of code all over the codebase, this `helpers.py`
tries to abstract that away and make it more readable.
2023-10-16 13:27:59 +02:00
dni ⚡andPavol Rusnak c8661ffcf3 feat: improve im workflows runs
only run push on main / dev
2023-10-16 11:39:40 +02:00
Pavol RusnakandGitHub 00c3b69c7a downgrade rpds-py to 0.10.3 which is not broken on nix (#2027) 2023-10-12 20:14:16 +02:00
Pavol RusnakandGitHub 6457f7b5e6 use PNG logo for QR code instead of SVG logo (#2028)
SVG causes rendering issues on some browsers unfortunately
2023-10-12 12:59:02 +02:00
4ec6a055d9 feat: add file logging (#2023)
logs into `lnbits.log` with info loglevel and `debug.log` with DEBUG loglevel
files will be saved into `LNBITS_DATA_DIR`.
retentions is configurable but defaults to `90 days`
* added log rotation aswell

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2023-10-12 11:24:05 +02:00
Vlad StanandPavol Rusnak 484e1ae78a chore: make bundle 2023-10-11 16:16:41 +02:00
Vlad StanandPavol Rusnak ba6408b944 feat: add lnbits-dynamic-fields 2023-10-11 16:16:41 +02:00
Pavol Rusnak dc7ce1e43c make bundle 2023-10-11 15:45:42 +02:00
Tiago VasconcelosandPavol Rusnak 371dacf8b2 make QR img bigger, adjust css 2023-10-11 15:45:42 +02:00
Tiago VasconcelosandPavol Rusnak ebaad7fe49 q-btn inside block 2023-10-11 15:45:42 +02:00
Pavol RusnakandGitHub ba0e431199 add possibility to turn off new registrations (#2017)
with LNBITS_ALLOW_NEW_ACCOUNTS=false

while keeping existing users functional (i.e. no allowlist for existing users)
2023-10-11 13:46:27 +02:00
b2384c10cc Refactor: Calculate invoice expiry outside of the crud (#1849)
* expiry before crud
* using Bolt11 as argument instead of payment_request
* fix missing expiry

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-10-10 12:03:24 +02:00
80529dee4b feat: improve on admin ui funding tab (#1989)
* feat: improve on admin ui funding tab
* now only shows settings of selected fundingsource.
* refactor into vue component
* use camel case
* refactor: move admin js into .js file (#1990)
* updateFundingData is redundent now

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2023-10-10 12:02:40 +02:00
c2a11fa0bb Add a customizable logo to QR (#1955)
* add QR_LOGO variable
* add logo to images folder
* css for overlaying logo on qr
* qr url on settings
* add qr url to global env
* add QR url input on theme tab in adminUI
* new component added
* use svg
* remove white border around logo in the QR

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: dni  <office@dnilabs.com>
2023-10-09 21:11:11 +02:00
dni ⚡andGitHub f286a91253 bug: check prettier workflows (#2021)
workflow wasnt failing on wrong formatting
2023-10-09 18:15:44 +02:00
jackstar12andGitHub a937e09f85 fix: make bolt11 optional in NodePayment (#2005) 2023-10-09 13:02:22 +02:00
jackstar12andGitHub 219838244b feat: fix filename in payments export (#1991) 2023-10-09 12:45:14 +02:00
dni ⚡andGitHub 54e0d1aa15 [CHORE] cleanup ip block middle workaround for older fastapi (#1981) 2023-10-09 07:57:13 +02:00
daywalker90andGitHub 829e1b8f05 remove comma after version (#2016) 2023-10-09 07:53:08 +02:00
dni ⚡andGitHub 4a14cb9f41 [CHORE] cleanup nodemanagement vue components (#1986)
lnbits-date component was a duplication.
moved all to the Vue.components into node.js
empty {% raw %} tags in public
2023-10-07 23:50:52 +02:00
Pavol RusnakandGitHub cb746056e0 dependency cleanup (#2011)
- async-timeout is not used anywhere (nor core neither extensions)
- cerberus is not used anywhere (nor core neither extensions)
- move embit and secp256k1 to end of the list
  - these are not used by core
  - document which extensions use them
2023-10-07 21:06:58 +02:00
Pavol RusnakandGitHub c1b3938d5d use current version of logo for manifest icon (#2010) 2023-10-07 10:38:37 +02:00
jackstar12andGitHub 6e5fbb92e8 fix: use correct field from response (old one got removed) (#1999) 2023-10-06 17:13:22 +02:00
dni ⚡andGitHub 9f847ffcad hotfix: add environs backwards compatibility (#2007)
for the time beeing its better to readd this package for backwards compatibility in LNURLp and cashu
2023-10-06 16:28:34 +02:00
dni ⚡andGitHub b51daedb50 update: to 0.11.1 (#1988) 2023-10-06 09:13:33 +02:00
Pavol RusnakandGitHub 4dfce8f0fa drop version from readme (#2001) 2023-10-06 08:59:02 +02:00
dni ⚡ bcdf1f401f revert: update 2023-10-04 14:00:07 +02:00
dni ⚡ a3816a90b6 update: to 0.11.1 2023-10-04 13:58:11 +02:00
dni ⚡andGitHub 33f4c6fde7 fix: admin_ui js error (#1982)
* fix: admin_ui js error

undefined formData.lnbits_allowed_currencies JS error in console

* vlad suggestion
2023-10-04 12:23:27 +02:00
callebtcandGitHub 36445732d2 catch exceptions in migrations for extensions (#1987) 2023-10-04 12:09:54 +02:00
c5192146af nix: fix nixosModule and build (#1979)
* lnbits/settings: get LNBITS_COMMIT from envvar if available

There is no other way of setting the git commit at runtime, for build systems like Nix that allow you to hermetically define envvars derived from the source that will be available reproducibly at runtime

* nix: update potree2nix and nixosModule

This is a general refactor that gets everything building and passing the vmTest again

* Update nix/modules/lnbits-service.nix

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
2023-10-03 09:50:35 +01:00
Vlad StanandGitHub 542273a8d9 feat: remove commit version (#1980) 2023-10-02 20:31:06 +01:00
52608c4ffc Add Markdown capability globally (#1965)
* add markdown capability globally

* add markdown to site description

* add showdown to package.json

remove it from vendor.json, its bundled

* formatting

* io

* Update lnbits/core/templates/core/index.html

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-09-27 11:32:03 +01:00
jackstar12andGitHub 5c5dd98af2 fix: catch sse exceptions (#1971) 2023-09-27 11:25:42 +02:00
Vlad StanandGitHub 2971b571bc [BUG] Fix bad transation call (#1973)
* fix: `:label="$('xxx')` to :label="$t('xxx')

* chore: commit bundles

* chore: remove extra file
2023-09-27 11:57:02 +03:00
dni ⚡andGitHub 69703b0a05 [BUG] bundle is broken (#1972) 2023-09-27 10:47:55 +02:00
jackstar12andGitHub 97cc3c5adc fix: node ranks (#1968) 2023-09-27 08:02:53 +02:00
Pavol RusnakandGitHub b13c470d54 fix typos (#1969) 2023-09-27 08:02:28 +02:00
d79a55b4c6 Don't create user if allowed users is set (#1822)
* dont allow creating of users when allowed users are set
* add info to .env.example

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-09-26 08:08:36 +02:00
dni ⚡andGitHub 5b16f54857 [FEAT] cleanup GET /wallet endpoint, add wallet api routes (#1932)
* [FEAT] cleanup GET /wallet endpoint, add wallet api route
this removes the functionalitiy to create accounts and wallets via
the GET /wallet endpoint in generic.py

it add endpoints inside the api.py for it and the frontend is modified to use the api endpoints

this also simplifies for the `feat/login` for the route.

* remove stale generic tests and add api tests

* bug wrong endpoint create account

* vlad nitpick

* added checkif deleted is 404

* reload after renaming wallet

* another iteration with vlad

* create new wallet if it none exist

* fix delete refresh

* formatting
2023-09-25 14:06:00 +01:00
eb73daffe9 [FEAT] Node Managment (#1895)
* [FEAT] Node Managment

feat: node dashboard channels and transactions
fix: update channel variables
better types
refactor ui
add onchain balances and backend_name
mock values for fake wallet
remove app tab
start implementing peers and channel management
peer and channel management
implement channel closing
add channel states, better errors
seperate payments and invoices on transactions tab
display total channel balance
feat: optional public page
feat: show node address
fix: port conversion
feat: details dialog on transactions
fix: peer info without alias
fix: rename channel balances
small improvements to channels tab
feat: pagination on transactions tab
test caching transactions
refactor: move WALLET into wallets module
fix: backwards compatibility
refactor: move get_node_class to nodes modules
post merge bundle fundle
feat: disconnect peer
feat: initial lnd support
only use filtered channels for total balance
adjust closing logic
add basic node tests
add setting for disabling transactions tab
revert unnecessary changes
add tests for invoices and payments
improve payment and invoice implementations
the previously used invoice fixture has a session scope, but a new invoice is required
tests and bug fixes for channels api
use query instead of body in channel delete
delete requests should generally not use a body
take node id through path instead of body for delete endpoint
add peer management tests
more tests for errors
improve error handling
rename id and pubkey to peer_id for consistency
remove dead code
fix http status codes
make cache keys safer
cache node public info
comments for node settings
rename node prop in frontend
adjust tests to new status codes
cln: use amount_msat instead of value for onchain balance
turn transactions tab off by default
enable transactions in tests
only allow super user to create or delete
fix prop name in admin navbar

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-25 15:04:44 +02:00
c536df0dae Support custom path for installed extensions (#1940)
* feat: the more you fuck around the more you learn
* feat: backwards compatible static file loading
* refactor: update paths for extension files
* refactor: var renaming
* doc: update `LNBITS_EXTENSIONS_PATH` documentation
* fix: default folder install
* feat: install ext without external path
* doc: `PYTHONPATH` no longer required
* fix: add warnings
* fix: missing path
* refactor: re-order statements
* fix: hardcoded path separator

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-09-25 12:44:29 +02:00
50561a8696 Startup optimization: nonblocking expiry check (#1943)
* nonblocking expiry check
* autoruff
* smaller interval

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-09-25 12:32:01 +02:00
blackcoffeexbtandGitHub 7a182244b9 Updated guide for extension development for current extension management process (#1962) 2023-09-25 11:19:30 +01:00
dni ⚡andGitHub 1646b087cf adding bolt11 lib and removing bolt11.py from the codebase (#1817)
* add latest bolt11 lib

decode exception handling for create_payment
fix json response for decode
bugfix hexing description hash
improvement on bolt11 lib
update to bolt11 2.0.1
fix clnrest
* bolt 2.0.4
* refactor core/crud.py

* catch bolt11 erxception clnrest
2023-09-25 12:06:54 +02:00
Pavol RusnakandGitHub bd1db0c919 add editorconfig config, fix issues (only whitespace changes) (#1958) 2023-09-25 11:06:25 +01:00
193c751670 [FEAT] Focus cursor to textarea (#1959)
* Focus cursor to textarea 

Fix need for additional click to textarea before pasting

* [FEAT] autofocus textarea on paste request

closes #1959

* format arba

* bundle

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-09-25 10:59:04 +01:00
a8082f798f [CHORE] update dependencies, unsafe pip packages, fastapi (#1609)
* d

* middleware needs to be initialised before startup

runs on python3.11

update secp256k1

update psycopg

* update fastapi to v0.103

* fix webpush

* bump dependencies

* ruff issue

* lock versions

* bump versions

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-25 10:54:02 +01:00
callebtcandGitHub d3d1010360 remove LNURL-p description_hash validation (#1951) 2023-09-20 12:09:48 +02:00
ArcandGitHub 3260acbcc7 Revert "do not show deleted wallets (#1942)" (#1947)
This reverts commit 1ee2d53069.
2023-09-18 19:41:35 +01:00
callebtcandGitHub b8922a7cd7 extension loader: do not panic! (#1945) 2023-09-18 19:29:10 +01:00
Tiago VasconcelosandGitHub 1ee2d53069 do not show deleted wallets (#1942)
* do not show deleted wallets

* rename parameter for clarity
2023-09-18 19:21:20 +01:00
dni ⚡andGitHub 6acbf823ae [CHORE] bugfix formatting prettier (#1936) 2023-09-12 20:05:43 +01:00
jackstar12andGitHub 4c16675b3b Fix payments chart (#1851)
* feat: payment history endpoint

* test payment history

* use new endpoint in frontend

* refactor tests
2023-09-12 13:38:30 +01:00
f526a93b6c make wallets name prettier! (#1918)
* make wallets name prettier!

* Update lnbits/core/templates/admin/index.html

Co-authored-by: dni  <office@dnilabs.com>

* Update lnbits/core/templates/admin/index.html

Co-authored-by: dni  <office@dnilabs.com>

---------

Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
2023-09-12 13:51:56 +02:00
fee40d7321 [REFACTOR] core/__init__ to not have circular import issues (#1876)
* F541 fix

remove unused workflow and combine linters into one

add lnbits/static to ruff ignore
remote setupnode

ignore upgrades for mypy

ignore F401 for __init__ files

unused noqa

ignore upgrades for black

F821: undefine name

disabled and logged webhook_listener for opennode and lnpay because they are obvisouly not working

E402: module level import not at top of file

fixup

revert breaking changes wait for PR #1876

https://github.com/lnbits/lnbits/pull/1876

E721 fixes, only popped up for python3.9 not 3.10

[REFACTOR] core/__init__ to not have circular import issues

WIP

add db for backwards compat

fix pyright

make mypy happy again

pyright did not catch those, i think mypy got confused with relative imports. maybe we should use absolute ones everywhere

E402: module level import not at top of file

dont forget to add core_app

rebase on ruff pr

f

remo

format

* fix clnrest

* ignore E402 in conftest

* refactoring issues

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-12 11:25:05 +01:00
f19d59d429 [BUG] self payments for the same wallet (#1914)
* [BUG] self payments for fakewallet

make it possible to pay to yourself on fakewallet

f

* bugfix selfpayments for fakewallet

* delete by wallet and fix previous_payment check (#1919)

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-09-12 11:23:23 +01:00
jackstar12andGitHub a97f298586 [TEST] use test data as mockdata (#1929)
* add db group to cli

* delete mock_data.zip

* generate migration data through tests
2023-09-12 11:21:05 +01:00
0b3d5bbe0e [CORE] Document CoreLightningRestWallet (#1920)
* document CoreLightningRestWallet
* pushed to comment
* fixed base64 comment

---------

Co-authored-by: Ben <ben@arc.wales>
2023-09-12 12:14:47 +02:00
jackstar12andGitHub c29e7a1759 [TEST] use clean db in postgres tests (#1928) 2023-09-12 11:02:01 +01:00
bda054415a [FEAT] improve update_admin_settings (#1903)
* [FEAT] improve update_admin_settings

while working on the push notification pr i found it very hard just to update
2 settings inside the db, so i improved upon update_admin_settings.
now you just need to provide a dict with key/values you want to update inside db.

also debugging the endpoints for update_settings i found despite the type of `EditableSettings`
fastapi did in fact pass a dict.

* t

* use `EditableSettings` as param in update_settings

* fix settings model validation

we previously overrode the pydantic validation with our own method

* make `LnbitsSettings` a `BaseModel` and only add `BaseSettings` later

this allows us to instantiate `EditableSettings` without the environment values being loaded in

* add test

* forbid extra fields in update api

* fixup

* add test

* test datadir

* move UpdateSettings

* fix compat

* fixup webpush

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-09-12 10:59:32 +01:00
jackstar12andGitHub 1b2db34e08 [FEAT] create data folder if it doesnt exist (#1930) 2023-09-12 11:56:21 +02:00
dni ⚡andGitHub 28e4612559 [FIX] codeql for dev branch (#1935) 2023-09-12 10:17:40 +01:00
dni ⚡andGitHub 301079d4e1 [CHORE] update to version 0.11.0 (#1933) 2023-09-12 10:16:04 +01:00
jackstar12andGitHub 6360f23495 [FIX] correct amount in fiat tracking (#1934)
* better debug log

* fix: convert msat to sat for fiat amount
2023-09-12 10:06:35 +01:00
fb98576431 [FEAT] Push notification integration into core (#1393)
* push notification integration into core

added missing component

fixed bell working on all pages
- made pubkey global template env var
- had to move `get_push_notification_pubkey` to `helpers.py` because of circular reference with `tasks.py`

formay

trying to fix mypy

added py-vapid to requirements

Trying to fix stub mypy issue

* removed key files

* webpush key pair is saved in db `webpush_settings`

* removed lnaddress extension changes

* support for multi user account subscriptions, subscriptions are stored user based

fixed syntax error

fixed syntax error

removed unused line

* fixed subscribed user storage with local storage, no get request required

* method is singular now

* cleanup unsubscribed or expired push subscriptions

fixed flake8 errors

fixed poetry errors

* updating to latest lnbits

formatting, rebase error

fix

* remove unused?

* revert

* relock

* remove

* do not create settings table use adminsettings

mypy

fix

* cleanup old code

* catch case when client tries to recreate existing webpush subscription e.g. on cleared local storage

* show notification bell on user related pages only

* use local storage with one key like array, some refactoring

* fixed crud import

* fixed too long line

* removed unused imports

* ruff

* make webpush editable

* fixed privkey encoding

* fix ruff

* fix migration

---------

Co-authored-by: schneimi <admin@schneimi.de>
Co-authored-by: schneimi <dev@schneimi.de>
Co-authored-by: dni  <office@dnilabs.com>
2023-09-11 14:48:49 +01:00
dni ⚡andGitHub 8f0c1f6a80 [REFACTOR] grpcio and protofbuf for lndgrpc is not optional (#1886)
also removed dead code def get_ssl_context

black
2023-09-11 14:24:37 +01:00
576e20d0cd Add deleted flag wallet (#1826)
* add deleted flag on wallets

set deleted on delete wallet

2 twelves

format

fail on create invoice

make deleted check on SQL query

nazi flake8

add_test

boom... it works and passes!!

* add app fixture

vlad's recommendations

add deleted

* Add deleted flag to Wallet

* restore crud

* do not check for wallet in services.py

* add deleted flag on wallets

set deleted on delete wallet

2 twelves

format

fail on create invoice

make deleted check on SQL query

nazi flake8

add_test

boom... it works and passes!!

* add app fixture

vlad's recommendations

* add deleted

* error checks

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-09-11 14:06:31 +01:00
dni ⚡andGitHub 6773a0f533 [REFACTOR] .super_user move it into data dir (#1900)
related to https://github.com/lnbits/lnbits/pull/1855
put the `.super_user` into our data_dir to not pollute our root dir
removed the weird mention to the url in the logs and mention
the `lnbits-cli superuser` instead

this is a breaking change for the bundlers, `cat .super_user` is not good anymore.
fix would be `cat data/.super_user` but for future compatibility i recommend
using `poetry run lnbits-cli superuser`
2023-09-11 12:19:19 +01:00
Djuri BaarsandGitHub 2c53445b5e Remove LNTXbot files (#1921) 2023-09-11 12:35:56 +02:00
dni ⚡andGitHub 62435b3723 [TEST] workflows, added 10s timeout to lncli subprocess and logging (#1910)
* [FIX] workflows

testing why workflwos unstable instable

* debug run_command

* add timeout to process run

* log lncli output

* try catch json exception in clnrest

* settle does not return a json

* add 10 minutes timeout to regtests if they ever get stuck

* better subprocess handling

* fix race condition

* test 3

* remove

* run nr 4

* increase waits for race conditions

* revert clnrest

* better fiat amount test
2023-08-30 11:01:32 +01:00
dni ⚡andGitHub 6efe1a156b [FEAT] add codecov token (#1908)
for better coverage reports
2023-08-28 13:49:33 +02:00
dni ⚡andGitHub 85b7591f6e update to version 0.10.10 (#1773) 2023-08-28 12:40:02 +01:00
callebtcandGitHub 172c277c2d fakewallet: set tag of description_hash invoice correctly (#1901) 2023-08-28 11:02:58 +01:00
jackstar12andGitHub 2623e9247a track fiat value of payments (#1789)
* save fiat_amounts on payment creation

* show fiat amount in frontend

* add lnbits_default_accounting_currency

* extract fiat calculation logic into service

* move all currency conversions to calc_fiat_amounts

move all conversion logic into create_invoice so extensions can benefit aswell

* show user-defined and wallet-defined currency in frontend

* remove sat from fiat units

* make bundle

* improve tests

* debug log
2023-08-28 11:00:59 +01:00
7a37e72915 [REFACTOR] replace async_wrap with run_sync (#1858)
* replace async_wrap with run_sync

formatting

remove unused fn

properly raise exception, not for timeout though

* [TEST] proper startup and shutdown (#1860)

* add proper startup / shutdown in tests

* fix event loop issues

because uvloop was installed in server.py which is not the main entry point when tests are ran.
this caused the loops referenced by the locks and queues to be a different one than the one used to run the tests
(only an issue in python 3.9)

* give openapi more time, does not matter anyway, regtest takes way longer

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>

remove uvloop

* fix test

* dont touch pyproject

* fix: install uvloop in conftest

not using uvloop at all causes tests to take way longer

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-08-28 10:59:56 +01:00
48f25488df [CHORE] cleanup cache and exception on make test (#1899)
* [CHORE] cleanup cache and exception on `make test`

moved the invalidate_forever task to proper location and use `create_permanent_task`.
and there was a Task never awaited Exception when running tests

* imrpove expiration test

* move cache into utils

* fix issue with pytest_asyncio

s

---------

Co-authored-by: jacksn <jkranawetter05@gmail.com>
2023-08-28 10:56:59 +01:00
dni ⚡andGitHub 6077edad8f [CHORE] remove redundant mocks (#1902)
* [CHORE] rmeove redundant mocks

* remove mock dependency
2023-08-28 10:56:20 +01:00
jackstar12andGitHub e50a7fb2d1 refactor exchange rates (#1847)
* simplify and cache exchange rate

note that exir was removed as a provider

* add binance as provider

* log exception

* add test

* add blockchain.com provider
2023-08-24 11:59:57 +01:00
7343d1e0a0 [REFACTOR] proper create_invoice responses for descriptions (#1863)
* [REFACTOR] proper create_invoice responses for descriptions

return a exception per error it is description hash or unhashed_description
keep types

* Update lnbits/core/views/api.py

Co-authored-by: michael1011 <me@michael1011.at>

* Update lnbits/core/views/api.py

Co-authored-by: michael1011 <me@michael1011.at>

---------

Co-authored-by: michael1011 <me@michael1011.at>
2023-08-24 12:58:10 +02:00
dni ⚡andGitHub 1efec9bb80 [REFACTOR] do not throw an exception in list_parse_fallback (#1891) 2023-08-24 12:50:38 +02:00
dni ⚡andGitHub c54f48ee73 [CI] improve on github workflows (#1889)
* [CI] making a proper github CI workflow
2023-08-24 12:36:37 +02:00
jackstar12andGitHub ac0ef07a77 optimize balances view (#1779)
* optimize balances view

flake8

flake8

* fix line length
2023-08-24 11:20:15 +01:00
dni ⚡andGitHub 39d717e34c improve on openapi metadata (#1795) 2023-08-24 10:52:12 +01:00
dni ⚡andGitHub 4e6f229db2 [CHORE] string formatting default length 88 (#1887)
* [CHORE] string formatting default length 88

uses blacks default off 88 and enabled autostringformatting

* formatting

* nitpicks jackstar

fix
2023-08-24 10:26:09 +01:00
dni ⚡andGitHub 2ab18544c3 [BUG] use WalletType for get_wallet_for_key fn (#1896) 2023-08-24 08:26:34 +02:00
dni ⚡andGitHub fe88320f08 [CHORE] update mypy and new issues (#1892)
* [CHORE] update mypy and new issues

lnbits/cache.py:21: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/extension_manager.py:210: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/db.py:110: error: Only instance methods can be decorated with @property  [misc]
lnbits/tasks.py:152: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/tasks.py:171: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/core/services.py:520: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/app.py:520: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/app.py:525: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]
lnbits/app.py:532: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs  [annotation-unchecked]

* fix db.py
* fix mypy notes, type were not needed
2023-08-23 21:24:54 +02:00
dni ⚡andGitHub b0ad47b69b [CHORE] update pyln-client (#1894)
lock versions

fixup
2023-08-23 12:51:40 +02:00
dni ⚡andGitHub 88c07acc2c [FEAT] save fiat amount in api_create_invoice (#1869)
save user input into extra when he chooses a currency for api_create invoice
2023-08-23 12:45:19 +02:00
c4da1dfdce [REFACTOR] WalletType into enum (#1888)
* [REFACTOR] WalletType into enum

- move wallettype models from decorators.py into models.py
- use enum instead of int
- use HTTPStatus for consistency

* Update lnbits/core/models.py

Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>

* use dataclass

---------

Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>
2023-08-23 12:41:22 +02:00
3a653630f1 Wallets: add cln-rest (#1775)
* receive and pay works

* fix linter issues

* import Paymentstatus from core.models

* fix test real payment

* fix get_payment_status check in lnbits

* fix tests?

* simplify

* refactor AsyncClient

* inline import of get_wallet_class

fixes the previous cyclic import

* invoice stream working

* add notes as a reminder to get rid of labels when cln-rest supports payment_hash

* create Payment dummy classmethod

* remove unnecessary fields from dummy

* fixes tests?

* fix model

* fix cln bug (#1814)

* auth header

* rename cln to corelightning

* add clnrest to admin_ui

* add to clnrest allowed sources

* add allowed sources to .env.example

* allow macaroon files

* add corelightning rest to workflow

* proper env names

* cleanup routine

* log wallet connection errors and fix macaroon clnrest

* print error on connection fails

* clnrest: handle disconnects faster

* fix test use of get_payment_status

* make format

* clnrest: add unhashed_description

* add unhashed_description to test

* description_hash test

* unhashed_description not supported by clnrest

* fix checking_id return in api_payments_create_invoice

* refactor test to use client instead of api_payments

* formatting, some errorlogging

* fix test 1

* fix other tests, paid statuses was missing

* error handling

* revert unnecessary changes (#1854)

* apply review of motorina0

---------

Co-authored-by: jackstar12 <jkranawetter05@gmail.com>
Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
2023-08-23 08:59:39 +02:00
dni ⚡andGitHub bb5033d225 [FEAT] add lnbits-cli for commands.py (#1890)
- add superuser command for print
- add delete-settings command
2023-08-21 16:29:13 +02:00
dni ⚡andGitHub eb0b06f98c [DEV] add ruff for linting and combine linters into one workflow (#1875)
* remove unused and update black + precommit
* makefile add ruff remove unused
* F541 fix
* complete ruff ignore
* remove unused workflow and combine linters into one
add lnbits/static to ruff ignore
save preview and linelength for later
define target version for black
* ignore upgrades for mypy
* remove flake8
* ignore F401 for __init__ files
* unused pylint comment
* unused noqa
* ignore upgrades for black
* run linting on py3.9 and py3.10
* dont assume python
2023-08-19 07:38:04 +02:00
jackstar12andGitHub 642f9a4c00 Update payments on invoice creation (#1782)
* update payments on invoice creation
* remove unnecessary payment polling
2023-08-18 12:10:52 +02:00
jackstar12andGitHub 8a6e411a0d [REFACTOR] payments sse endpoint (#1781)
* exclude sse from gzip

* refactor sse endpoint

* cleanup imports
2023-08-18 11:05:14 +01:00
stackregisterandGitHub d4de78f1e8 Add description to POST v1/payments endpoint. (#1848) 2023-08-18 10:30:45 +01:00
1fd4d9d514 cancel all long-running tasks (#1793)
* add centralized task management

in order to properly cleanup all long-running tasks we have to keep a list of them

* use new task management functions

* unify shutdown events

* vlads suggestions

rename variable for create_task
wrap cancel() with try/catch

fixup

* rename func to coro

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-08-18 10:25:33 +01:00
dni ⚡andGitHub 65db43ace4 feat: catch wallet init exception, string formatting (#1846)
this catches exception on set_wallet_class and switches to VoidWallet if it fails
2023-08-18 10:23:57 +01:00
dni ⚡andGitHub 59acd3a2ef [REFACTOR] cleanup views/api.py (#1865)
pull out models
line lengths
change models for tests
2023-08-18 10:22:22 +01:00
dni ⚡andGitHub 905afc1f5c FEAT: add installed extensions to log (#1857)
for immediate vision of installed extensions
2023-08-18 10:13:37 +01:00
dni ⚡andGitHub c7cf743852 [TEST] run tests.yml on py39 and py310 (#1882)
i think it is a good idea to test both at least for fakewallet tests.
2023-08-18 09:50:56 +01:00
b43e7efa0b [FEATURE] Adding coverage for 'lntbs' signet prefix (#1881)
* Update bolt11-decoder.js

Adding coverage for 'lntbs' signet prefix

* adding 'make bundle' modified files

---------

Co-authored-by: enaples <enaples@users.noreply.github.com>
2023-08-18 09:48:45 +01:00
dni ⚡andGitHub c8414829d7 [TEST] dont install default extensions for tests (#1861) 2023-08-16 19:19:21 +02:00
dni ⚡andGitHub 5e03f8eed8 [TEST] update pytest-cov to remove warning (#1867)
cleanup this warning when running tests
2023-08-16 16:26:48 +02:00
dni ⚡andGitHub 511a0fd895 [CHORE] cln.py pyln-client is not optional (#1859)
* [refactor] pyln-client is not optional

some time ago we added pyln do main dependencies, its need for think like, saas or docker.
so this import was dead code

* stubs for pyln.client
2023-08-16 15:54:06 +02:00
dni ⚡andGitHub 2434518d21 [TEST] better logs (#1866)
now you can see which lnbits logs belongs to which tests! :)
2023-08-16 12:31:56 +01:00
dni ⚡andGitHub 26a0633d49 [CHORE] fix pyright warning in db.py (#1868)
```warning: Class methods should take a "cls" parameter (reportSelfClsParameterName)```
2023-08-16 12:41:49 +02:00
dni ⚡andGitHub f0a66e41fb [CHORE] flake8 issues E402, E721 and F821 (#1874)
* F821: undefine name
disabled and logged webhook_listener for opennode and lnpay because they are obviously not working
* E402: module level import not at top of file
* E721 fixes, only popped up for python3.9 not 3.10
2023-08-16 12:22:14 +02:00
dni ⚡andGitHub 355806608b [CHORE] E722 bare exception fix (#1871)
* [CHORE] E722 bare exception fix
remove all bare exceptions from codebase and change it in `.flake8`
2023-08-16 12:17:54 +02:00
jackstar12andGitHub 3aef1605be Fix csv export (#1852)
* fetch all payments for csv export

* include fiat amount and currency in export
2023-08-15 12:39:12 +01:00
dni ⚡andGitHub f096b51f70 [TEST] proper credit_wallet function from services (#1862) 2023-08-07 21:49:37 +02:00
michael1011andGitHub 03acf5e5ec UI: hide paste payment request in unsupported environments (#1853) 2023-08-03 20:38:05 +02:00
772ae2a1ab support uvicorn reload functionality (#1841)
* support uvicorn reload functionality
see:
https://github.com/encode/uvicorn/blob/8239373ec649b1a41179070b6ba2c8dc549df63e/uvicorn/main.py#L580-L582
* add docs for --reload

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-08-03 09:53:36 +02:00
jackstar12andGitHub 2577ce7f81 add cache utility (#1790)
* add simple caching utility

* test cache

* remove prefix, default on get

* check expiry in pop aswell

* remove unnecessary type

* improve invalidation task

increase default interval to 10 seconds - doesnt have to check that often.
instead of recreating the dict everytime mutate the existing one
2023-08-02 14:13:31 +02:00
callebtcandGitHub 9bc8a9db55 UI: paste payment request from clipboard (#1845)
* paste payment request from clipboard

* remove unelevated
2023-08-02 12:42:23 +01:00
callebtcandGitHub 9f40b3cdbd Tests: hold invoice tests pending during payment (#1839)
* tests: hold invoice tests pending during payment

* sigh...

* ok

* hash preimage

* preimage_hash == payment_hash

* should error

* check that payment is gone

* cancel task while in-flight

* lnbits endpoing return error

* return error

* add both options

* fix list

* respond to error

* return false if payment status errors

* outgoing invoice should be deleted after one status check

* test api_payments_create error
2023-07-31 20:25:22 +02:00
jackstar12andGitHub 1ab81f6d7e Bug: invalid type converter (#1842)
* temporary test case

* remove buggy converter
2023-07-31 10:21:45 +02:00
jackstar12andGitHub 67d3a4f359 remove nested filters (#1843)
* remove nested filters

* remove from openapi schema aswell
2023-07-31 10:21:30 +02:00
7f0c7138af TEST: add hold invoices to test helpers and a basic test (#1838)
* add hold invoices and basic test

* run request in parallel

* create_task

* dont cancel it

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-07-26 14:18:14 +02:00
jackstar12andGitHub dda6c1b3c1 websocket internal payment notifications (#1831)
* add send_payment_notification service
payment notifications are sent from multiple places with inconsistent and incomplete data
* adopt new send_payment_notification service
* add tests
2023-07-26 12:08:22 +02:00
Mariusz KogenandGitHub cf0a87582c Ads layout fix (#1835) 2023-07-26 10:38:45 +02:00
4b25714b31 Fix drawer (#1836)
* make sidebar above tabs on mobile
* Can we add padding instead
* fix: add padding for small screen
* make bundle... again

---------

Co-authored-by: Ben <ben@arc.wales>
Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2023-07-26 10:35:52 +02:00
Mariusz KogenandGitHub 0ff0abf442 Adding Start9 (#1834) 2023-07-25 15:38:54 +02:00
dni ⚡andGitHub 6ce0387e1c FEAT: proper failing invoice/payment responses for VoidWallet (#1805)
before that change anyone using the api just got a 500 error without any error message.
there was also a bug stacetrace in the logs each time.

flake8
2023-07-25 14:13:34 +01:00
dni ⚡andGitHub 427f155c76 follow redirect for lnurls (#1832)
closes #976
2023-07-25 11:06:37 +02:00
dni ⚡andGitHub a81a2b5a08 fix ugly printed error of uvicorn.error (#1833) 2023-07-24 13:25:47 +02:00
callebtcandGitHub 11fec7a889 [Wallets] CLN: fix pending state check (#1770)
* better checking

* flake8 fix

* make format

* invoices scope are function for tests

* invoice back to sessionbut keep real_invoice for now

* make format

* comment

* get payment by checking id and test
2023-07-24 12:00:41 +02:00
dni ⚡andGitHub f478c1848a update embit to 0.7.0 (#1777) 2023-07-21 10:42:35 +02:00
callebtcandGitHub 2d4c9f349b Wallets: Catch errors during cleanup (#1829)
* jetz aba

* catch RuntimeError
2023-07-21 09:50:50 +02:00
Tiago VasconcelosandGitHub befb288900 Fix admin notifications (#1803)
* fix save badge being on all the time

* make deep copy and add restart to notification popup
2023-07-21 09:50:18 +02:00
dni ⚡andGitHub 963fd5de50 add github workflow for checking openapi (#1551)
* add github workflow for checking openapi

* verbose errors

* actual fix? remove env_names from BaseSettings class?

* dont kill process in workflow

* poetry version 1.5.1 for openapiu
2023-07-19 12:28:27 +01:00
ArcandGitHub a3aafe4b6f Data returned from wallet websocket was not json (#1823)
* Fixed walet websocket json

* format
2023-07-19 12:14:03 +01:00
f384c5c366 Increase default invoice expiry to 1 hour (#1825)
* more reasonable default expiry

600 = 10 minutes which is a pretty short amount of time, for example when using lnbits fundingsource inside lnbits itself it would fail if you do a submarine swap or anything involved with hold invoices

i think 3600 = 1 hour should not make a difference in performance and is a way more reasonable expiry

* change default in .env.example

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-07-19 13:13:46 +02:00
dni ⚡andGitHub 153e22fb54 remove extensions dir completly, create it on startup (#1778)
* remove extensions dir compleltly, and create it on server.py start

* also ignore symlinks
2023-07-18 13:46:50 +02:00
dni ⚡andGitHub 213be79aaa quickfix for failing checks (#1819)
root package is needed for tests

root package is needed for mypy

removing testfile
2023-07-11 13:25:35 +02:00
dni ⚡andGitHub f7cbe74d77 Update poetry to 1.5.1 (#1804)
* update poetry to 1.5.1 inside workflows

* update lock file to poetry 1.5.1

* test run 1

* test run 2

* run 3

* try different sortorder

* cli should use --no-root

here  are the refereneces.
https://python-poetry.org/docs/basic-usage/#installing-dependencies-only
https://pip.pypa.io/en/stable/topics/local-project-installs/#regular-installs

* cache migrations aswell

* test flake

* optimize docker image for regtest building

* playng

* remove test run 1

* remove run 2

* remove run 3

* run 4

* run 6

* run 7

* run 8

* run 9

* run 10
2023-07-11 12:46:40 +02:00
ArcandGitHub d0d5c4d033 Update README.md (#1818) 2023-07-11 09:34:21 +01:00
ArcandGitHub 0adaf03de5 update favicon (#1811) 2023-07-07 10:21:24 +01:00
83a04d8f74 CI adjustment - Pinkify Legend (#1766)
Add files via upload
Update base.scss
* making the bundle for pinkify
fix flamingo

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-07-05 14:39:39 +02:00
dni ⚡andGitHub e13426a56b needs restart after saving killswitch (#1800)
fix prettier
2023-07-03 16:52:33 +02:00
6aaa46e5a0 Fix ip blocker 2nd try (#1796)
* fix ipblock v2

fix ipblock 2nd try

* remove sleep inside ip block

dont ignore other exceptions

typo

* remove asyncio

* Update lnbits/middleware.py

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-07-03 11:25:00 +02:00
callebtcandGitHub b9db3eafe0 remove sleep (#1797) 2023-07-03 11:12:09 +02:00
dni ⚡andGitHub ae9eb314cc fix bundle for 0.10.9 (#1792) 2023-07-03 10:29:03 +02:00
dni ⚡andGitHub fe393b83ab ip blocker only call next after validation (#1791)
* only call next after validation

* lets the blocked ips wait :)

* calles suggestion :)
2023-07-03 10:26:35 +02:00
jackstar12andGitHub bc55d52ea2 Refactor get walletclass (#1776)
* move `get_wallet_class` to wallets module

* adjust imports, fix type issues

flake8
2023-06-27 16:11:00 +02:00
dni ⚡andGitHub 9a0878de19 ignore upgrades dir in linters and formatters and precommit (#1783) 2023-06-26 10:38:40 +02:00
callebtcandGitHub be7c589722 add pre-commit-config (#1785) 2023-06-26 10:38:16 +02:00
dni ⚡andGitHub 28a55b2d41 precommit only check prettier if html/css/js/json (#1786)
add precommit checks only for types
2023-06-26 10:35:39 +02:00
callebtcandGitHub d66c83c91a no timeout for the sse connection (#1784) 2023-06-26 10:02:49 +02:00
7e1f43933d Adds security tools, such as a rate limiter, IP block/allow, server logs (#1606)
* added ratelimiter
* Adds server logs to admin ui
* Added IP allow/ban list
* fixed remove ips
* Split rate limit number and unit
* security tab and background tasks for killswitch
* fix test for auditor api

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-06-20 11:26:33 +02:00
Tiago VasconcelosandGitHub 758a4ecaf6 Fix user label across lnbits (#1771)
* check for super user on get_user
* remove unecessary assingments
2023-06-20 08:41:57 +02:00
callebtcandGitHub 339645a912 docs: add precommit hooks (#1772)
* docs: add precommit hooks
* adjust test
2023-06-20 08:41:23 +02:00
9014e73529 Create cn.js (#1768)
* Create cn.js
Add chinese translation
* added additional files for chinese language
* Update lnbits/templates/base.html

---------

Co-authored-by: hodlbaby <52352588+hodlbaby@users.noreply.github.com>
Co-authored-by: dni  <office@dnilabs.com>
2023-06-20 08:41:04 +02:00
48fc73d9d4 Ability to limit available fiat currencies (#1748)
* feat: limit number of available fiat currencies
* feat: Add allowed currency selector to Admin UI
* motorinas suggestions

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-06-19 12:24:19 +02:00
OrenandGitHub 150a33009d s/gihub/github/g (#1767) 2023-06-19 12:22:53 +02:00
jackstar12andGitHub e6499104c0 Wallets refactor (#1729)
* feat: cleanup function for wallet

* update eclair implementation

* update lnd implementation

* update lnbits implementation

* update lnpay implementation

* update lnbits implementation

* update opennode implementation

* update spark implementation

* use base_url for clients

* fix lnpay

* fix opennode

* fix lntips

* test real invoice creation

* add small delay to test

* test paid invoice stream

* fix lnbits

* fix lndrest

* fix spark

fix spark

* check node balance in test

* increase balance check delay

* check balance in pay test aswell

* make sure get_payment_status is called

* fix lndrest

* revert unnecessary changes
2023-06-19 12:12:00 +02:00
Vlad StanandGitHub 49411e58cc Fix: extension links (#1764)
* fix: open extension link
* fix: do not show `Enable` for admin extensions to non-admin users
2023-06-15 19:06:21 +02:00
Vlad StanandGitHub 8c0e7725de Add option to drop extension db at un-install time or later (#1746)
* chore: remove un-used file
* feat: allow extension DB clean-up
* feat: i18n and bundle update
* chore: code format
* fix: button color
* chore: delete temp file
* chore: fix merge conflicts
* chore: add extra log
* chore: bump CACHE_VERSION to `37`
2023-06-15 16:22:18 +02:00
Vlad StanandGitHub 95281eba8c feat: show extension repo as tooltip for each repo (#1743) 2023-06-15 16:08:24 +02:00
ArcandGitHub cd17b59a3a Change asdict to dict (#1757) 2023-06-15 15:44:44 +02:00
dni ⚡andGitHub ec623f4861 remove settings logging (security) (#1763) 2023-06-15 15:44:08 +02:00
7c479a3ebf Hide the superuser ID URL from logs with comment where to find superuser ID (#1749)
* Hide the superuser ID URL from logs with comment where to find admin ID
* Update admin_ui.md

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-06-15 15:34:39 +02:00
dni ⚡andGitHub 9017cee491 quickfix spacing und mobile footer (#1740) 2023-06-15 15:32:10 +02:00
dni ⚡andGitHub 2940716807 bump to version 0.10.9 (#1742) 2023-06-15 15:30:39 +02:00
dni ⚡andGitHub c48e2329c5 TEST: fix tests for real wallets (#1761)
* fix tests for real wallets
* change realinvoice provider to lnd-1
* use lnd-3 for tests
* fix eclair tests
* use bullseye in dockerimage
2023-06-15 12:36:28 +02:00
dni ⚡andGitHub 8ed2422ade highlight active language #1730 (#1741) 2023-06-01 11:33:53 +01:00
Vlad StanandGitHub af660fec14 fix: ext short description for pre-installed extensions (#1744)
* fix: update `installed_extensions` entry after the ext has been unzipped

* fix: show installedRelease description as fallback
2023-05-26 10:48:40 +01:00
ArcandGitHub 6bbebe0cee Fixes and expands payments table csv (#1739) 2023-05-25 05:29:15 +01:00
ArcandGitHub f863a043cb Adds translations for send/receive (#1738)
* Send/receive translation

* bundle fundle
2023-05-24 20:59:34 +01:00
ArcandGitHub 2216649412 remove msat fee to sat (#1737)
* remove msat fee

* Fixed some formatting
2023-05-24 20:27:56 +01:00
ArcandGitHub 9d8c78c927 added msats (#1736) 2023-05-24 19:47:54 +01:00
ArcandGitHub 02bf2f9819 Better wallet format (#1735)
* Fixing big inout

* btter wallet format
2023-05-24 15:44:28 +01:00
ArcandGitHub 921984e52b mre formats (#1733) 2023-05-24 14:27:16 +01:00
ArcandGitHub d0599458c6 Format wallet UI tweaks (#1732)
* Formatting wallet stuff

* Bundle fundle

* Added v-if
2023-05-24 13:41:03 +01:00
Vlad StanandGitHub 80b803913f feat: print extensions version on install/uninstall/reinstall (#1726) 2023-05-24 11:58:11 +01:00
Tiago VasconcelosandGitHub 6eab3dae01 add loop for language selection (#1725)
add languages

add portuguese

fix bug with singlequotes, and a vendor.json

fixup formatting

include bundle

rename files
2023-05-24 11:43:31 +01:00
Tiago VasconcelosandGitHub eab19280d5 fix "boolean" query for sqlite (#1727)
use false
2023-05-24 12:14:21 +02:00
ArcandGitHub c960f718f5 Adds websocket for incoming/outgoing payments with wallet balance (#1679)
* Websocket works for incoming/outgoing and includes wallet balance

* mypy

* mypy try

* Actual mypy (no try)

* format
2023-05-22 12:38:26 +01:00
7df131d3c7 Minor improvements to the German translation (#1721)
* Minor improvements to the German translation

This replaces some usages of "Sie" with "du", and also fixes a few minor mistakes.

* format

---------

Co-authored-by: ben <ben@arc.wales>
2023-05-22 12:22:51 +01:00
dni ⚡andGitHub cae24b233e fix formatting on main (#1723) 2023-05-22 12:21:11 +01:00
jackstar12andGitHub 252fd6c313 feat: specify sort fields (#1716)
* feat: specify sort fields

* doc string
2023-05-22 12:11:05 +01:00
37ac630573 add voltage logo to index.html under runs on section (#1711)
* add voltage to index.html under runs on section

* fixed url

---------

Co-authored-by: Jose Sahagun <jsahagun91@gmail.com>
2023-05-22 12:10:21 +01:00
dni ⚡andGitHub 035d6263f9 Bump version to 0.10.8 (#1717) 2023-05-22 12:09:45 +01:00
dni ⚡andGitHub 0ab1924327 remove mypy from precommit (#1718) 2023-05-22 12:09:21 +01:00
Aaron DewesandGitHub 127b780af7 C-Lightning -> Core Lightning (#1720)
They rebranded a while ago, so this changes the GUI texts to reflect that
2023-05-22 12:06:23 +01:00
daywalker90andGitHub 39066e4bc8 fix cln wallet for msat migration (#1671)
* fix cln wallet for msat migration

* status wants our funds only
2023-05-22 12:03:37 +01:00
ArcandGitHub ca41a92bd9 version update (#1715) 2023-05-19 19:09:17 +01:00
dni ⚡andGitHub f97c698917 FIX: qrcode scanner on legends, by updating the library (#1714) 2023-05-19 13:23:22 +01:00
dni ⚡andGitHub dd1c17f86f FIX: internal qrcode scanning check should always check for lowercase (#1713)
fixup
2023-05-19 12:41:58 +01:00
Tiago VasconcelosandGitHub 587b763f03 Add user role (#1712)
* Add user role

* make format
2023-05-19 09:32:02 +02:00
callebtcandGitHub e39241a9e9 Fix: get_payments crud default Filter values. (#1703)
* get_payments: deafult values for filter

* lololololoolol
2023-05-11 18:36:20 +02:00
callebtc b6223f0e6d get_payments: deafult values for filter 2023-05-11 18:29:08 +02:00
6f2771e334 Merges extensions into one page (#1656)
* Merged extensions into one page

* Bundle files updated

* Fixed install bug

* feat: client side version compatibility check

* fix: hide `Activated/Deactivated` toggle for non-admins

* feat: translate labels to `EN`

* feat: add other language translations

* chore: update bundle for i18n

* feat: check extension version server-side

* feat: show warning message

* refactor: nicer mapping

Co-authored-by: dni  <office@dnilabs.com>

* chore: code format

* chore: extra log

* feat: check_latest_version of ext

* feat: show tooltip for new version

* chore: `make bundle`

* chore: `mypy`

* chore: code clean-up

* feat: show version in badge (spacing is fine)

* chore: make bundle

* feat: check `min_lnbits_version` and `warning` in `config.json`

* chore: code formatting

* chore: downgrade log level

* fix: extract `ExtensionsInstallSettings` as readonly

* fix: do not show installed and deactivated extensions

* chore: format

* fix: `Enable` button tooltip

* fix: set installed release after installation

* fix: hide deactivated extensions from regular users

* bundle fundle

* bundle fundle

---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
Co-authored-by: dni  <office@dnilabs.com>
2023-05-11 01:14:07 +01:00
3fe33dfb81 Translated en.js to Polish (#1701)
* Translated en.js to Polish

* Minor pl.js fixes

* Added to language menu

* Update bundle

---------

Co-authored-by: ben <ben@arc.wales>
2023-05-11 01:01:35 +01:00
4ec16b56bf Extend bar for mobile (#1544)
* extend bar for mobile

* smaller padding

* center buttons

* Added rounded style

* Added icons, looks better

* service worker

* Changed icon

* Fixed show if

---------

Co-authored-by: ben <ben@arc.wales>
2023-05-11 00:12:48 +01:00
ArcandGitHub 662f792d4f Added Welsh (#1696)
* Added Welsh

* format

* Added to base

* bundle fundle
2023-05-10 11:18:18 +01:00
dni ⚡andGitHub d8ccb4dcd8 fix old payments endpoint for lndhub (#1698)
* fix old payments endpoint for lndhub

* fixup tests
2023-05-10 11:47:04 +02:00
7e0beb508c Adding IT properly (#1695)
* Adding IT properly

* added to base + format + bundle

---------

Co-authored-by: ben <ben@arc.wales>
2023-05-10 10:33:17 +01:00
dni ⚡andGitHub 95071eb87e quick fix languages bundle (#1691) 2023-05-09 09:56:44 +01:00
BitkarrotandGitHub 44c5c87405 Add troubleshooting note for Poetry errors on OSX install (#1641)
* Update installation.md

for poetry errors on OSX

* Update installation.md

* Update installation.md

* Update installation.md

remove trouble shooting message at top
2023-05-09 09:43:19 +01:00
dni ⚡andGitHub 8e8093bef3 de.js i18n updates, add nl and pirate (#1675)
bump service worker
2023-05-09 09:42:27 +01:00
dni ⚡andGitHub bb9ef7f88c FEAT: precommit git hooks, based on fusions #913 (#1645)
* init commit for precommit

* some reformatting precommit does
2023-05-09 09:41:40 +01:00
Vlad StanandGitHub 67d8b67ee5 Create super user account if it does not exist (#1688)
* fix: temp create account for `super_user_id` if missing

* chore: remove dumb import

* refactor: move logic outside `crud`

* feat: add uuid4 conversion

* fix: require valid string in .env file

* fix: update the `settings.super_user` value in case or normalisation for UUID4

* fix: allow long super_user

* chore: code format

* fix: add UI redirect with the normalized user

* fix: normalize `super_user` up one level

* fix: should normalize user in non-ui mode also
2023-05-09 09:22:19 +01:00
Vlad StanandGitHub 132d8a9320 fix: rename bool super_user to is_super_user (#1690) 2023-05-09 09:20:51 +01:00
c0f66989cb Serverside Pagination for payments (#1613)
* initial backend support

* implement payments pagination on frontend

* implement search for payments api

* fix pyright issues

* sqlite support for searching

* backwards compatability

* formatting, small fixes

* small optimization

* fix sorting issue, add error handling

* GET payments test

* filter by dates, use List instead of list

* fix sqlite

* update bundle

* test old payments endpoint aswell

* refactor for easier review

* optimise test

* revert unnecessary change

---------

Co-authored-by: dni  <office@dnilabs.com>
2023-05-09 09:18:53 +01:00
dni ⚡andGitHub 45b199a8ef create datadir on launch (#1674)
* create datadir on launch
* change makedirs to Path.mkdir
2023-05-09 10:04:27 +02:00
dni ⚡andGitHub 159aa24a4e update 0.10.6 (#1689) 2023-05-09 09:48:05 +02:00
62812938bd French language added (#1686)
* French language added
---------
Co-authored-by: Dood597 <66718486+Dood597@users.noreply.github.com>
2023-05-09 09:47:06 +02:00
dni ⚡andGitHub 3c92ef2900 FIX: #1661 (#1663)
fixup
2023-05-04 17:22:24 +02:00
dni ⚡andGitHub 7feac3bb5e add lnbits version to frontend (#1677) 2023-05-04 17:21:37 +02:00
Tiago VasconcelosandGitHub 590b52cb31 Update print.html (#1678)
* Update print.html

Fix print.html not getting the JS libraries

* Update print.html

Add `INCLUDED_CSS` also
2023-05-04 17:21:18 +02:00
callebtcandGitHub b4fce8b00b fix internal invoice check (#1681)
* fix internal invoice check

* return error

* make format
2023-05-04 17:20:37 +02:00
ArcandGitHub bd47527d34 Merge pull request #1672 from lnbits/paymentsocketjson
Output json to payment websocket
2023-05-02 18:42:51 +01:00
ben 171aa08e0c output json 2023-05-02 18:40:46 +01:00
ArcandGitHub bc2c5fd1a9 Merge pull request #1648 from lnbits/typeignore-generic
removing type ignores from views/generic.py
2023-05-02 14:50:00 +01:00
ArcandGitHub 67c377affc Merge pull request #1670 from lnbits/fixsparkimage
Fixes spark image on homepage
2023-05-02 14:25:59 +01:00
ben 09d67249cc format 2023-05-02 14:11:01 +01:00
ben 068965d81b Missing div 2023-05-02 14:07:25 +01:00
ben aec15ae820 format 2023-05-02 13:43:54 +01:00
ben c714a2641d Adds websocket for payments 2023-05-02 12:53:36 +01:00
ben b9d3fc5744 Fix spark image 2023-05-02 12:27:51 +01:00
dni ⚡andcalle 21157fffe2 removing type ignores from views/generic.py
suggestion motorina1

Update lnbits/core/views/generic.py

Co-authored-by: calle <93376500+callebtc@users.noreply.github.com>

quickfix
2023-04-27 16:28:16 +02:00
dni ⚡andGitHub f8058825d8 FEAT: introduce bundle_assets variable and decouple it from DEBUG (#1660) 2023-04-27 16:05:49 +02:00
dni ⚡andGitHub bfb27020ee regression: fatfinger removed block styles (#1659)
quickfix

format
2023-04-27 15:38:52 +02:00
dni ⚡andGitHub 667de4d1f0 generate .super_user file on startup if adminui is enabled (#1651)
* generate .super_user file on startup if adminui is enabled
* only save super_user id instead of url
2023-04-25 10:25:50 +02:00
dni ⚡andGitHub 5c119e903d increment cache version for service-worker.js (#1644)
* increment cache version
* add serviceworker incrementer on make bundle
2023-04-25 10:25:09 +02:00
dni ⚡andGitHub 3f3af53240 prepare lnbits version 0.10.5 (#1649) 2023-04-25 10:24:24 +02:00
dni ⚡andGitHub 1b84ebf13d FEAT: build static files with npm (sass, concat, minify), remove build step from python, include minified bundle files (#1601) 2023-04-21 14:51:46 +02:00
dni ⚡andGitHub bef2d4a72e remove boltz_client as dependecy (#1610) 2023-04-21 14:42:52 +02:00
dni ⚡andGitHub 04471b8b5b remove python38 from pyproject.toml (#1632) 2023-04-21 14:37:26 +02:00
ArcandGitHub be1d515307 Merge pull request #1642 from lnbits/new-version
change version to 0.10.4
2023-04-21 13:15:28 +01:00
dni ⚡ 87d1de2a31 change version to 0.10.4.1 2023-04-21 13:18:03 +02:00
dni ⚡ 0546fb0a77 change version to 0.10.4 2023-04-21 13:08:01 +02:00
388 changed files with 110615 additions and 22746 deletions
+2
View File
@@ -3,12 +3,14 @@ data
docker
docs
tests
node_modules
lnbits/static/css/*
lnbits/static/bundle.js
lnbits/static/bundle.css
*.md
!README.md
*.log
.env
+14
View File
@@ -0,0 +1,14 @@
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file, utf-8 charset
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[/lnbits/static/vendor/*]
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset
+212 -64
View File
@@ -1,74 +1,56 @@
#For more information on .env files, their content and format: https://pypi.org/project/python-dotenv/
HOST=127.0.0.1
PORT=5000
######################################
####### Auth Configurations ##########
######################################
# Secret Key: will default to the hash of the super user.
# !!!!! It is strongly recommended that you set your own strong random value !!!!
AUTH_SECRET_KEY=""
AUTH_TOKEN_EXPIRE_MINUTES=525600
# Possible authorization methods: user-id-only, username-password, nostr-auth-nip98, google-auth, github-auth, keycloak-auth
AUTH_ALLOWED_METHODS="user-id-only, username-password"
# Set this flag if HTTP is used for OAuth
# OAUTHLIB_INSECURE_TRANSPORT="1"
# uvicorn variable, uncomment to allow https behind a proxy
# FORWARDED_ALLOW_IPS="*"
######################################
########### Admin Settings ###########
######################################
DEBUG=false
# Allow users and admins by user IDs (comma separated list)
LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
# Warning: Enabling this will make LNbits ignore this configuration file. Your settings will
# be stored in your database and you will be able to change them only through the Admin UI.
# Disable this to make LNbits use this config file again.
LNBITS_ADMIN_UI=false
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# Ad space description
# LNBITS_AD_SPACE_TITLE="Supported by"
# csv ad space, format "<url>;<img-light>;<img-dark>, <url>;<img-light>;<img-dark>", extensions can choose to honor
# LNBITS_AD_SPACE="https://shop.lnbits.com/;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-light.png;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-dark.png"
# Hides wallet api, extensions can choose to honor
LNBITS_HIDE_API=false
# LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions-trial.json"
# GitHub has rate-limits for its APIs. The limit can be increased specifying a GITHUB_TOKEN
# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx
# Extensions to be installed by default. If an extension from this list is uninstalled then it will be re-installed on the next restart.
# The extension must be removed from this list in order to not be re-installed.
LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos"
# Database: to use SQLite, specify LNBITS_DATA_FOLDER
# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://...
# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://...
# for both PostgreSQL and CockroachDB, you'll need to install
# psycopg2 as an additional dependency
LNBITS_DATA_FOLDER="./data"
# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename"
LNBITS_SERVICE_FEE="0.0"
# value in millisats
LNBITS_RESERVE_FEE_MIN=2000
# value in percent
LNBITS_RESERVE_FEE_PERCENT=1.0
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available.
# Warning: Enabling this will make LNbits ignore most configurations in file. Only the
# configurations defined in `ReadOnlySettings` will still be read from the environment variables.
# The rest of the settings will be stored in your database and you will be able to change them
# only through the Admin UI.
# Disable this and clear `settings` table from database to make LNbits use this config file again.
LNBITS_ADMIN_UI=true
# Change theme
LNBITS_SITE_TITLE="LNbits"
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
LNBITS_SITE_TAGLINE="Open Source Lightning Payments Platform"
LNBITS_SITE_DESCRIPTION="The world's most powerful suite of bitcoin tools. Run for yourself, for others, or as part of a stack."
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber"
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
# Choose from LNPayWallet, OpenNodeWallet, ClicheWallet,
# LndWallet, LndRestWallet, CoreLightningWallet, EclairWallet,
# LnTipsWallet, LNbitsWallet, SparkWallet, FakeWallet,
HOST=127.0.0.1
PORT=5000
######################################
########## Funding Source ############
######################################
# which fundingsources are allowed in the admin ui
# LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, BlinkWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet, NWCWallet, BreezSdkWallet, BoltzWallet"
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
# just so you can see the UI before dealing with this file.
# How many times to retry connectiong to the Funding Source before defaulting to the VoidWallet
# FUNDING_SOURCE_MAX_RETRIES=4
# Invoice expiry for LND, CLN, Eclair, LNbits funding sources
LIGHTNING_INVOICE_EXPIRY=600
LIGHTNING_INVOICE_EXPIRY=3600
# Set one of these blocks depending on the wallet kind you chose above:
@@ -82,22 +64,27 @@ SPARK_TOKEN=myaccesstoken
# CoreLightningWallet
CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
# CoreLightningRestWallet
CORELIGHTNING_REST_URL=http://127.0.0.1:8185/
CORELIGHTNING_REST_MACAROON="/path/to/clnrest/access.macaroon" # or BASE64/HEXSTRING
CORELIGHTNING_REST_CERT="/path/to/clnrest/tls.cert"
# LnbitsWallet
LNBITS_ENDPOINT=https://legend.lnbits.com
LNBITS_ENDPOINT=https://demo.lnbits.com
LNBITS_KEY=LNBITS_ADMIN_KEY
# LndWallet
LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=10009
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
LND_GRPC_CERT="/home/bob/.lnd/tls.cert"
LND_GRPC_MACAROON="/home/bob/.lnd/data/chain/bitcoin/mainnet/admin.macaroon" # or HEXSTRING
# To use an AES-encrypted macaroon, set
# LND_GRPC_MACAROON="eNcRyPtEdMaCaRoOn"
# LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
LND_REST_CERT="/home/bob/.lnd/tls.cert"
LND_REST_MACAROON="/home/bob/.lnd/data/chain/bitcoin/mainnet/admin.macaroon" # or HEXSTRING
# To use an AES-encrypted macaroon, set
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
@@ -108,6 +95,29 @@ LNPAY_API_KEY=LNPAY_API_KEY
# Wallet Admin in Wallet Access Keys
LNPAY_WALLET_KEY=LNPAY_ADMIN_KEY
# AlbyWallet
ALBY_API_ENDPOINT=https://api.getalby.com/
ALBY_ACCESS_TOKEN=ALBY_ACCESS_TOKEN
# BoltzWallet
BOLTZ_CLIENT_ENDPOINT=127.0.0.1:9002
BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroon" # or HEXSTRING
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert" # or HEXSTRING
BOLTZ_CLIENT_WALLET="lnbits"
# ZBDWallet
ZBD_API_ENDPOINT=https://api.zebedee.io/v0/
ZBD_API_KEY=ZBD_ACCESS_TOKEN
# BlinkWallet
BLINK_API_ENDPOINT=https://api.blink.sv/graphql
BLINK_WS_ENDPOINT=wss://ws.blink.sv/graphql
BLINK_TOKEN=BLINK_TOKEN
# PhoenixdWallet
PHOENIXD_API_ENDPOINT=http://localhost:9740/
PHOENIXD_API_PASSWORD=PHOENIXD_KEY
# OpenNodeWallet
OPENNODE_API_ENDPOINT=https://api.opennode.com/
OPENNODE_KEY=OPENNODE_ADMIN_KEY
@@ -120,12 +130,150 @@ LNBITS_DENOMINATION=sats
ECLAIR_URL=http://127.0.0.1:8283
ECLAIR_PASS=eclairpw
# NWCWalllet
NWC_PAIRING_URL="nostr+walletconnect://000...000?relay=example.com&secret=123"
# LnTipsWallet
# Enter /api in LightningTipBot to get your key
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
LNTIPS_API_ENDPOINT=https://ln.tips
# Cashu Mint
# Use a long-enough random (!) private key.
# Once set, you cannot change this key as for now.
CASHU_PRIVATE_KEY="SuperSecretPrivateKey"
# BreezSdkWallet
BREEZ_API_KEY=KEY
BREEZ_GREENLIGHT_SEED=SEED
# A Greenlight invite code or Greenlight partner certificate/key can be used
BREEZ_GREENLIGHT_INVITE_CODE=CODE
BREEZ_GREENLIGHT_DEVICE_KEY="/path/to/breezsdk/device.pem" # or BASE64/HEXSTRING
BREEZ_GREENLIGHT_DEVICE_CERT="/path/to/breezsdk/device.crt" # or BASE64/HEXSTRING
# BREEZ_USE_TRAMPOLINE=true
# Google OAuth Config
# Make sure that the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
# GitHub OAuth Config
# Make sure that the authorization callback URL is set to https://{domain}/api/v1/auth/github/token
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
# Keycloak OAuth Config
# Make sure that the valid redirect URIs contain https://{domain}/api/v1/auth/keycloak/token
KEYCLOAK_CLIENT_ID=""
KEYCLOAK_CLIENT_SECRET=""
KEYCLOAK_DISCOVERY_URL=""
######################################
# uvicorn variable, uncomment to allow https behind a proxy
# IMPORTANT: this also needs the webserver to be configured to forward the headers
# http://docs.lnbits.org/guide/installation.html#running-behind-an-apache2-reverse-proxy-over-https
# FORWARDED_ALLOW_IPS="*"
# Server security, rate limiting ips, blocked ips, allowed ips
LNBITS_RATE_LIMIT_NO="200"
LNBITS_RATE_LIMIT_UNIT="minute"
LNBITS_ALLOWED_IPS=""
LNBITS_BLOCKED_IPS=""
# Allow users and admins by user IDs (comma separated list)
# if set new users will not be able to create accounts
LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# ID of the super user. The user ID must exist.
# SUPER_USER=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, nostrclient"
# Extensions enabled by default when a user is created
LNBITS_USER_DEFAULT_EXTENSIONS="lnurlp"
# Start LNbits core only. The extensions are not loaded.
# LNBITS_EXTENSIONS_DEACTIVATE_ALL=true
# Disable account creation for new users
# LNBITS_ALLOW_NEW_ACCOUNTS=false
# Enable Node Management without activating the LNBITS Admin GUI
# by setting the following variables to true.
LNBITS_NODE_UI=false
LNBITS_PUBLIC_NODE_UI=false
# Enabling the transactions tab can cause crashes on large Core Lightning nodes.
LNBITS_NODE_UI_TRANSACTIONS=false
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# Ad space description
# LNBITS_AD_SPACE_TITLE="Supported by"
# csv ad space, format "<url>;<img-light>;<img-dark>, <url>;<img-light>;<img-dark>", extensions can choose to honor
# LNBITS_AD_SPACE="https://shop.lnbits.com/;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-light.png;https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits/static/images/lnbits-shop-dark.png"
# LNBITS_SHOW_HOME_PAGE_ELEMENTS=true # if set to true, the ad space will be displayed on the home page
# LNBITS_CUSTOM_BADGE="USE WITH CAUTION - LNbits wallet is still in BETA"
# LNBITS_CUSTOM_BADGE_COLOR="warning"
# Hides wallet api, extensions can choose to honor
LNBITS_HIDE_API=false
# LNBITS_EXTENSIONS_MANIFESTS="https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json,https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions-trial.json"
# GitHub has rate-limits for its APIs. The limit can be increased specifying a GITHUB_TOKEN
# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx
# Path where extensions will be installed (defaults to `./lnbits/`).
# Inside this directory the `extensions` and `upgrades` sub-directories will be created.
# LNBITS_EXTENSIONS_PATH="/path/to/some/dir"
# Extensions to be installed by default. If an extension from this list is uninstalled then it will be re-installed on the next restart.
# The extension must be removed from this list in order to not be re-installed.
LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos"
# Database: to use SQLite, specify LNBITS_DATA_FOLDER
# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://...
# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://...
# for both PostgreSQL and CockroachDB, you'll need to install
# psycopg2 as an additional dependency
LNBITS_DATA_FOLDER="./data"
# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename"
# the service fee (in percent)
LNBITS_SERVICE_FEE=0.0
# the wallet where fees go to
# LNBITS_SERVICE_FEE_WALLET=
# the maximum fee per transaction (in satoshis)
# LNBITS_SERVICE_FEE_MAX=1000
# disable fees for internal transactions
# LNBITS_SERVICE_FEE_IGNORE_INTERNAL=true
# value in millisats
LNBITS_RESERVE_FEE_MIN=2000
# value in percent
LNBITS_RESERVE_FEE_PERCENT=1.0
# limit the maximum balance for each wallet
# throw an error if the wallet attempts to create a new invoice
# LNBITS_WALLET_LIMIT_MAX_BALANCE=1000000
# LNBITS_WALLET_LIMIT_DAILY_MAX_WITHDRAW=1000000
# LNBITS_WALLET_LIMIT_SECS_BETWEEN_TRANS=60
# Limit fiat currencies allowed to see in UI
# LNBITS_ALLOWED_CURRENCIES="EUR, USD"
######################################
###### Logging and Development #######
######################################
DEBUG=false
DEBUG_DATABASE=false
BUNDLE_ASSETS=true
# logging into LNBITS_DATA_FOLDER/logs/
ENABLE_LOG_TO_FILE=true
# https://loguru.readthedocs.io/en/stable/api/logger.html#file
LOG_ROTATION="100 MB"
LOG_RETENTION="3 months"
# for database cleanup commands
# CLEANUP_WALLETS_DAYS=90
-16
View File
@@ -1,16 +0,0 @@
[flake8]
max-line-length = 150
exclude = lnbits/wallets/lnd_grpc_files/, lnbits/extensions/
ignore =
# E203 whitespace before ':' black does not like it
E203
# E402: module level import not at top of file
E402,
# W503: line break before binary operator
W503,
# F821: undefined name - should be addressed in future PR
F821,
# E722 do not use bare 'except' - should be addressed in future PR
E722,
# flake8-requirements import checks
I
+1 -1
View File
@@ -1 +1 @@
custom: https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK
custom: https://demo.lnbits.com/lnurlp/link/fH59GD
+5 -4
View File
@@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
title: '[BUG]'
labels: bug
assignees: ''
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -24,8 +24,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- LNbits version: [e.g. 0.9.2 or commit hash]
- Database [e.g. sqlite, postgres]
- LNbits version: [e.g. 0.9.2 or commit hash]
- Database [e.g. sqlite, postgres]
**Additional context**
Add any other context about the problem here.
+1 -2
View File
@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature request]"
title: '[Feature request]'
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
-3
View File
@@ -4,7 +4,4 @@ about: Anything else that you need to say
title: ''
labels: ''
assignees: ''
---
+71
View File
@@ -0,0 +1,71 @@
name: prepare
inputs:
python-version:
description: "Python Version"
required: true
default: "3.10"
poetry-version:
description: "Poetry Version"
default: "1.8.5"
node-version:
description: "Node Version"
default: "20.x"
npm:
description: "use npm"
default: false
type: boolean
runs:
using: "composite"
steps:
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
# cache poetry install via pip
cache: "pip"
- name: Set up Poetry ${{ inputs.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ inputs.poetry-version }}
- name: Setup a local virtual environment (if no poetry.toml file)
shell: bash
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v4
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
shell: bash
run: |
poetry env use python${{ inputs.python-version }}
poetry install
# needed for conv tests
poetry add psycopg2-binary
- name: Use Node.js ${{ inputs.node-version }}
if: ${{ (inputs.npm == 'true') }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- uses: actions/cache@v4
if: ${{ (inputs.npm == 'true') }}
name: Define a cache for the npm based on the dependencies lock file
with:
path: ./node_modules
key: npm-${{ hashFiles('package-lock.json') }}
- name: Install npm packages
if: ${{ (inputs.npm == 'true') }}
shell: bash
run: npm install
+81
View File
@@ -0,0 +1,81 @@
#!/bin/sh
LAUNCH_DIR="$PWD"
# Define persistent storage for extracted LNbits
PERSISTENT_DIR="$HOME/.local/share/lnbits"
# Remove existing LNbits directory before extraction
if [ -d "$PERSISTENT_DIR" ]; then
echo "Removing existing LNbits directory..."
rm -rf "$PERSISTENT_DIR"
fi
# Ensure the persistent directory exists
mkdir -p "$PERSISTENT_DIR"
# Extract LNbits from the AppImage if not already extracted
echo "Extracting LNbits to disk for better performance..."
cp -r "$APPDIR/usr/lnbits"/* "$PERSISTENT_DIR/"
chmod +x "$PERSISTENT_DIR/dist/lnbits"
# Check if the directory exists, and create it if it doesn't
if [ ! -d "$LAUNCH_DIR/lnbits/database" ]; then
mkdir -p "$LAUNCH_DIR/lnbits/database"
echo "Created database directory at $LAUNCH_DIR/lnbits/database"
fi
if [ ! -d "$LAUNCH_DIR/lnbits/extensions" ]; then
mkdir -p "$LAUNCH_DIR/lnbits/extensions"
echo "Created extensions directory at $LAUNCH_DIR/lnbits/extensions"
fi
cd "$PERSISTENT_DIR"
# Export the directory as an environment variable for the app
LNBITS_DATA_FOLDER="${LNBITS_DATA_FOLDER:-$LAUNCH_DIR/lnbits/database}"
LNBITS_EXTENSIONS_PATH="${LNBITS_EXTENSIONS_PATH:-$LAUNCH_DIR/lnbits/extensions}"
export LNBITS_DATA_FOLDER
export LNBITS_EXTENSIONS_PATH
export LNBITS_ADMIN_UI=true
# Define the LNbits URL
URL="http://0.0.0.0:5000"
"./dist/lnbits" "$@" &
LNBITS_PID=$!
# Wait for LNbits to be ready before showing the popup
sleep 3
CLOSED=false
# Function to stop LNbits gracefully
kill_lnbits() {
LN_PIDS=$(lsof -t -i:5000 2>/dev/null) # Capture all PIDs
if [ -n "$LN_PIDS" ]; then
echo "Stopping LNbits (PIDs: $LN_PIDS)..."
kill -2 $LN_PIDS # Send SIGINT to all processes on port 5000
CLOSED=true
fi
}
# Show a GUI with a clickable link to open the browser
if command -v zenity >/dev/null 2>&1; then
while [ "$CLOSED" = false ]; do
zenity --info --title="LNbits" --width=400 --text="<b>LNbits is running.</b>\n\n<a href='$URL'>$URL</a>\n\nClick 'Close Server' to stop LNbits." --ok-label="Close Server"
kill_lnbits
sleep 1
done
elif command -v yad >/dev/null 2>&1; then
while [ "$CLOSED" = false ]; do
yad --title="LNbits" --width=400 --text="<b>LNbits is running.</b>\n\n<a href='$URL'>$URL</a>\n\nClick 'Close Server' to stop LNbits." --button="Close Server":0
kill_lnbits
sleep 1
done
else
echo "No GUI tool found. LNbits is running at $URL"
fi
# Ensure the script doesn't hang after closing
if ps -p $LNBITS_PID >/dev/null 2>&1; then
wait $LNBITS_PID 2>/dev/null || true
fi
@@ -0,0 +1,6 @@
[Desktop Entry]
Name=LNbits
Exec=lnbits
Icon=lnbits
Type=Application
Categories=X-Bitcoin;X-LightningNetwork;X-Finance;Network;
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+95
View File
@@ -0,0 +1,95 @@
name: LNbits CI
on:
push:
branches:
- main
- dev
pull_request:
jobs:
lint:
uses: ./.github/workflows/lint.yml
test-api:
needs: [ lint ]
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
uses: ./.github/workflows/tests.yml
with:
custom-pytest: "poetry run pytest tests/api"
python-version: ${{ matrix.python-version }}
db-url: ${{ matrix.db-url }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-wallets:
needs: [ lint ]
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
uses: ./.github/workflows/tests.yml
with:
custom-pytest: "poetry run pytest tests/wallets"
python-version: ${{ matrix.python-version }}
db-url: ${{ matrix.db-url }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-unit:
needs: [ lint ]
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
db-url: ["", "postgres://lnbits:lnbits@0.0.0.0:5432/lnbits"]
uses: ./.github/workflows/tests.yml
with:
custom-pytest: "poetry run pytest tests/unit"
python-version: ${{ matrix.python-version }}
db-url: ${{ matrix.db-url }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
migration:
needs: [ lint ]
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
uses: ./.github/workflows/migration.yml
with:
python-version: ${{ matrix.python-version }}
openapi:
needs: [ lint ]
uses: ./.github/workflows/make.yml
with:
make: openapi
regtest:
needs: [ lint ]
uses: ./.github/workflows/regtest.yml
strategy:
matrix:
python-version: ["3.10"]
backend-wallet-class: ["LndRestWallet", "LndWallet", "CoreLightningWallet", "CoreLightningRestWallet", "LNbitsWallet", "EclairWallet"]
with:
custom-pytest: "poetry run pytest tests/regtest"
python-version: ${{ matrix.python-version }}
backend-wallet-class: ${{ matrix.backend-wallet-class }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
jmeter:
needs: [ lint ]
strategy:
matrix:
python-version: ["3.10"]
poetry-version: ["1.5.1"]
uses: ./.github/workflows/jmeter.yml
with:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ matrix.poetry-version }}
+4 -4
View File
@@ -2,18 +2,18 @@ name: codeql
on:
push:
branches: [main, ]
branches: [main, dev]
pull_request:
branches: [main]
branches: [main, dev]
schedule:
- cron: '0 12 * * 5'
jobs:
analyze:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 2
- run: git checkout HEAD^2
+53
View File
@@ -0,0 +1,53 @@
name: docker
on:
workflow_dispatch:
inputs:
tag:
default: latest
type: string
workflow_call:
inputs:
tag:
default: latest
type: string
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true
jobs:
push_to_dockerhub:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
id: cache
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits:${{ inputs.tag }}
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
-27
View File
@@ -1,27 +0,0 @@
name: flake8
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
- name: Run tests
run: make flake8
-33
View File
@@ -1,33 +0,0 @@
name: formatting
on: [push, pull_request]
jobs:
checks:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install packages
run: |
poetry install
npm install prettier
- name: Check black
run: make checkblack
- name: Check isort
run: make checkisort
- uses: actions/setup-node@v3
- name: Check prettier
run: make checkprettier
+69
View File
@@ -0,0 +1,69 @@
name: JMeter Extension Tests
on:
workflow_call:
inputs:
python-version:
description: "Python Version"
required: true
default: "3.10"
type: string
poetry-version:
description: "Poetry Version"
required: true
default: "1.5.1"
type: string
jobs:
jmeter:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/prepare
with:
python-version: ${{ inputs.python-version }}
- name: run LNbits
env:
LNBITS_ADMIN_UI: true
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw"
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
run: |
poetry run lnbits &
sleep 10
- name: setup java version
run: |
update-java-alternatives --list
sudo update-java-alternatives --set /usr/lib/jvm/temurin-8-jdk-amd64
java -version
- name: clone lnbits-extensions, install jmeter and run tests
env:
JAVA_HOME: /usr/lib/jvm/temurin-8-jdk-amd64
EXTENSIONS_MANIFEST_PATH: "/lnbits/lnbits-extensions/refs/heads/main/extensions.json"
run: |
git clone https://github.com/lnbits/lnbits-extensions
cd lnbits-extensions
mkdir logs
mkdir reports
make install-jmeter
make start-mirror-server
make test
- name: print lnbits log
if: ${{ always() }}
run: |
# catch up time for lnbits
sleep 1
cat data/logs/debug.log
- name: upload jmeter test results
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: jmeter-extension-test-results
path: |
lnbits-extensions/reports/
lnbits-extensions/logs/
+55
View File
@@ -0,0 +1,55 @@
name: lint
on:
workflow_call:
jobs:
black:
uses: ./.github/workflows/make.yml
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
with:
make: checkblack
python-version: ${{ matrix.python-version }}
ruff:
uses: ./.github/workflows/make.yml
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
with:
make: checkruff
python-version: ${{ matrix.python-version }}
mypy:
uses: ./.github/workflows/make.yml
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
with:
make: mypy
python-version: ${{ matrix.python-version }}
pyright:
uses: ./.github/workflows/make.yml
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
with:
make: pyright
python-version: ${{ matrix.python-version }}
npm: true
prettier:
uses: ./.github/workflows/make.yml
with:
make: checkprettier
npm: true
bundle:
uses: ./.github/workflows/make.yml
with:
make: checkbundle
npm: true
+34
View File
@@ -0,0 +1,34 @@
name: make
on:
workflow_call:
inputs:
make:
description: "make command that is run"
required: true
type: string
npm:
description: "use npm install"
default: false
type: boolean
python-version:
description: "python version"
type: string
default: "3.10"
jobs:
make:
name: ${{ inputs.make }} (${{ inputs.python-version }})
strategy:
matrix:
os-version: ["ubuntu-24.04"]
node-version: ["18.x"]
runs-on: ${{ matrix.os-version }}
steps:
- uses: actions/checkout@v4
- uses: lnbits/lnbits/.github/actions/prepare@dev
with:
python-version: ${{ inputs.python-version }}
node-version: ${{ matrix.node-version }}
npm: ${{ inputs.npm }}
- run: make ${{ inputs.make }}
+37
View File
@@ -0,0 +1,37 @@
name: migration
on:
workflow_call:
inputs:
python-version:
description: "python version"
type: string
default: "3.10"
jobs:
make:
name: migration (${{ inputs.python-version }})
strategy:
matrix:
os-version: ["ubuntu-24.04"]
runs-on: ${{ matrix.os-version }}
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: lnbits
POSTGRES_PASSWORD: lnbits
POSTGRES_DB: migration
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/prepare
with:
python-version: ${{ inputs.python-version }}
- run: make test-migration
-43
View File
@@ -1,43 +0,0 @@
name: migrations
on: [pull_request]
jobs:
sqlite-to-postgres:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: lnbits
POSTGRES_PASSWORD: lnbits
POSTGRES_DB: migration
ports:
# maps tcp port 5432 on service container to the host
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: |
poetry install
sudo apt install unzip
- name: Run migrations
run: |
make test-migration
-27
View File
@@ -1,27 +0,0 @@
name: mypy
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
- name: Run tests
run: make mypy
+40
View File
@@ -0,0 +1,40 @@
name: LNbits CI / nix
# - run : on main, dev, nix and cachix branches when relevant files change
# - cache : on main, dev and cachix branches when relevant files change
on:
push:
branches:
- main
- dev
- nix
- cachix
paths:
- 'flake.nix'
- 'flake.lock'
- 'pyproject.toml'
- 'poetry.lock'
- '.github/workflows/nix.yml'
pull_request:
paths:
- 'flake.nix'
- 'flake.lock'
- 'pyproject.toml'
- 'poetry.lock'
jobs:
nix:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-24.05
- uses: cachix/cachix-action@v15
with:
name: lnbits
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- run: nix build -L
- run: cachix push lnbits ./result
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/cachix'
-69
View File
@@ -1,69 +0,0 @@
name: Build and push Docker image on tag
env:
DOCKER_CLI_EXPERIMENTAL: enabled
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+-*"
jobs:
build:
runs-on: ubuntu-20.04
name: Build and push lnbits image
steps:
- name: Login to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Checkout project
uses: actions/checkout@v3
- name: Import environment variables
id: import-env
shell: bash
run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: Show set environment variables
run: |
printf " TAG: %s\n" "$TAG"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
id: qemu
- name: Setup Docker buildx action
uses: docker/setup-buildx-action@v1
id: buildx
- name: Show available Docker buildx platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Cache Docker layers
uses: actions/cache@v2
id: cache
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Run Docker buildx against tag
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKER_USERNAME }}/lnbits-legend:${TAG} \
--output "type=registry" ./
- name: Run Docker buildx against latest
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKER_USERNAME }}/lnbits-legend:latest \
--output "type=registry" ./
+72
View File
@@ -0,0 +1,72 @@
name: Build LNbits AppImage DMG
on:
release:
types: [published]
jobs:
build-linux-package:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the repository
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
# Step 2: Install Dependencies
- name: Install Dependencies
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
sudo apt-get update
sudo apt-get install -y libfuse2
shell: bash
# Step 3: Clone LNbits Repository
- name: Clone LNbits
run: |
mv .github/packaging packaging
mkdir -p packaging/linux/AppDir/usr
git clone https://github.com/lnbits/lnbits.git packaging/linux/AppDir/usr/lnbits
shell: bash
# Step 4: Make the AppImage Asset
- name: Make Asset
run: |
cd packaging/linux/AppDir/usr/lnbits
poetry install
poetry run pip install pyinstaller
# Build the LNbits binary
poetry run pyinstaller --onefile --name lnbits --hidden-import=embit --collect-all embit --collect-all lnbits --collect-all sqlalchemy --collect-all aiosqlite --hidden-import=passlib.handlers.bcrypt $(poetry run which lnbits)
cd ../../../../..
chmod +x packaging/linux/AppDir/AppRun
chmod +x packaging/linux/AppDir/lnbits.desktop
chmod +x packaging/linux/AppDir/usr/lnbits/dist/lnbits
find packaging/linux/AppDir/usr/lnbits -mindepth 1 -maxdepth 1 \
! -name 'dist' \
! -name 'lnbits' \
-exec rm -rf {} +
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
TAG_NAME=${{ github.event.release.tag_name }}
APPIMAGE_NAME="LNbits-${TAG_NAME}.AppImage"
./appimagetool-x86_64.AppImage --updateinformation "gh-releases-zsync|lnbits|lnbits|latest|*.AppImage.zsync" packaging/linux/AppDir "$APPIMAGE_NAME"
chmod +x "$APPIMAGE_NAME"
echo "APPIMAGE_NAME=$APPIMAGE_NAME" >> $GITHUB_ENV
shell: bash
# Step 5: Upload Linux Release Asset
- name: Upload Linux Release Asset
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ env.APPIMAGE_NAME }}
asset_name: ${{ env.APPIMAGE_NAME }}
asset_content_type: application/octet-stream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-27
View File
@@ -1,27 +0,0 @@
name: pylint
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
- name: Run tests
run: make pylint
-28
View File
@@ -1,28 +0,0 @@
name: pyright
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
npm install
- name: Run tests
run: make pyright
+65 -199
View File
@@ -1,223 +1,89 @@
name: regtest
on: [push, pull_request]
on:
workflow_call:
inputs:
custom-pytest:
description: "Custom pytest arguments"
required: true
type: string
python-version:
default: "3.10"
type: string
os-version:
default: "ubuntu-24.04"
type: string
backend-wallet-class:
required: true
type: string
secrets:
CODECOV_TOKEN:
required: true
jobs:
LndRestWallet:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
regtest:
runs-on: ${{ inputs.os-version }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- uses: actions/checkout@v4
- name: docker build
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
run: |
docker build -t lnbits/lnbits .
- name: Setup Regtest
run: |
docker build -t lnbitsdocker/lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry install
- name: Run tests
- uses: ./.github/actions/prepare
with:
python-version: ${{ inputs.python-version }}
- name: Run pytest
uses: pavelzw/pytest-action@v2
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: LndRestWallet
LNBITS_DATABASE_URL: ${{ inputs.db-url }}
LNBITS_BACKEND_WALLET_CLASS: ${{ inputs.backend-wallet-class }}
LND_REST_ENDPOINT: https://localhost:8081/
LND_REST_CERT: ./docker/data/lnd-1/tls.cert
LND_REST_MACAROON: ./docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
LndWallet:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Setup Regtest
run: |
docker build -t lnbitsdocker/lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry install
- name: Run tests
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: LndWallet
LND_REST_CERT: ./docker/data/lnd-3/tls.cert
LND_REST_MACAROON: ./docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
LND_GRPC_ENDPOINT: localhost
LND_GRPC_PORT: 10009
LND_GRPC_CERT: docker/data/lnd-1/tls.cert
LND_GRPC_MACAROON: docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
CoreLightningWallet:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Setup Regtest
run: |
docker build -t lnbitsdocker/lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry install
- name: Run tests
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: CoreLightningWallet
LND_GRPC_CERT: docker/data/lnd-3/tls.cert
LND_GRPC_MACAROON: docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
LNbitsWallet:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Setup Regtest
run: |
docker build -t lnbitsdocker/lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
docker exec lnbits-legend-lnbits-1 /bin/bash -c "poetry run python tools/create_fake_admin.py"
- name: Install dependencies
run: |
poetry install
- name: Run tests
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: LNbitsWallet
CORELIGHTNING_REST_URL: https://localhost:3001
CORELIGHTNING_REST_MACAROON: ./docker/data/clightning-2-rest/access.macaroon
CORELIGHTNING_REST_CERT: ./docker/data/clightning-2-rest/certificate.pem
LNBITS_ENDPOINT: http://localhost:5001
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
EclairWallet:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Setup Regtest
run: |
docker build -t lnbitsdocker/lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
./tests
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry install
- name: Run tests
env:
PYTHONUNBUFFERED: 1
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: EclairWallet
ECLAIR_URL: http://127.0.0.1:8082
LNBITS_MAX_OUTGOING_PAYMENT_AMOUNT_SATS: 1000000000
LNBITS_MAX_INCOMING_PAYMENT_AMOUNT_SATS: 1000000000
ECLAIR_PASS: lnbits
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
PYTHONUNBUFFERED: 1
DEBUG: true
with:
verbose: true
job-summary: true
emoji: false
click-to-expand: true
custom-pytest: ${{ inputs.custom-pytest }}
report-title: "regtest (${{ inputs.python-version }}, ${{ inputs.backend-wallet-class }}"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
- name: docker lnbits logs
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
run: |
docker logs lnbits-lnbits-1
+32
View File
@@ -0,0 +1,32 @@
name: release-rc
on:
push:
tags:
- "*-rc[0-9]+"
permissions:
contents: write
jobs:
docker:
uses: ./.github/workflows/docker.yml
with:
tag: ${{ github.ref_name }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
pypi:
runs-on: ubuntu-24.04
steps:
- name: Install dependencies for building secp256k1
run: |
sudo apt-get update
sudo apt-get install -y build-essential automake libtool libffi-dev libgmp-dev
- uses: actions/checkout@v4
- name: Build and publish to pypi
uses: JRubics/poetry-publish@v1.15
with:
pypi_token: ${{ secrets.PYPI_API_KEY }}
+54
View File
@@ -0,0 +1,54 @@
name: release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+"
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Create github release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: |
gh release create "$tag" --generate-notes --draft
docker:
needs: [ release ]
uses: ./.github/workflows/docker.yml
with:
tag: ${{ github.ref_name }}
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
docker-latest:
needs: [ release ]
uses: ./.github/workflows/docker.yml
with:
tag: latest
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
pypi:
runs-on: ubuntu-24.04
steps:
- name: Install dependencies for building secp256k1
run: |
sudo apt-get update
sudo apt-get install -y build-essential automake libtool libffi-dev libgmp-dev
- uses: actions/checkout@v4
- name: Build and publish to pypi
uses: JRubics/poetry-publish@v1.15
with:
pypi_token: ${{ secrets.PYPI_API_KEY }}
+51 -49
View File
@@ -1,70 +1,72 @@
name: tests
on: [push, pull_request]
on:
workflow_call:
inputs:
custom-pytest:
description: "Custom pytest arguments"
required: true
type: string
python-version:
default: "3.10"
type: string
os-version:
default: "ubuntu-24.04"
type: string
db-url:
default: ""
type: string
db-name:
default: "lnbits"
type: string
secrets:
CODECOV_TOKEN:
required: true
jobs:
sqlite:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
- name: Run tests
run: make test
postgres:
runs-on: ubuntu-latest
tests:
runs-on: ${{ inputs.os-version }}
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
POSTGRES_USER: lnbits
POSTGRES_PASSWORD: lnbits
POSTGRES_DB: ${{ inputs.db-name }}
ports:
# maps tcp port 5432 on service container to the host
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v3
- name: Set up Poetry ${{ matrix.poetry-version }}
uses: abatilo/actions-poetry@v2
- uses: actions/checkout@v4
- uses: ./.github/actions/prepare
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Install dependencies
run: |
poetry install
- name: Run tests
python-version: ${{ inputs.python-version }}
- name: Run pytest
uses: pavelzw/pytest-action@v2
env:
LNBITS_DATABASE_URL: postgres://postgres:postgres@0.0.0.0:5432/postgres
run: make test
LNBITS_DATABASE_URL: ${{ inputs.db-url }}
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
PYTHONUNBUFFERED: 1
DEBUG: true
with:
verbose: true
job-summary: true
emoji: false
click-to-expand: true
custom-pytest: ${{ inputs.custom-pytest }}
report-title: "test (${{ inputs.python-version }}, ${{ inputs.db-url }})"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
verbose: false
+19 -3
View File
@@ -11,6 +11,7 @@ __pycache__
*.egg
*.egg-info
.coverage
.coverage.*
.pytest_cache
.webassets-cache
htmlcov
@@ -23,6 +24,7 @@ tests/data/*.sqlite3
*.pyc
*.env
.env
.pre-commit-config.yaml
data
*.sqlite3
@@ -32,8 +34,16 @@ __bundle__
coverage.xml
node_modules
lnbits/static/bundle.*
lnbits/static/bundle.js
lnbits/static/bundle-components.js
lnbits/static/bundle.css
lnbits/static/bundle.min.js.old
lnbits/static/bundle.min.css.old
lnbits/static/bundle-components.min.js.old
lnbits/upgrades
docker
generated
openapi.json*
# Nix
*result*
@@ -44,5 +54,11 @@ fly.toml
lnbits-backup.zip
# Ignore extensions (post installable extension PR)
extensions/
upgrades/
/lnbits/extensions
/lnbits/upgrades/
# builded python package
dist
# jetbrains
.idea
+30
View File
@@ -0,0 +1,30 @@
exclude: '^lnbits/static/bundle.*|^docs/.*|^lnbits/static/vendor/.*|^lnbits/extensions/.*|^lnbits/upgrades/.*|^lnbits/wallets/lnd_grpc_files/.*|^package-lock.json$'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-docstring-first
- id: check-json
- id: debug-statements
- id: mixed-line-ending
- id: check-case-conflict
- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v4.0.0-alpha.8"
hooks:
- id: prettier
types_or: [css, javascript, html, json]
args: ['lnbits']
+6
View File
@@ -6,7 +6,13 @@
*.yml
**/lnbits/extensions/*
**/lnbits/upgrades/*
**/lnbits/static/vendor
**/lnbits/static/bundle.*
**/lnbits/static/bundle-components.*
**/lnbits/static/css/*
flake.lock
.venv
+36 -11
View File
@@ -1,26 +1,51 @@
FROM python:3.10-slim
FROM python:3.12-slim-bookworm AS builder
RUN apt-get clean
RUN apt-get update
RUN apt-get install -y curl pkg-config build-essential
RUN apt-get install -y curl pkg-config build-essential libnss-myhostname
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
ENV PATH="/root/.local/bin:$PATH"
WORKDIR /app
# Only copy the files required to install the dependencies
COPY pyproject.toml poetry.lock ./
RUN mkdir data
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
RUN poetry install --only main
FROM python:3.12-slim-bookworm
# needed for backups postgresql-client version 14 (pg_dump)
RUN apt-get install -y wget
RUN echo "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" > /etc/apt/sources.list.d/pgdg.list
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN apt-get update
RUN apt-get install -y postgresql-client-14
RUN apt-get update && apt-get -y upgrade && \
apt-get -y install gnupg2 curl lsb-release && \
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
apt-get update && \
apt-get -y install postgresql-client-14 postgresql-client-common && \
apt-get clean all && rm -rf /var/lib/apt/lists/*
RUN curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
ENV PATH="/root/.local/bin:$PATH"
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
WORKDIR /app
COPY . .
COPY --from=builder /app/.venv .venv
RUN mkdir data
RUN poetry config virtualenvs.create false
RUN poetry install --only main
ENV LNBITS_PORT="5000"
+91 -46
View File
@@ -2,91 +2,136 @@
all: format check
format: prettier isort black
format: prettier black ruff
check: mypy pyright pylint flake8 checkisort checkblack checkprettier
check: mypy pyright checkblack checkruff checkprettier checkbundle
test: test-unit test-wallets test-api test-regtest
prettier:
poetry run ./node_modules/.bin/prettier --write lnbits
poetry run ./node_modules/.bin/prettier --write .
pyright:
poetry run ./node_modules/.bin/pyright
black:
poetry run black .
flake8:
poetry run flake8
mypy:
poetry run mypy
isort:
poetry run isort .
black:
poetry run black .
pylint:
poetry run pylint *.py lnbits/ tools/ tests/
ruff:
poetry run ruff check . --fix
checkruff:
poetry run ruff check .
checkprettier:
poetry run ./node_modules/.bin/prettier --check lnbits
poetry run ./node_modules/.bin/prettier --check .
checkblack:
poetry run black --check .
checkisort:
poetry run isort --check-only .
checkeditorconfig:
editorconfig-checker
test:
dev:
poetry run lnbits --reload
docker:
docker build -t lnbits/lnbits .
test-wallets:
LNBITS_DATA_FOLDER="./tests/data" \
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
FAKE_WALLET_SECRET="ToTheMoon1" \
LNBITS_DATA_FOLDER="./tests/data" \
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest
poetry run pytest tests/wallets
test-real-wallet:
test-unit:
LNBITS_DATA_FOLDER="./tests/data" \
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest tests/unit
test-api:
LNBITS_DATA_FOLDER="./tests/data" \
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest tests/api
test-regtest:
LNBITS_DATA_FOLDER="./tests/data" \
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest
poetry run pytest tests/regtest
test-migration:
rm -rf ./migration-data
mkdir -p ./migration-data
unzip tests/data/mock_data.zip -d ./migration-data
LNBITS_ADMIN_UI=True \
make test-api
HOST=0.0.0.0 \
PORT=5002 \
LNBITS_DATA_FOLDER="./migration-data" \
LNBITS_DATA_FOLDER="./tests/data" \
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
HOST=0.0.0.0 \
PORT=5002 \
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
LNBITS_ADMIN_UI=False \
timeout 5s poetry run lnbits --host 0.0.0.0 --port 5002 || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi
LNBITS_DATA_FOLDER="./migration-data" \
LNBITS_DATA_FOLDER="./tests/data" \
LNBITS_DATABASE_URL="postgres://lnbits:lnbits@localhost:5432/migration" \
poetry run python tools/conv.py
migration:
poetry run python tools/conv.py
bak:
# LNBITS_DATABASE_URL=postgres://postgres:postgres@0.0.0.0:5432/postgres
#
openapi:
LNBITS_ADMIN_UI=False \
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
LNBITS_DATA_FOLDER="./tests/data" \
PYTHONUNBUFFERED=1 \
HOST=0.0.0.0 \
PORT=5003 \
poetry run lnbits &
sleep 15
curl -s http://0.0.0.0:5003/openapi.json | poetry run openapi-spec-validator --errors=all -
# kill -9 %1
updatevendor:
generate:
rm -rf generated
mkdir generated
rm -f openapi.json
wget localhost:5000/openapi.json
npm run generate
rm openapi.json
sass:
npm run sass
bundle:
npm install
cp ./node_modules/moment/moment.js ./lnbits/static/vendor/
cp ./node_modules/underscore/underscore.js ./lnbits/static/vendor/
cp ./node_modules/axios/dist/axios.js ./lnbits/static/vendor/
cp ./node_modules/vue/dist/vue.js ./lnbits/static/vendor/
cp ./node_modules/vue-router/dist/vue-router.js ./lnbits/static/vendor/
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.browser.js ./lnbits/static/vendor/
cp ./node_modules/@chenfengyuan/vue-qrcode/dist/vue-qrcode.js ./lnbits/static/vendor/
cp ./node_modules/vuex/dist/vuex.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.ie.polyfills.umd.min.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.umd.js ./lnbits/static/vendor/
cp ./node_modules/chart.js/dist/Chart.bundle.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.css ./lnbits/static/vendor/
cp ./node_modules/chart.js/dist/Chart.css ./lnbits/static/vendor/
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.css ./lnbits/static/vendor/
cp ./node_modules/vue-i18n/dist/vue-i18n.js ./lnbits/static/vendor/
npm run bundle
poetry run ./node_modules/.bin/prettier -w ./lnbits/static/vendor.json
checkbundle:
cp lnbits/static/bundle.min.js lnbits/static/bundle.min.js.old
cp lnbits/static/bundle.min.css lnbits/static/bundle.min.css.old
cp lnbits/static/bundle-components.min.js lnbits/static/bundle-components.min.js.old
make bundle
diff -q lnbits/static/bundle.min.js lnbits/static/bundle.min.js.old || exit 1
diff -q lnbits/static/bundle.min.css lnbits/static/bundle.min.css.old || exit 1
diff -q lnbits/static/bundle-components.min.js lnbits/static/bundle-components.min.js.old || exit 1
@echo "Bundle is OK"
rm lnbits/static/bundle.min.js.old
rm lnbits/static/bundle.min.css.old
rm lnbits/static/bundle-components.min.js.old
install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall"
poetry run pre-commit install
pre-commit:
poetry run pre-commit run --all-files
+38 -37
View File
@@ -1,31 +1,28 @@
LNbits
======
<picture >
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/QE6SIrs.png" style="width:300px">
<img src="https://i.imgur.com/fyKPgVT.png" style="width:300px">
</picture>
[![license-badge]](LICENSE)
[![docs-badge]][docs]
![phase: beta](https://img.shields.io/badge/phase-beta-C41E3A) [![license-badge]](LICENSE) [![docs-badge]][docs] ![PRs: welcome](https://img.shields.io/badge/PRs-Welcome-08A04B) [<img src="https://img.shields.io/badge/community_chat-Telegram-24A1DE">](https://t.me/lnbits) [<img src="https://img.shields.io/badge/supported_by-%3E__OpenSats-f97316">](https://opensats.org)
![Lightning network wallet](https://i.imgur.com/DeIiO0y.png)
# The world's most powerful suite of bitcoin tools.
![Lightning network wallet](https://i.imgur.com/EHvK6Lq.png)
## Run for yourself, for others, or as part of a stack.
# LNbits v0.10 BETA, free and open-source Lightning wallet accounts system
(Join us on [https://t.me/lnbits](https://t.me/lnbits))
LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me
Use [legend.lnbits.com](https://legend.lnbits.com), or run your own LNbits server!
LNbits is beta, for responsible disclosure of any concerns please contact an admin in the community chat.
LNbits is a Python server that sits on top of any funding source. It can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet
* Extendable platform for exploring Lightning network functionality via the LNbits extension framework
* Part of a development stack via LNbits API
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
- Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet
- Extendable platform for exploring Lightning network functionality via the LNbits extension framework
- Part of a development stack via LNbits API
- Fallback wallet for the LNURL scheme
- Instant wallet for LN demonstrations
LNbits can run on top of any Lightning funding source. It supports LND, CLN, Eclair, Spark, LNpay, OpenNode, LightningTipBot, and with more being added regularly.
LNbits can run on top of almost all Lightning funding sources.
See [LNbits Wiki](https://github.com/lnbits/lnbits/wiki/) for more detailed documentation.
See [LNbits manual](https://docs.lnbits.org/guide/wallets.html) for more detailed documentation about each funding source.
Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series.
@@ -33,41 +30,45 @@ LNbits is inspired by all the great work of [opennode.com](https://www.opennode.
## Running LNbits
See the [install guide](https://github.com/lnbits/lnbits/wiki/LNbits-Documentation#install-options) for details on installation and setup.
Test on our demo server [demo.lnbits.com](https://demo.lnbits.com), or on [lnbits.com](https://lnbits.com) software as a service, where you can spin up an LNbits instance for 21sats per hr.
## LNbits as an account system
See the [install guide](https://github.com/lnbits/lnbits/blob/main/docs/guide/installation.md) for details on installation and setup.
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv + more to come..
## LNbits account system
![Lightning network wallet](https://i.imgur.com/w8jdGpF.png)
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending, export to csv. Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
Each wallet also comes with its own API keys, to help partition the exposure of your funding source.
<img src="https://i.imgur.com/w8jdGpF.png" style="width:800px">
(LNbits M5StackSats available here https://github.com/arcbtc/M5StackSats)
## LNbits extension universe
![lnurl ATM](https://i.imgur.com/WfCg8wY.png)
Extend YOUR LNbits to meet YOUR needs.
## LNbits as an LNURL-withdraw fallback
All non-core features are installed as extensions, reducing your code base and making your LNbits unique to you. Extend your LNbits install in any direction, and even create and share your own extensions.
LNURL has a fallback scheme, so if scanned by a regular QR code reader it can default to a URL. LNbits exploits this to generate an instant wallet using the [LNURL-withdraw](https://github.com/btcontract/lnurl-rfc/blob/master/lnurl-withdraw.md).
<img src="https://i.imgur.com/aEBpwJF.png" style="width:800px">
![lnurl fallback](https://i.imgur.com/CPBKHIv.png)
## LNbits API
Using **lnbits.com/?lightning="LNURL-withdraw"** will trigger a withdraw that builds an LNbits wallet.
Example use would be an ATM, which utilises LNURL, if the user scans the QR with a regular QR code scanner app, they will still be able to access the funds.
LNbits has a powerful API, many projects use LNbits to do the heavy lifting for their bitcoin/lightning services.
![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg)
<img src="https://i.imgur.com/V742sb9.png" style="width:800px">
## LNbits as an instant wallet
## LNbits node manager
Wallets can be easily generated and given out to people at events. "Go to this website", has a lot less friction than "Download this app".
LNbits comes packaged with a light node management UI, to make running your node that much easier.
![lnurl ATM](https://i.imgur.com/xFWDnwy.png)
<img src="https://i.imgur.com/TYqIK60.png" style="width:800px">
## LNbits across all your devices
As well as working great in a browser, LNbits has native IoS and Android apps as well as a chrome extension. So you can enjoy the same UI across ALL your devices.
<img src="https://i.imgur.com/J96EbRf.png" style="width:800px">
## Tip us
If you like this project [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)!
If you like this project [send some tip love](https://demo.lnbits.com/lnurlp/link/fH59GD)!
[docs]: https://github.com/lnbits/lnbits/wiki
[docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg
+1 -1
View File
@@ -1 +1 @@
docs.lnbits.org
docs.lnbits.org
+1 -1
View File
@@ -1,7 +1,7 @@
title: "LNbits docs"
remote_theme: pmarsceill/just-the-docs
color_scheme: dark
logo: "/logos/lnbits-full--inverse.png"
logo: "/logos/lnbits-full-inverse.png"
search_enabled: true
url: https://docs.lnbits.org
aux_links:
+2 -4
View File
@@ -5,8 +5,6 @@ title: API reference
nav_order: 3
---
# API reference
API reference
=============
[Swagger Docs](https://docs.lnbits.org/devs/swagger.html)
[Swagger Docs](https://demo.lnbits.com/docs)
+42 -5
View File
@@ -5,38 +5,75 @@ nav_order: 4
has_children: true
---
For developers
==============
# For developers
Thanks for contributing :)
# Run
Tests
=====
Follow the [Basic installation: Option 1 (recommended): poetry](https://docs.lnbits.org/guide/installation.html#option-1-recommended-poetry)
guide to install poetry and other dependencies.
Then you can start LNbits uvicorn server with:
```bash
poetry run lnbits
```
Or you can use the following to start uvicorn with hot reloading enabled:
```bash
make dev
# or
poetry run lnbits --reload
```
You might need the following extra dependencies on clean installation of Debian:
```
sudo apt install nodejs
sudo apt install npm
npm install
sudo apt-get install autoconf libtool libpg-dev
```
# Precommit hooks
This ensures that all commits adhere to the formatting and linting rules.
```bash
make install-pre-commit-hook
```
# Tests
This project has unit tests that help prevent regressions. Before you can run the tests, you must install a few dependencies:
```bash
poetry install
npm i
```
Then to run the tests:
```bash
make test
```
Run formatting:
```bash
make format
```
Run mypy checks:
```bash
poetry run mypy
```
Run everything:
```bash
make all
```
+33 -17
View File
@@ -5,33 +5,42 @@ title: Making extensions
nav_order: 2
---
# Extension set up
Making extensions
=================
Start off by creating a fork of the [example extension](https://github.com/lnbits/example) into own GitHub repository and rename the repository to `mysuperplugin`:
Start off by cloning the [example extension](https://github.com/lnbits/example) into your `lnbits/extensions` folder and renaming it to `mysuperplugin`:
```sh
cd lnbits/extensions
git clone https://github.com/lnbits/example.git --depth=1 mysuperplugin # Let's not use dashes or anything; it doesn't like those.
cd [my-working-folder]
git clone https://github.com/[my-user-name]/mysuperplugin.git --depth=1 # Let's not use dashes or anything; it doesn't like those.
cd mysuperplugin
rm -rf .git/
find . -type f -print0 | xargs -0 sed -i 's/example/mysuperplugin/g' # Change all occurrences of 'example' to your plugin name 'mysuperplugin'.
mv templates/example templates/mysuperplugin # Rename templates folder.
```
- if you are on macOS and having difficulty with 'sed', consider `brew install gnu-sed` and use 'gsed', without -0 option after xargs.
Going over the example extension's structure:
* views_api.py: This is where your public API would go. It will be exposed at "$DOMAIN/$PLUGIN/$ROUTE". For example: https://lnbits.com/mysuperplugin/api/v1/tools.
* views.py: The `/` path will show up as your plugin's home page in lnbits' UI. Other pages you can define yourself. The `templates` folder should explain itself in relation to this.
* migrations.py: Create database tables for your plugin. They'll be created automatically when you start lnbits.
1. Edit `manifest.json` and change the organisation name to your GitHub username.
1. Push your changes to GitHub.
1. In GitHub create a new release for your extension repo. Tag the release with `0.0.1`
1. Copy the URL of the extension's raw `manifest.json` URL `https://raw.githubusercontent.com/[my-user-name]/mysuperplugin/master/manifest.json`
1. If you are using the LMNbits Admin UI, go to the Admin UI > Server > Extension Sources, click "Add", paste the URL, then click "Save"
1. If you are configuring LNbits via environment variables, add the URL to the .env file's `LNBITS_EXTENSIONS_MANIFESTS` variable. Restart the LNbits python process
1. You will now see your extension in the LNbits > Extensions list. Click "Enable" to enable it.
1. ...
1. Profit!!!
## Extension structure explained
- views_api.py: This is where your public API would go. It will be exposed at "$DOMAIN/$PLUGIN/$ROUTE". For example: https://lnbits.com/mysuperplugin/api/v1/tools.
- views.py: The `/` path will show up as your plugin's home page in lnbits' UI. Other pages you can define yourself. The `templates` folder should explain itself in relation to this.
- migrations.py: Create database tables for your plugin. They'll be created automatically when you start lnbits.
... This document is a work-in-progress. Send pull requests if you get stuck, so others don't.
## Adding new dependencies
Adding new dependencies
-----------------------
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are available in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `poerty`:
@@ -40,11 +49,9 @@ $ poetry add <package>
```
**But we need an extra step to make sure LNbits doesn't break in production.**
Dependencies need to be added to `pyproject.toml`, then tested by running on `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
Dependencies need to be added to `pyproject.toml`, then tested by running on `poetry` compatibility can be tested with `nix build .#checks.x86_64-linux.vmTest`.
SQLite to PostgreSQL migration
-----------------------
## SQLite to PostgreSQL migration
LNbits currently supports SQLite and PostgreSQL databases. There is a migration script `tools/conv.py` that helps users migrate from SQLite to PostgreSQL. This script also copies all extension databases to the new backend.
@@ -53,22 +60,31 @@ LNbits currently supports SQLite and PostgreSQL databases. There is a migration
`mock_data.zip` contains a few lines of sample SQLite data and is used in automated GitHub test to see whether your migration in `conv.py` works. Run your extension and save a few lines of data into a SQLite `your_extension.sqlite3` file. Unzip `tests/data/mock_data.zip`, add `your_extension.sqlite3`, updated `database.sqlite3` and zip it again. Add the updated `mock_data.zip` to your PR.
### running migration locally
you will need a running postgres database
#### create lnbits user for migration database
```console
sudo su - postgres -c "psql -c 'CREATE ROLE lnbits LOGIN PASSWORD 'lnbits';'"
```
#### create migration database
```console
sudo su - postgres -c "psql -c 'CREATE DATABASE migration;'"
```
#### run the migration
```console
make test-migration
```
sudo su - postgres -c "psql -c 'CREATE ROLE lnbits LOGIN PASSWORD 'lnbits';'"
#### clean migration database afterwards, fails if you try again
```console
sudo su - postgres -c "psql -c 'DROP DATABASE IF EXISTS migration;'"
```
+30 -27
View File
@@ -1,29 +1,32 @@
<html>
<head>
<!-- Load the latest Swagger UI code and style from npm using unpkg.com -->
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css"/>
<title>My New API</title>
</head>
<body>
<div id="swagger-ui"></div> <!-- Div to hold the UI component -->
<script>
window.onload = function () {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "https://legend.lnbits.com/openapi.json", //Location of Open API spec in the repo
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
})
window.ui = ui
}
</script>
</body>
<head>
<!-- Load the latest Swagger UI code and style from npm using unpkg.com -->
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<link
rel="stylesheet"
type="text/css"
href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css"
/>
<title>My New API</title>
</head>
<body>
<div id="swagger-ui"></div>
<!-- Div to hold the UI component -->
<script>
window.onload = function () {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: 'https://demo.lnbits.com/openapi.json', //Location of Open API spec in the repo
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
plugins: [SwaggerUIBundle.plugins.DownloadUrl]
})
window.ui = ui
}
</script>
</body>
</html>
+3 -6
View File
@@ -5,14 +5,11 @@ title: Websockets
nav_order: 2
---
# Websockets
Websockets
=================
`websockets` are a great way to add a two way instant data channel between server and client.
LNbits has a useful in built websocket tool. With a websocket client connect to (obv change `somespecificid`) `wss://legend.lnbits.com/api/v1/ws/somespecificid` (you can use an online websocket tester). Now make a get to `https://legend.lnbits.com/api/v1/ws/somespecificid/somedata`. You can send data to that websocket by using `from lnbits.core.services import websocketUpdater` and the function `websocketUpdater("somespecificid", "somdata")`.
`websockets` are a great way to add a two way instant data channel between server and client.
LNbits has a useful in built websocket tool. With a websocket client connect to (obv change `somespecificid`) `wss://demo.lnbits.com/api/v1/ws/somespecificid` (you can use an online websocket tester). Now make a get to `https://demo.lnbits.com/api/v1/ws/somespecificid/somedata`. You can send data to that websocket by using `from lnbits.core.services import websocketUpdater` and the function `websocketUpdater("somespecificid", "somdata")`.
Example vue-js function for listening to the websocket:
+28 -22
View File
@@ -4,17 +4,15 @@ title: Admin UI
nav_order: 4
---
# Admin UI
Admin UI
========
The LNbits Admin UI lets you change LNbits settings via the LNbits frontend.
It is disabled by default and the first time you set the environment variable LNBITS_ADMIN_UI=true
It is disabled by default and the first time you set the environment variable `LNBITS_ADMIN_UI=true`
the settings are initialized and saved to the database and will be used from there as long the UI is enabled.
From there on the settings from the database are used.
# Super User
Super User
==========
With the Admin UI we introduced the super user, it is created with the initialisation of the Admin UI and will be shown with a success message in the server logs.
The super user has access to the server and can change settings that may crash the server and make it unresponsive via the frontend and api, like changing funding sources.
@@ -29,44 +27,52 @@ We also added a decorator for the API routes to check for super user.
There is also the possibility of posting the super user via webhook to another service when it is created. you can look it up here https://github.com/lnbits/lnbits/blob/main/lnbits/settings.py `class SaaSSettings`
# Admin Users
Admin Users
===========
environment variable: LNBITS_ADMIN_USERS, comma-separated list of user ids
Admin Users can change settings in the admin ui as well, with the exception of funding source settings, because they require e server restart and could potentially make the server inaccessable. Also they have access to all the extension defined in LNBITS_ADMIN_EXTENSIONS.
environment variable: `LNBITS_ADMIN_USERS`, comma-separated list of user ids
Admin Users can change settings in the admin ui as well, with the exception of funding source settings, because they require e server restart and could potentially make the server inaccessible. Also they have access to all the extension defined in `LNBITS_ADMIN_EXTENSIONS`.
# Allowed Users
Allowed Users
=============
environment variable: LNBITS_ALLOWED_USERS, comma-separated list of user ids
environment variable: `LNBITS_ALLOWED_USERS`, comma-separated list of user ids
By defining this users, LNbits will no longer be usable by the public, only defined users and admins can then access the LNbits frontend.
Setting this environment variable also disables account creation.
Account creation can be also disabled by setting `LNBITS_ALLOW_NEW_ACCOUNTS=false`
# How to activate
How to activate
=============
```
$ sudo systemctl stop lnbits.service
$ cd ~/lnbits-legend
$ cd ~/lnbits
$ sudo nano .env
```
-> set: `LNBITS_ADMIN_UI=true`
Now start LNbits once in the terminal window
```
$ poetry run lnbits
```
$ poetry run lnbits
```
It will now show you the Super User Account:
`SUCCESS | ✔️ Access super user account at: https://127.0.0.1:5000/wallet?usr=5711d7..`
You can now `cat` the Super User ID:
The `/wallet?usr=..` is your super user account. You just have to append it to your normal LNbits web domain.
```
$ cat data/.super_user
123de4bfdddddbbeb48c8bc8382fe123
```
After that you will find the __`Admin` / `Manage Server`__ between `Wallets` and `Extensions`
You can access your super user account at `/wallet?usr=super_user_id`. You just have to append it to your normal LNbits web domain.
Here you can design the interface, it has TOPUP to fill wallets and you can restrict access rights to extensions only for admins or generally deactivated for everyone. You can make users admins or set up Allowed Users if you want to restrict access. And of course the classic settings of the .env file, e.g. to change the funding source wallet or set a charge fee.
After that you will find the **`Admin` / `Manage Server`** between `Wallets` and `Extensions`
Here you can design the interface, it has credit/debit to change wallets balances and you can restrict access rights to extensions only for admins or generally deactivated for everyone. You can make users admins or set up Allowed Users if you want to restrict access. And of course the classic settings of the .env file, e.g. to change the funding source wallet or set a charge fee.
Do not forget
```
sudo systemctl start lnbits.service
```
A little hint, if you set `RESET TO DEFAULTS`, then a new Super User Account will also be created. The old one is then no longer valid.
+35 -34
View File
@@ -10,16 +10,16 @@ Go to `Manage Server` > `Server` > `Extensions Manifests`
![image](https://user-images.githubusercontent.com/2951406/213494038-e8152d8e-61f2-4cb7-8b5f-361fc3f9a31f.png)
An `Extension Manifest` is a link to a `JSON` file which contains information about various extensions that can be installed (repository of extensions).
Multiple repositories can be configured. For more information check the [Manifest File](https://github.com/lnbits/lnbits/blob/main/docs/guide/extension-install.md#manifest-file) section.
**LNbits** administrators should configure their instances to use repositories that they trust (like the [lnbits-extensions](https://github.com/lnbits/lnbits-extensions/) one).
**LNbits** administrators should configure their instances to use repositories that they trust (like the [lnbits-extensions](https://github.com/lnbits/lnbits-extensions/) one).
> **Warning**
> Extensions can have bugs or malicious code, be careful what you install!!
## Install New Extension
Only administrator users can install or upgrade extensions.
Go to `Manage Extensions` > `Add Remove Extensions`
@@ -40,18 +40,17 @@ Click the `Manage` button in order to install a particular release of the extens
Select the version to be installed (usually the last one) and click `Install`. One can also check the `Release Notes` first.
> **Note**:
>
>
> For Github repository: the order of the releases is the one in the GitHub releases page
>
>
> For Explicit Release: the order of the releases is the one in the "extensions" object
The extension has been installed but it cannot be accessed yet. In order to activate the extension toggle it in the `Activated` state.
Go to `Manage Extensions` (as admin user or regular user). Search for the extension and enable it.
## Uninstall Extension
On the `Install` page click `Manage` for the extension you want to uninstall:
![image](https://user-images.githubusercontent.com/2951406/213653194-32cbb1da-dcc8-43cf-8a82-1ec5d2d3dc16.png)
@@ -65,6 +64,7 @@ Users will no longer be able to access the extension.
> The database for the extension is not removed. If the extension is re-installed later, the data will be accessible.
## Manifest File
The manifest file is just a `JSON` file that lists a collection of extensions that can be installed. This file is of the form:
```json
@@ -77,30 +77,32 @@ The manifest file is just a `JSON` file that lists a collection of extensions th
There are two ways to specify installable extensions:
### Explicit Release
It goes under the `extensions` object and it is of the form:
```json
{
"id": "lnurlp",
"name": "LNURL Pay Links",
"version": 1,
"shortDescription": "Upgrade to version 111111111",
"icon": "receipt",
"details": "All charge names should be <code>111111111</code>. API panel must show: <br>",
"archive": "https://github.com/lnbits/lnbits-extensions/raw/main/new/lnurlp/1/lnurlp.zip",
"hash": "a22d02de6bf306a7a504cd344e032cc6d48837a1d4aeb569a55a57507bf9a43a",
"htmlUrl": "https://github.com/lnbits/lnbits-extensions/tree/main/new/lnurlp/1",
"infoNotification": "This is a very old version",
"dependencies": ["other-ext-id"]
}
{
"id": "lnurlp",
"name": "LNURL Pay Links",
"version": 1,
"shortDescription": "Upgrade to version 111111111",
"icon": "receipt",
"details": "All charge names should be <code>111111111</code>. API panel must show: <br>",
"archive": "https://github.com/lnbits/lnbits-extensions/raw/main/new/lnurlp/1/lnurlp.zip",
"hash": "a22d02de6bf306a7a504cd344e032cc6d48837a1d4aeb569a55a57507bf9a43a",
"htmlUrl": "https://github.com/lnbits/lnbits-extensions/tree/main/new/lnurlp/1",
"infoNotification": "This is a very old version",
"dependencies": ["other-ext-id"]
}
```
<details><summary>Fields Detailed Description</summary>
| Field | Type | | Description |
|----------------------|---------------|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| id | string | mandatory | The ID of the extension. Must be unique for each extension. It is also used as the path in the URL. |
| -------------------- | ------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | string | mandatory | The ID of the extension. Must be unique for each extension. It is also used as the path in the URL. |
| name | string | mandatory | User friendly name for the extension. It will be displayed on the installation page. |
| version | string | mandatory | Version of this release. [Semantic versioning](https://semver.org/) is recommended. |
| version | string | mandatory | Version of this release. [Semantic versioning](https://semver.org/) is recommended. |
| shortDescription | string | optional | A few words about the extension. It will be displayed on the installation page. |
| icon | string | optional | quasar valid icon name |
| details | string (html) | optional | Details about this particular release |
@@ -109,31 +111,30 @@ It goes under the `extensions` object and it is of the form:
| htmlUrl | string | optional | Link to the extension home page. |
| infoNotification | string | optional | Users that have this release installed will see a info message for their extension. For example if the extension support will be terminated soon. |
| criticalNotification | string | optional | Reserved for urgent notifications. The admin user will receive a message each time it visits the `Install` page. One example is if the extension has a critical bug. |
| dependencies | list | optional | A list of extension IDs. It signals that those extensions must be installed BEFORE the this one can be installed.
| dependencies | list | optional | A list of extension IDs. It signals that those extensions must be installed BEFORE the this one can be installed. |
</details>
This mode has the advantage of strictly specifying what releases of an extension can be installed.
### GitHub Repository
It goes under the `repos` object and it is of the form:
```json
{
"id": "withdraw",
"organisation": "lnbits",
"repository": "withdraw-extension"
"id": "withdraw",
"organisation": "lnbits",
"repository": "withdraw-extension"
}
```
| Field | Type | Description |
|--------------|--------|-------------------------------------------------------|
| id | string | The ID of the extension. Must be unique for each extension. It is also used as the path in the URL. |
| organisation | string | The GitHub organisation (eg: `lnbits`) |
| repository | string | The GitHub repository name (eg: `withdraw-extension`) |
| Field | Type | Description |
| ------------ | ------ | --------------------------------------------------------------------------------------------------- |
| id | string | The ID of the extension. Must be unique for each extension. It is also used as the path in the URL. |
| organisation | string | The GitHub organisation (eg: `lnbits`) |
| repository | string | The GitHub repository name (eg: `withdraw-extension`) |
The admin user will see all releases from the Github repository:
![image](https://user-images.githubusercontent.com/2951406/213508934-11de5ae5-2045-471c-854b-94b6acbf4434.png)
+37 -34
View File
@@ -4,10 +4,10 @@ title: FAQ
nav_order: 5
---
# FAQ - Frequently Asked Questions
## Install options
<ul><p>LNbits is not a node management software but a ⚡️LN only accounting system on top of a funding source.</p>
<details><summary>Funding my LNbits wallet from my node it doesn't work.</summary>
@@ -19,24 +19,24 @@ allow-self-payment=1
```
</details>
<details><summary>Funding source only available via tor (e.g. Umbrel)</summary>
<details><summary>Funding source only available via tor (e.g. Start9 or Umbrel)</summary>
<p>If you want your setup to stay behind tor then only apps, pos and wallets that have tor activated can communicate with your wallets. Most likely you will have trouble when people try to redeem your voucher through onion or when importing your lnbits wallets into a wallet-app that doesnt support tor. If you plan to let LNbits wallets interact with plain internet shops and services you should consider <a href="https://github.com/TrezorHannes/Dual-LND-Hybrid-VPS">setting up hybrid mode for your node</a>.</p>
</details>
<details><summary>Funding source is in a cloud</summary>
<p>This means that you might not have access to some files which would allow certain administrative functions. E.g. on <a href="https://voltage.cloud/">Voltage</a> lnd.conf can not be edited. Payments from your node to LNbits wallets can therefore not be configurated in this case atm so you will need to take an extra wallet to send from funding source->wallet x->LNbits wallet (only) for the initial funding of the wallet.</p>
</details>
<details><summary>LNbits via clearnet domain</summary>
<p><a href="https://github.com/TrezorHannes/Dual-LND-Hybrid-VPS">Step by step guide how to convert your Tor only node</a> into a clearnet node to make apps like LNbits accessible via https.</p>
</details>
<details><summary>Which funding sources can I use for LNbits?</summary>
<p>There are several ways to run a LNbits instance funded from different sources. It is important to choose a source that has a good liquidity and good peers connected. If you use LNbits for public services your users´ payments can then flow happily in both directions. If you would like to fund your LNbits wallet via btc please see section Troubleshooting.</p>
<p>The <a href="http://docs.lnbits.org/guide/wallets.html">LNbits manual</a> shows you which sources can be used and how to configure each: CLN, LND, LNPay, Cliche, OpenNode as well as bots.</p>
<p>The <a href="https://docs.lnbits.org/guide/wallets.html">LNbits manual</a> shows you which sources can be used and how to configure each.</p>
</details>
<!--Later to be added
<details><summary>Advanced setup options</summary>
<p>more text coming soon...</p>
@@ -47,13 +47,14 @@ allow-self-payment=1
<p>When you run your LNbits in clearnet basically everyone can generate a wallet on it. Since the funds of your node are bound to these wallets you might want to prevent that. There are two ways to do so:</p>
<ul>
<li>Configure allowed users & extensions <a href="https://github.com/lnbits/lnbits/blob/main/.env.example">in the .env file</a></li>
<li>Configure allowed users & extensions <a href="https://github.com/lnbits/usermanager">via the Usermanager-Extension</a>. You can find <a href="http://docs.lnbits.org/guide/admin_ui.html">more info about the superuser and the admin extension here</a></li>
<li>Configure allowed users & extensions <a href="https://github.com/lnbits/usermanager">via the Usermanager-Extension</a>. You can find <a href="https://docs.lnbits.org/guide/admin_ui.html">more info about the superuser and the admin extension here</a></li>
</ul>
<p>Please note that all entries in the .env file will not be the taken into account once you activated the admin extension.</p>
</details>
</ul>
## Troubleshooting
<ul><details><summary>Message "https error" or network error" when scanning a LNbits QR</summary>
<p>Bad news, this is a routing error that might have quite a lot of reasons. Let´s try a few of the most possible problems and their solutions. First choose your setup</p>
<ul>
@@ -61,7 +62,7 @@ allow-self-payment=1
<details><summary>LNbits is running via Tor only, you can't open it on a public domain like lnbits.yourdomain.com</summary>
<ul>
<li>Given that you want your setup to stay like this open your LNbits wallet using the .onion URI and create it again. In this way the QR is generated to be accessible via this .onion URI so via tor only. Do not generate that QR from a .local URI, because it will not be reachable via internet - only from within your home-LAN.</li>
<li>Open your LN wallet app that you used to scan that QR and this time by using tor (see wallet app settings).
<li>Open your LN wallet app that you used to scan that QR and this time by using tor (see wallet app settings).
If the app doesn't offer tor, you can use Orbot (Android) instead. See section Installation->Clearnet for detailed instructions.</li>
</ul>
</details>
@@ -70,10 +71,10 @@ allow-self-payment=1
<details><summary>LNbits is running via Tor only, you want to offer public LN services via https</summary>
<ul>
<li>For this we need to partially open LNbits to a clearnet (domain/IP) through a https SSL certificate. Follow the instructions from <a href="https://docs.lnbits.org/guide/installation.html#reverse-proxy-with-automatic-https-using-caddy">this LNbits caddy installation instruction</a>.
You need to have a domain and to be able to configure a CNAME for your DNS record as well as generate a subdomain dedicated to your LNbits instance like eg. lnbits.yourdomain.com.
You need to have a domain and to be able to configure a CNAME for your DNS record as well as generate a subdomain dedicated to your LNbits instance like eg. lnbits.yourdomain.com.
You also need access to your internet router to open the https port (usually 443) and forward it your LNbits IP within your LAN (usually 80). The ports might depend on your node implementation if those ports do not work please ask for them in a help group of your node supplier.</li>
<li>You can also follow the Apache installation option, explained in the <a href="https://docs.lnbits.org/guide/installation.html#running-behind-an-apache2-reverse-proxy-over-https">LNbits installation manual</a>.</li>
<li>If you run LNbits from a bundle node (Umbrel, myNode, Embassy, Raspiblitz etc), you can follow <a href="https://github.com/TrezorHannes/vps-lnbits">this extensive guide</a> with many options to switch your Tor only LNbits into a clearnet LNbits. For Citadel there is a HTTPS Option in your manual to activate https for LNbits in the newest version.</li>
<li>If you run LNbits from a bundle node (myNode, Start9, Umbrel,Raspiblitz etc), you can follow <a href="https://github.com/TrezorHannes/vps-lnbits">this extensive guide</a> with many options to switch your Tor only LNbits into a clearnet LNbits. For Citadel there is a HTTPS Option in your manual to activate https for LNbits in the newest version.</li>
</ul>
</details>
</li>
@@ -83,15 +84,15 @@ allow-self-payment=1
<details><summary>Wallet-URL deleted, are my funds safu ?</summary>
<ul>
<li>
<details><summary>Wallet on demo server legend.lnbits</summary>
<details><summary>Wallet on demo server demo.lnbits.com</summary>
<p>Always save a copy of your wallet-URL, Export2phone-QR or LNDhub for your own wallets in a safe place. LNbits CANNOT help you to recover them when lost.</p>
</details>
</li>
<li>
<details><summary>Wallet on your own funding source/node</summary>
<p>Always save a copy of your wallet-URL, Export2phone-QR or LNDhub for your own wallets in a safe place.
You can find all LNbits users and wallet-IDs in your LNbits user manager extension or in your sqlite database.
To edit or read the LNbits database, go to the LNbits /data folder and look for the file called sqlite.db.
<p>Always save a copy of your wallet-URL, Export2phone-QR or LNDhub for your own wallets in a safe place.
You can find all LNbits users and wallet-IDs in your LNbits user manager extension or in your sqlite database.
To edit or read the LNbits database, go to the LNbits /data folder and look for the file called sqlite.db.
You can open and edit it with excel or with a dedicated SQL-Editor like <a href="https://sqlitebrowser.org/">SQLite browser</a>.</p>
</details>
</li>
@@ -100,7 +101,7 @@ allow-self-payment=1
<details><summary>Configure a comment that people see when paying to my LNURLp QR</summary>
<p>When you create a LNURL-p, by default the comment box is not filled. That means comments are not allowed to be attached to payments.<p>
<p>In order to allow comments, add the characters lenght of the box, from 1 to 250. Once you put a number there,
<p>In order to allow comments, add the characters length of the box, from 1 to 250. Once you put a number there,
the comment box will be displayed in the payment process. You can also edit a LNURL-p already created and add that number.</p>
![lnbits-lnurl-comment.png](https://i.postimg.cc/HkJQ9xKr/lnbits-lnurl-comment.png)
@@ -113,16 +114,16 @@ allow-self-payment=1
<li>
<details><summary>A - Via an external swap service</summary>
<p>If the user do not have full access of your LNbits, is just an external user, can use swap services like <a href="https://boltz.exchange/">Boltz</a>, <a href="https://fixedfloat.com/">FixedFloat</a>, <a href="https://swap.diamondhands.technology/">DiamondHands</a> or <a href="https://zigzag.io/">ZigZag</a>.</p>
<p>This is useful if you provide only LNURL/LN invoices from your LNbits instance, but a payer only has onchain sats so
<p>This is useful if you provide only LNURL/LN invoices from your LNbits instance, but a payer only has onchain sats so
they will have to the swap those sats first on their side.</p>
<p>The procedure is simple: user sends onchain btc to the swap service and provides the LNURL / LN invoice from LNbits as destination of the swap.</p>
</details>
</li>
<li>
<details><summary>B - Using the Onchain LNbits extension</summary>
<p>Keep in mind that this is a separate wallet, not the LN btc one that is represented by LNbits as "your wallet" upon your LN funding source.
This onchain wallet can be used also to swap LN btc to (e.g. your hardwarewallet) by using the LNbits Boltz or Deezy extension.
If you run a webshop that is linked to your LNbits for LN payments, it is very handy to regularly drain all the sats from LN into onchain.
<p>Keep in mind that this is a separate wallet, not the LN btc one that is represented by LNbits as "your wallet" upon your LN funding source.
This onchain wallet can be used also to swap LN btc to (e.g. your hardwarewallet) by using the LNbits Boltz or Deezy extension.
If you run a webshop that is linked to your LNbits for LN payments, it is very handy to regularly drain all the sats from LN into onchain.
This leads to more space in your LN channels to be able to receive new fresh sats.</p>
<p>Procedure:</p>
<ul>
@@ -130,32 +131,31 @@ allow-self-payment=1
<li>Go to wallet information and copy the xpub</li>
<li>Go to LNbits - Onchain extension and create a new watch-only wallet with that xpub</li>
<li>Go to LNbits - Tipjar extension and create a new Tipjar. Select also the onchain option besides the LN wallet.</li>
<li>Optional - Go to LNbits - SatsPay extension and create a new charge for onchain btc.
<li>Optional - Go to LNbits - SatsPay extension and create a new charge for onchain btc.
You can choose between onchain and LN or both. It will then create an invoice that can be shared.</li>
<li>Optional - If you use your LNbits linked to a Wordpress + Woocommerce page, once you create/link a watch-only wallet to your LN btc shop wallet,
<li>Optional - If you use your LNbits linked to a Wordpress + Woocommerce page, once you create/link a watch-only wallet to your LN btc shop wallet,
the customer will have both options to pay on the same screen.</li>
</ul>
</details>
</li>
</ul>
</details>
<details><summary>Where can I see payment details?</summary>
<p>When you receive a payment in LNbits, the transaction log will display only a resumed type of the transaction.
<p>When you receive a payment in LNbits, the transaction log will display only a resumed type of the transaction.
![lnbits-tx-log.png](https://i.postimg.cc/gk2FMFG9/lnbits-tx-log.png)
<p>In your transaction overview you will find a little green arrow for received and a red arrow for sended funds.<p>
<p>If you click on those arrows, a details popup shows attached messages as well as the sender´s name if given.</p>
</details>
<details><summary>Can I configure a name to the payments i make?</summary>
<p>In LNbits this is currently not possible to do - but to receive. This is only possible if the sender's LN wallet supports <a href="https://github.com/lnurl/luds">LUD-18</a> (nameDesc) like e.g. <a href="https://darthcoin.substack.com/p/obw-open-bitcoin-wallet">Open Bitcion Wallet - OBW</a> does. You will then see an alias/pseudonym in the details section of your LNbits transactions (click the arrows). Note that you can give any name there and it might not be related to the real sender´s name(!) if your receive such.</p>
<p>In LNbits this is currently not possible to do - but to receive. This is only possible if the sender's LN wallet supports <a href="https://github.com/lnurl/luds">LUD-18</a> (nameDesc) like e.g. <a href="https://darthcoin.substack.com/p/obw-open-bitcoin-wallet">Open Bitcoin Wallet - OBW</a> does. You will then see an alias/pseudonym in the details section of your LNbits transactions (click the arrows). Note that you can give any name there and it might not be related to the real sender´s name(!) if your receive such.</p>
![lnbits-tx-details.png](https://i.postimg.cc/yYnvyK4w/lnbits-tx-details.png)
</p>
</details>
<details><summary>How can I use a LNbits lndhub account in other wallet apps?</summary>
<p>Open your LNbits with the account / wallet you want to use, go to "manage extensions" and activate the <a href="https://github.com/lnbits/lndhub">LNDHUB extension</a>.</p>
<p>Then open the LNDHUB extension, choose the wallet you want to use and scan the QR code you want to use: "admin" or "invoice only", depending on the security level you want for that wallet.</p>
@@ -166,12 +166,13 @@ allow-self-payment=1
</ul>
## Building hardware tools
<ul> <p>LNbits has all sorts of open APIs and tools to program and connect to a lot of different devices for a gazillion of use-cases. Let us know what you did with it ! Come to the <a href="https://t.me/makerbits">Makerbits Telegram Group</a> if you are interested in building or if you need help with a project - we got you!</p>
<details><summary>ATM - deposit and withdraw in your shop or at your meetup</summary>
<p>This is a do-it-yourself project consisting of a mini-computer (Raspberry Pi Zero), a coin acceptor, a display, a 3D printed case, and a Bitcoin Lightning wallet as a funding source. It exchanges fiat coins for valuable Bitcoin Lightning ⚡ Satoshis. The user can pick up the Satoshis via QR code (LNURL-withdraw) on a mobile phone wallet. You can get the components as individual parts and build the case yourself e.g. from <a href="https://www.Fulmo.org">Fulmo</a> who also made a <a href="https://blog.fulmo.org/the-lightningatm-pocket-edition/">guide</a> on it. The shop offers payments in Bitcoin and Lightning ⚡. The code can be found on <a href="https://github.com/21isenough/LightningATM">the ATM github project page></a>.</p>
</details>
<details><summary>POS Terminal - an offline terminal for merchants</summary>
<p>The LNpos is a self-sufficient point of sale terminal which allows offline onchain payments and an offline Lightning ATM for withdrawals. Free and open source software, free from intermediaries, with integrated battery, WLAN, DIY. You can get the 3D print as well as the whole kit from the LNbits shop from 👇 Resources. It allows
<li>LNPoS Online interactive Lightning payments</li>
@@ -183,21 +184,21 @@ allow-self-payment=1
</p>
</p>
</details>
<details><summary>Hardware Wallet- build your own, stack harder</summary>
<p>The hardwarewallet is a very cheap solution for builders. The projects´ <a hrel="https://github.com/lnbits/hardware-wallet">code and installation instructions for the LNbits hardware wallet can be found on github</a></p>
<p>
<img width="546" alt="Bildschirm­foto 2023-01-20 um 18 08 37" src="https://user-images.githubusercontent.com/63317640/213760948-38fd77b0-9247-4505-9433-f5af1b223527.png">
</p>
</details>
<details><summary>Bitcoin Switch - turn things on with bitcoin</summary>
<p>Candy dispenser, vending machines (online), grabbing machines, jukeboxes, bandits and <a href="https://github.com/cryptoteun/awesome-lnbits">all sorts of other things have already been build with LNbits´ tools</a>. Further info see 👇 Resources.</p>
<p>
<img width="549" alt="Bildschirm­foto 2023-01-20 um 18 11 55" src="https://user-images.githubusercontent.com/63317640/213761646-d25d4745-e50d-4389-98e5-f83237a8cf6b.png">
</p>
</details>
<details><summary>Vending machine (offline)</summary>
<p>This code works similar to the LNpos. Note that the <a href=" https://www.youtube.com/watch?v=Fg0UuuzsYXc&t=762s">setup-video for the vending machine</a> misses the new way of installing it via the new LNURLdevices extension. The <a href="https://github.com/arcbtc/LNURLVend">vending machine project code resides on github</a>.</p>
<p>
@@ -216,6 +217,7 @@ allow-self-payment=1
</ul>
## Use cases of LNbits
<ul><details><summary>Merchant</summary>
<p>LNbits is a powerful solution for merchants, due to the easy setup with various extensions, that can be used for many scenarios.</p>
<p><a href="https://darthcoin.substack.com/p/lnbits-for-small-merchants">Here is an overview of the LNbits tools available for a small restaurant as well as a hotel</a></p>
@@ -262,7 +264,8 @@ allow-self-payment=1
</ul>
## Developing for LNbits
<ul>
<li><a href="http://docs.lnbits.org/devs/development.html">Making Estension / how to use Websockets / API reference</a></li>
<ul>
<li><a href="https://docs.lnbits.org/devs/development.html">Making extensions / How to use Websockets / API reference</a></li>
<li><a href="https://t.me/lnbits">Telegram LNbits Support Group</a></li></ul>
</ul>
+29 -9
View File
@@ -1,19 +1,23 @@
## Defining a route with path parameters
**old:**
```python
# with <>
# with <>
@offlineshop_ext.route("/lnurl/<item_id>", methods=["GET"])
```
**new:**
```python
# with curly braces: {}
@offlineshop_ext.get("/lnurl/{item_id}")
```
## Check if a user exists and access user object
**old:**
```python
# decorators
@check_user_exists()
@@ -24,36 +28,43 @@ async def do_routing_stuff():
**new:**
If user doesn't exist, `Depends(check_user_exists)` will raise an exception.
If user exists, `user` will be the user object
```python
# depends calls
@core_html_routes.get("/my_route")
async def extensions(user: User = Depends(check_user_exists)):
pass
```
## Returning data from API calls
**old:**
```python
return (
{
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"balance": wallet.wallet.balance_msat
},
HTTPStatus.OK,
)
```
FastAPI returns `HTTPStatus.OK` by default id no Exception is raised
**new:**
```python
return {
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"id": wallet.wallet.id,
"name": wallet.wallet.name,
"balance": wallet.wallet.balance_msat
}
```
To change the default HTTPStatus, add it to the path decorator
```python
@core_app.post("/api/v1/payments", status_code=HTTPStatus.CREATED)
async def payments():
@@ -61,7 +72,9 @@ async def payments():
```
## Raise exceptions
**old:**
```python
return (
{"message": f"Failed to connect to {domain}."},
@@ -74,6 +87,7 @@ abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
**new:**
Raise an exception to return a status code other than the default status code.
```python
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@@ -82,7 +96,9 @@ raise HTTPException(
```
## Extensions
**old:**
```python
from quart import Blueprint
@@ -92,6 +108,7 @@ amilk_ext: Blueprint = Blueprint(
```
**new:**
```python
from fastapi import APIRouter
from lnbits.jinja2_templating import Jinja2Templates
@@ -114,9 +131,12 @@ offlineshop_rndr = template_renderer([
```
## Possible optimizations
### Use Redis as a cache server
Instead of hitting the database over and over again, we can store a short lived object in [Redis](https://redis.io) for an arbitrary key.
Example:
* Get transactions for a wallet ID
* User data for a user id
* Wallet data for a Admin / Invoice key
- Get transactions for a wallet ID
- User data for a user id
- Wallet data for a Admin / Invoice key
+121 -62
View File
@@ -6,37 +6,56 @@ nav_order: 2
# Basic installation
You can choose between four package managers, `poetry` and `nix`
Note that by default LNbits uses SQLite as its database, which is simple and effective but you can configure it to use PostgreSQL instead which is also described in a section below.
By default, LNbits will use SQLite as its database. You can also use PostgreSQL which is recommended for applications with a high load (see guide below).
## Option 1: AppImage (LInux)
## Option 1 (recommended): poetry
### AppImage (Linux)
If you have problems installing LNbits using these instructions, please have a look at the [Troubleshooting](#troubleshooting) section.
Go to [releases](https://github.com/lnbits/lnbits/releases) and pull latest AppImage, or:
```sh
sudo apt-get install libfuse2
wget $(curl -s https://api.github.com/repos/lnbits/lnbits/releases/latest | jq -r '.assets[] | select(.name | endswith(".AppImage")) | .browser_download_url') -O LNbits-latest.AppImage
chmod +x LNbits-latest.AppImage
./LNbits-latest.AppImage --host 0.0.0.0 --port 5000
```
LNbits will create a folder for db and extension files in the folder the AppImage runs from.
## Option 2: Poetry (recommended for developers)
It is recommended to use the latest version of Poetry. Make sure you have Python version `3.12` installed.
### Install Python 3.12
## Option 2 (recommended): Poetry
It is recommended to use the latest version of Poetry. Make sure you have Python version 3.9 or higher installed.
### Verify Python version
```sh
python3 --version
```
### Install Poetry
```sh
# If path 'export PATH="$HOME/.local/bin:$PATH"' fails, use the path echoed by the install
curl -sSL https://install.python-poetry.org | python3 - && export PATH="$HOME/.local/bin:$PATH"
```
### install LNbits
```sh
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt install python3.9 python3.9-distutils
curl -sSL https://install.python-poetry.org | python3 -
# Once the above poetry install is completed, use the installation path printed to terminal and replace in the following command
export PATH="/home/user/.local/bin:$PATH"
# Next command, you can exchange with python3.10 or newer versions.
# Identify your version with python3 --version and specify in the next line
# command is only needed when your default python is not ^3.9 or ^3.10
poetry env use python3.9
poetry env use 3.12
git checkout main
poetry install --only main
mkdir data
cp .env.example .env
# set funding source amongst other options
nano .env
# Optional: to set funding source amongst other options via the env `nano .env`
```
#### Running the server
@@ -47,9 +66,17 @@ poetry run lnbits
# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output
# Note that you have to add the line DEBUG=true in your .env file, too.
```
#### LNbits-cli
```sh
# A very useful terminal client for getting the supersuer ID, updating extensions, etc
poetry run lnbits-cli --help
```
#### Updating the server
```
```sh
cd lnbits
# Stop LNbits with `ctrl + x`
git pull
@@ -58,49 +85,79 @@ poetry install --only main
# Start LNbits with `poetry run lnbits`
```
## Option 2: Nix
> note: currently not supported while we make some architectural changes on the path to leave beta
## Option 2: Install script (on Debian/Ubuntu)
```sh
wget https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits.sh &&
chmod +x lnbits.sh &&
./lnbits.sh
```
Now visit `0.0.0.0:5000` to make a super-user account.
`./lnbits.sh` can be used to run, but for more control `cd lnbits` and use `poetry run lnbits` (see previous option).
## Option 3: Nix
```sh
# Install nix. If you have installed via another manager, remove and use this install (from https://nixos.org/download)
sh <(curl -L https://nixos.org/nix/install) --daemon
# Enable nix-command and flakes experimental features for nix:
echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
# Add cachix for cached binaries
nix-env -iA cachix -f https://cachix.org/api/v1/install
cachix use lnbits
# Clone and build LNbits
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# Modern debian distros usually include Nix, however you can install with:
# 'sh <(curl -L https://nixos.org/nix/install) --daemon', or use setup here https://nixos.org/download.html#nix-verify-installation
nix build
nix build .#lnbits
mkdir data
```
#### Running the server
```sh
# .env variables are currently passed when running
LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT=https://legend.lnbits.com LNBITS_KEY=7b1a78d6c78f48b09a202f2dcb2d22eb ./result/bin/lnbits --port 9000
nix run
```
Ideally you would set the environment via the `.env` file,
but you can also set the env variables or pass command line arguments:
## Option 3: Docker
```sh
# .env variables are currently passed when running, but LNbits can be managed with the admin UI.
LNBITS_ADMIN_UI=true ./result/bin/lnbits --port 9000
# Once you have created a user, you can set as the super_user
SUPER_USER=be54db7f245346c8833eaa430e1e0405 LNBITS_ADMIN_UI=true ./result/bin/lnbits --port 9000
```
## Option 4: Docker
use latest version from docker hub
```sh
docker pull lnbitsdocker/lnbits-legend
docker pull lnbits/lnbits
wget https://raw.githubusercontent.com/lnbits/lnbits/main/.env.example -O .env
mkdir data
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbitsdocker/lnbits-legend
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
```
build the image yourself
```sh
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbitsdocker/lnbits-legend .
docker build -t lnbits/lnbits .
cp .env.example .env
mkdir data
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbitsdocker/lnbits-legend
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
```
## Option 4: Fly.io
## Option 5: Fly.io
Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use.
@@ -133,7 +190,7 @@ You'll be prompted to enter an app name, region, postgres (choose no), deploy no
You'll now find a file in the directory called `fly.toml`. Open that file and modify/add the following settings.
Note: Be sure to replace `${PUT_YOUR_LNBITS_ENV_VARS_HERE}` with all relevant environment variables in `.env` or `.env.example`. Environment variable strings should be quoted here, so if in `.env` you have `LNBITS_ENDPOINT=https://legend.lnbits.com` in `fly.toml` you should have `LNBITS_ENDPOINT="https://legend.lnbits.com"`.
Note: Be sure to replace `${PUT_YOUR_LNBITS_ENV_VARS_HERE}` with all relevant environment variables in `.env` or `.env.example`. Environment variable strings should be quoted here, so if in `.env` you have `LNBITS_ENDPOINT=https://demo.lnbits.com` in `fly.toml` you should have `LNBITS_ENDPOINT="https://demo.lnbits.com"`.
Note: Don't enter secret environment variables here. Fly.io offers secrets (via the `fly secrets` command) that are exposed as environment variables in your runtime. So, for example, if using the LND_REST funding source, you can run `fly secrets set LND_REST_MACAROON=<hex_macaroon_data>`.
@@ -183,17 +240,13 @@ Problems installing? These commands have helped us install LNbits.
sudo apt install pkg-config libffi-dev libpq-dev
# build essentials for debian/ubuntu
sudo apt install python3.9-dev gcc build-essential
sudo apt install python3.10-dev gcc build-essential
# if the secp256k1 build fails:
# if you used poetry
poetry add setuptools wheel
```
#### Poetry
If your Poetry version is older than 1.2, for `poetry install`, ignore the `--only main` flag.
### Optional: PostgreSQL database
If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits:
@@ -234,11 +287,10 @@ You might also need to install additional packages or perform additional setup s
Take a look at [Polar](https://lightningpolar.com/) for an excellent way of spinning up a Lightning Network dev environment.
# Additional guides
## SQLite to PostgreSQL migration
If you already have LNbits installed and running, on an SQLite database, we **highly** recommend you migrate to postgres if you are planning to run LNbits on scale.
There's a script included that can do the migration easy. You should have Postgres already installed and there should be a password for the user (see Postgres install guide above). Additionally, your LNbits instance should run once on postgres to implement the database schema before the migration works:
@@ -260,7 +312,6 @@ make migration
Hopefully, everything works and get migrated... Launch LNbits again and check if everything is working properly.
## LNbits as a systemd service
Systemd is great for taking care of your LNbits instance. It will start it on boot and restart it in case it crashes. If you want to run LNbits as a systemd service on your Debian/Ubuntu/Raspbian server, create a file at `/etc/systemd/system/lnbits.service` with the following content:
@@ -322,15 +373,6 @@ Assuming your LNbits is running on port `5000` add:
```
yourdomain.com {
handle /api/v1/payments/sse* {
reverse_proxy 0.0.0.0:5000 {
header_up X-Forwarded-Host yourdomain.com
transport http {
keepalive off
compression off
}
}
}
reverse_proxy 0.0.0.0:5000 {
header_up X-Forwarded-Host yourdomain.com
}
@@ -349,7 +391,7 @@ Install Apache2 and enable Apache2 mods:
```sh
apt-get install apache2 certbot
a2enmod headers ssl proxy proxy-http
a2enmod headers ssl proxy proxy_http
```
Create a SSL certificate with LetsEncrypt:
@@ -388,7 +430,7 @@ EOF
Restart Apache2:
```sh
service restart apache2
service apache2 restart
```
## Running behind an Nginx reverse proxy over HTTPS
@@ -420,6 +462,15 @@ server {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_request_headers on;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
listen [::]:443 ssl;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/lnbits.org/fullchain.pem;
@@ -433,32 +484,40 @@ EOF
Restart nginx:
```sh
service restart nginx
service nginx restart
```
## Using https without reverse proxy
The most common way of using LNbits via https is to use a reverse proxy such as Caddy, nginx, or ngriok. However, you can also run LNbits via https without additional software. This is useful for development purposes or if you want to use LNbits in your local network.
We have to create a self-signed certificate using `mkcert`. Note that this certificate is not "trusted" by most browsers but that's fine (since you know that you have created it) and encryption is always better than clear text.
#### Install mkcert
You can find the install instructions for `mkcert` [here](https://github.com/FiloSottile/mkcert).
Install mkcert on Ubuntu:
```sh
sudo apt install libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert
```
#### Create certificate
To create a certificate, first `cd` into your LNbits folder and execute the following command on Linux:
```sh
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out cert.pem -keyout key.pem
```
This will create two new files (`key.pem` and `cert.pem `).
Alternatively, you can use mkcert ([more info](https://kifarunix.com/how-to-create-self-signed-ssl-certificate-with-mkcert-on-ubuntu-18-04/)):
```sh
# add your local IP (192.x.x.x) as well if you want to use it in your local network
mkcert localhost 127.0.0.1 ::1
@@ -470,7 +529,6 @@ You can then pass the certificate files to uvicorn when you start LNbits:
poetry run uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5000 --ssl-keyfile ./key.pem --ssl-certfile ./cert.pem
```
## LNbits running on Umbrel behind Tor
If you want to run LNbits on your Umbrel but want it to be reached through clearnet, _Uxellodunum_ made an extensive [guide](https://community.getumbrel.com/t/guide-lnbits-without-tor/604) on how to do it.
@@ -482,7 +540,7 @@ To install using docker you first need to build the docker image as:
```
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbits-legend .
docker build -t lnbits/lnbits .
```
You can launch the docker in a different directory, but make sure to copy `.env.example` from lnbits there
@@ -494,6 +552,7 @@ cp <lnbits_repo>/.env.example .env
and change the configuration in `.env` as required.
Then create the data directory
```
mkdir data
```
@@ -501,7 +560,7 @@ mkdir data
Then the image can be run as:
```
docker run --detach --publish 5000:5000 --name lnbits-legend -e "LNBITS_BACKEND_WALLET_CLASS='FakeWallet'" --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend
docker run --detach --publish 5000:5000 --name lnbits -e "LNBITS_BACKEND_WALLET_CLASS='FakeWallet'" --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits
```
Finally you can access your lnbits on your machine at port 5000.
+77 -9
View File
@@ -4,21 +4,25 @@ title: Backend wallets
nav_order: 3
---
# Backend wallets
Backend wallets
===============
LNbits can run on top of many lightning-network funding sources. Currently there is support for CoreLightning, LND, LNbits, LNPay and OpenNode, with more being added regularly.
LNbits can run on top of many Lightning Network funding sources with more being added regularly.
A backend wallet can be configured using the following LNbits environment variables:
### CoreLightning
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningWallet**
- `CORELIGHTNING_RPC`: /file/path/lightning-rpc
### Spark (c-lightning)
### CoreLightning REST
- `LNBITS_BACKEND_WALLET_CLASS`: **CoreLightningRestWallet**
- `CORELIGHTNING_REST_URL`: http://127.0.0.1:8185/
- `CORELIGHTNING_REST_MACAROON`: /file/path/admin.macaroon or Base64/Hex
- `CORELIGHTNING_REST_CERT`: /home/lightning/clnrest/tls.cert
### Spark (Core Lightning)
- `LNBITS_BACKEND_WALLET_CLASS`: **SparkWallet**
- `SPARK_URL`: http://10.147.17.230:9737/rpc
@@ -29,7 +33,7 @@ A backend wallet can be configured using the following LNbits environment variab
- `LNBITS_BACKEND_WALLET_CLASS`: **LndRestWallet**
- `LND_REST_ENDPOINT`: http://10.147.17.230:8080/
- `LND_REST_CERT`: /file/path/tls.cert
- `LND_REST_MACAROON`: /file/path/admin.macaroon or Bech64/Hex
- `LND_REST_MACAROON`: /file/path/admin.macaroon or Base64/Hex
or
@@ -41,7 +45,7 @@ or
- `LND_GRPC_ENDPOINT`: ip_address
- `LND_GRPC_PORT`: port
- `LND_GRPC_CERT`: /file/path/tls.cert
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Bech64/Hex
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Base64/Hex
You can also use an AES-encrypted macaroon (more info) instead by using
@@ -64,7 +68,6 @@ For the invoice listener to work you have a publicly accessible URL in your LNbi
- `LNPAY_API_KEY`: sak_apiKey
- `LNPAY_WALLET_KEY`: waka_apiKey
### OpenNode
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary.
@@ -73,7 +76,72 @@ For the invoice to work you must have a publicly accessible URL in your LNbits.
- `OPENNODE_API_ENDPOINT`: https://api.opennode.com/
- `OPENNODE_KEY`: opennodeAdminApiKey
### Blink
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate a Blink API key after logging in or creating a new Blink account at: https://dashboard.blink.sv. For more info visit: https://dev.blink.sv/api/auth#create-an-api-key```
- `LNBITS_BACKEND_WALLET_CLASS`: **BlinkWallet**
- `BLINK_API_ENDPOINT`: https://api.blink.sv/graphql
- `BLINK_WS_ENDPOINT`: wss://ws.blink.sv/graphql
- `BLINK_TOKEN`: BlinkToken
### Alby
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate an alby access token here: https://getalby.com/developer/access_tokens/new
- `LNBITS_BACKEND_WALLET_CLASS`: **AlbyWallet**
- `ALBY_API_ENDPOINT`: https://api.getalby.com/
- `ALBY_ACCESS_TOKEN`: AlbyAccessToken
### Boltz
This funding source connects to a running [boltz-client](https://docs.boltz.exchange/v/boltz-client) and handles all lightning payments through submarine swaps on the liquid network.
You can configure the daemon to run in standalone mode by `standalone = True` in the config file or using the cli flag (`boltzd --standalone`).
Once running, you can create a liquid wallet using `boltzcli wallet create lnbits lbtc`.
- `LNBITS_BACKEND_WALLET_CLASS`: **BoltzWallet**
- `BOLTZ_CLIENT_ENDPOINT`: 127.0.0.1:9002
- `BOLTZ_CLIENT_MACAROON`: /home/bob/.boltz/macaroons/admin.macaroon or Base64/Hex
- `BOLTZ_CLIENT_CERT`: /home/bob/.boltz/tls.cert or Base64/Hex
- `BOLTZ_CLIENT_WALLET`: lnbits
### ZBD
For the invoice to work you must have a publicly accessible URL in your LNbits. No manual webhook setting is necessary. You can generate an ZBD API Key here: https://zbd.dev/docs/dashboard/projects/api
- `LNBITS_BACKEND_WALLET_CLASS`: **ZBDWallet**
- `ZBD_API_ENDPOINT`: https://api.zebedee.io/v0/
- `ZBD_API_KEY`: ZBDApiKey
### Phoenixd
For the invoice to work you must have a publicly accessible URL in your LNbits. You can get a phoenixd API key from the install
~/.phoenix/phoenix.conf, also see the documentation for phoenixd.
- `LNBITS_BACKEND_WALLET_CLASS`: **PhoenixdWallet**
- `PHOENIXD_API_ENDPOINT`: http://localhost:9740/
- `PHOENIXD_API_PASSWORD`: PhoenixdApiPassword
### Breez SDK
A Greenlight invite code or Greenlight partner certificate/key can be used to register a new node with Greenlight. If the Greenlight node already exists, neither are required.
- `LNBITS_BACKEND_WALLET_CLASS`: **BreezSdkWallet**
- `BREEZ_API_KEY`: ...
- `BREEZ_GREENLIGHT_SEED`: ...
- `BREEZ_GREENLIGHT_INVITE_CODE`: ...
- `BREEZ_GREENLIGHT_DEVICE_KEY`: /path/to/breezsdk/device.pem or Base64/Hex
- `BREEZ_GREENLIGHT_DEVICE_CERT`: /path/to/breezsdk/device.crt or Base64/Hex
### Cliche Wallet
- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000
### Nostr Wallet Connect (NWC)
To use NWC as funding source in LNbits you'll need a pairing URL (also known as pairing secret) from a NWC service provider. You can find a list of providers [here](https://github.com/getAlby/awesome-nwc?tab=readme-ov-file#nwc-wallets).
You can configure Nostr Wallet Connect in the admin ui or using the following environment variables:
- `LNBITS_BACKEND_WALLET_CLASS`: **NWCWallet**
- `NWC_PAIRING_URL`: **nostr+walletconnect://...your...pairing...secret...**
+7 -11
View File
@@ -4,21 +4,17 @@ title: Users Guide
nav_order: 1
---
LNbits, free and open-source lightning-network wallet/accounts system
=====================================================================
# LNbits, free and open-source Lightning Network wallet/accounts system
LNbits is a very simple Python application that sits on top of any funding source, and can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet
* Extendable platform for exploring lightning-network functionality via LNbits extension framework
* Part of a development stack via LNbits API
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
- Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet
- Extendable platform for exploring Lightning Network functionality via LNbits extension framework
- Part of a development stack via LNbits API
- Fallback wallet for the LNURL scheme
- Instant wallet for LN demonstrations
LNbits as an account system
---------------------------
## LNbits as an account system
LNbits is packaged with tools to help manage funds, such as a table of transactions, line chart of spending,
export to csv + more to come...
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.041 30.0001"><defs><path id="a" d="M33.2619 148.1667h154.2143v68.7917H33.2619z"/></defs><g fill="#1f2234" aria-label="LNbits" font-family="sans-serif" font-size=".3095" font-weight="400" letter-spacing=".0031" style="line-height:1.25;white-space:pre;shape-inside:url(#a)" transform="matrix(72.4607 0 0 72.4607 -2399.2814 -10741.3589)"><g transform="matrix(.00244 0 0 .00244 33.0708 148.1594)"><circle cx="101.2976" cy="116.4167" r="84.6667" fill="#673ab7" fill-rule="evenodd"/><path fill="#eee" d="M79.1105 71.9667v49.0613h13.3803v40.141l31.2208-53.5213h-17.8404l17.8404-35.681z"/></g><g fill="#eee" font-family="roboto"><path d="M33.6755 148.518h.0962v.0364h-.1416v-.22h.0454zM33.9796 148.5544h-.0453l-.0883-.1448v.1448h-.0453v-.22h.0453l.0884.145v-.145h.0452z" font-weight="700" style="-inkscape-font-specification:'roboto Bold'"/><path d="M34.1625 148.4744q0 .0375-.0172.0603-.0172.0227-.0462.0227-.031 0-.048-.0219l-.0014.019h-.0256v-.2322h.028v.0866q.0168-.021.0466-.021.0298 0 .0467.0225.017.0225.017.0617zm-.028-.003q0-.0286-.011-.044-.011-.0157-.0317-.0157-.0277 0-.0398.0257v.0707q.0129.0257.04.0257.0202 0 .0314-.0156.0112-.0156.0112-.0469zM34.231 148.5544h-.028v-.1635h.028zm-.0302-.2069q0-.007.0041-.0115.0042-.005.0124-.005.0082 0 .0124.005.0042.005.0042.0115 0 .007-.0042.0113-.0042.005-.0124.005-.0082 0-.0124-.005-.004-.005-.004-.0113zM34.3167 148.3513v.0396h.0305v.0216h-.0305v.1014q0 .01.004.0148.0042.005.014.005.0048 0 .0133-.002v.0227q-.011.003-.0215.003-.0187 0-.0283-.0113-.0095-.0113-.0095-.0322v-.1014h-.0297v-.0216h.0297v-.0396zM34.478 148.511q0-.0113-.0086-.0175-.0085-.006-.0298-.0109-.0211-.005-.0337-.0109-.0124-.006-.0184-.015t-.006-.0209q0-.02.017-.034.017-.0139.0435-.0139.0278 0 .045.0144.0174.0144.0174.0367h-.028q0-.0115-.0099-.0198-.0097-.008-.0245-.008-.0152 0-.0238.007-.0086.007-.0086.0174 0 .0101.008.0153.008.005.0288.01.021.005.034.0112.013.007.0192.0157.0063.009.0063.0222 0 .022-.0175.0352-.0175.0131-.0455.0131-.0197 0-.0348-.007-.015-.007-.0237-.0193-.0085-.0125-.0085-.027h.028q.0007.014.0111.0223.0106.008.0279.008.0158 0 .0253-.006.0097-.006.0097-.0172z" style="-inkscape-font-specification:roboto"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+11
View File
@@ -0,0 +1,11 @@
<svg width="3032" height="902" viewBox="0 0 3032 902" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M158.566 493.857L1 901L450.49 355.202H264.831L501.791 1H187.881L36.4218 493.857H158.566Z" fill="#FF1FE1" stroke="black"/>
<path d="M616.041 764.863V134.611H741.647V645.115H1023.41V764.863H616.041Z" fill="#F8F8F8"/>
<path d="M1079.99 764.863V134.611H1198.81L1478.31 543.014V134.611H1600.52V764.863H1478.31L1198.81 360.241V764.863H1079.99Z" fill="#F8F8F8"/>
<path d="M616.041 764.863V134.611H741.647V645.115H1023.41V764.863H616.041Z" stroke="black"/>
<path d="M1079.99 764.863V134.611H1198.81L1478.31 543.014V134.611H1600.52V764.863H1478.31L1198.81 360.241V764.863H1079.99Z" stroke="black"/>
<path d="M1693.87 768.522V148.458H1764.1V377.349H1770.05C1775.21 369.275 1782.35 358.981 1791.47 346.467C1800.8 333.751 1814.09 322.448 1831.35 312.557C1848.81 302.465 1872.41 297.419 1902.17 297.419C1940.66 297.419 1974.58 307.208 2003.94 326.787C2033.3 346.366 2056.22 374.119 2072.68 410.048C2089.15 445.976 2097.38 488.363 2097.38 537.209C2097.38 586.459 2089.15 629.149 2072.68 665.279C2056.22 701.207 2033.4 729.061 2004.24 748.842C1975.08 768.421 1941.45 778.21 1903.36 778.21C1874 778.21 1850.49 773.265 1832.84 763.375C1815.18 753.283 1801.59 741.878 1792.07 729.162C1782.55 716.244 1775.21 705.547 1770.05 697.069H1761.72V768.522H1693.87ZM1762.91 535.998C1762.91 571.119 1767.96 602.102 1778.08 628.947C1788.2 655.59 1802.98 676.481 1822.42 691.619C1841.86 706.556 1865.67 714.024 1893.84 714.024C1923.2 714.024 1947.7 706.152 1967.34 690.408C1987.18 674.463 2002.06 653.067 2011.98 626.222C2022.1 599.175 2027.15 569.1 2027.15 535.998C2027.15 503.299 2022.19 473.83 2012.28 447.59C2002.55 421.149 1987.78 400.258 1967.94 384.918C1948.3 369.376 1923.6 361.605 1893.84 361.605C1865.27 361.605 1841.27 368.972 1821.83 383.707C1802.38 398.24 1787.7 418.626 1777.78 444.866C1767.87 470.903 1762.91 501.281 1762.91 535.998Z" fill="white"/>
<path d="M2204.81 768.522V303.474H2275.04V768.522H2204.81ZM2240.52 225.966C2226.83 225.966 2215.03 221.223 2205.11 211.736C2195.39 202.25 2190.52 190.846 2190.52 177.524C2190.52 164.202 2195.39 152.798 2205.11 143.311C2215.03 133.825 2226.83 129.081 2240.52 129.081C2254.21 129.081 2265.91 133.825 2275.63 143.311C2285.55 152.798 2290.51 164.202 2290.51 177.524C2290.51 190.846 2285.55 202.25 2275.63 211.736C2265.91 221.223 2254.21 225.966 2240.52 225.966Z" fill="white"/>
<path d="M2602.45 303.474V364.027H2365.57V303.474H2602.45ZM2434.61 192.057H2504.84V635.305C2504.84 655.489 2507.72 670.628 2513.47 680.72C2519.42 690.61 2526.96 697.271 2536.09 700.702C2545.41 703.932 2555.23 705.547 2565.55 705.547C2573.28 705.547 2579.63 705.143 2584.59 704.335C2589.55 703.326 2593.52 702.519 2596.49 701.913L2610.78 766.1C2606.02 767.916 2599.37 769.733 2590.84 771.549C2582.31 773.568 2571.5 774.577 2558.41 774.577C2538.57 774.577 2519.12 770.237 2500.08 761.558C2481.23 752.879 2465.56 739.658 2453.06 721.896C2440.76 704.134 2434.61 681.729 2434.61 654.682V192.057Z" fill="white"/>
<path d="M3023.67 407.625L2960.58 425.791C2956.61 415.094 2950.76 404.699 2943.02 394.607C2935.49 384.313 2925.17 375.835 2912.08 369.174C2898.98 362.513 2882.22 359.183 2861.79 359.183C2833.81 359.183 2810.5 365.743 2791.85 378.863C2773.4 391.781 2764.18 408.231 2764.18 428.214C2764.18 445.976 2770.53 460.004 2783.22 470.298C2795.92 480.592 2815.76 489.17 2842.74 496.033L2910.59 512.988C2951.46 523.08 2981.91 538.521 3001.94 559.311C3021.98 579.899 3032 606.441 3032 638.938C3032 665.581 3024.46 689.399 3009.38 710.391C2994.51 731.383 2973.67 747.934 2946.89 760.044C2920.11 772.155 2888.96 778.21 2853.45 778.21C2806.83 778.21 2768.25 767.916 2737.7 747.328C2707.14 726.74 2687.8 696.665 2679.67 657.104L2746.33 640.149C2752.67 665.178 2764.68 683.949 2782.33 696.464C2800.19 708.978 2823.5 715.235 2852.26 715.235C2885 715.235 2910.99 708.17 2930.23 694.041C2949.67 679.711 2959.39 662.554 2959.39 642.571C2959.39 626.424 2953.84 612.9 2942.73 602.001C2931.62 590.899 2914.56 582.624 2891.54 577.174L2815.36 559.008C2773.5 548.916 2742.75 533.273 2723.11 512.08C2703.67 490.684 2693.95 463.94 2693.95 431.847C2693.95 405.607 2701.19 382.395 2715.67 362.211C2730.36 342.026 2750.29 326.182 2775.49 314.676C2800.88 303.171 2829.65 297.419 2861.79 297.419C2907.02 297.419 2942.53 307.511 2968.32 327.695C2994.31 347.88 3012.76 374.523 3023.67 407.625Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 28 KiB

+11 -1
View File
@@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.041 30.0001"><defs><path id="a" d="M33.2619 148.1667h154.2143v68.7917H33.2619z"/></defs><g fill="#1f2234" aria-label="LNbits" font-family="sans-serif" font-size=".3095" font-weight="400" letter-spacing=".0031" style="line-height:1.25;white-space:pre;shape-inside:url(#a)" transform="matrix(72.4607 0 0 72.4607 -2399.2814 -10741.3589)"><g transform="matrix(.00244 0 0 .00244 33.0708 148.1594)"><circle cx="101.2976" cy="116.4167" r="84.6667" fill="#673ab7" fill-rule="evenodd"/><path fill="#eee" d="M79.1105 71.9667v49.0613h13.3803v40.141l31.2208-53.5213h-17.8404l17.8404-35.681z"/></g><g font-family="roboto"><path d="M33.6755 148.518h.0962v.0364h-.1416v-.22h.0454zM33.9796 148.5544h-.0453l-.0883-.1448v.1448h-.0453v-.22h.0453l.0884.145v-.145h.0452z" font-weight="700" style="-inkscape-font-specification:'roboto Bold'"/><path d="M34.1625 148.4744q0 .0375-.0172.0603-.0172.0227-.0462.0227-.031 0-.048-.0219l-.0014.019h-.0256v-.2322h.028v.0866q.0168-.021.0466-.021.0298 0 .0467.0225.017.0225.017.0617zm-.028-.003q0-.0286-.011-.044-.011-.0157-.0317-.0157-.0277 0-.0398.0257v.0707q.0129.0257.04.0257.0202 0 .0314-.0156.0112-.0156.0112-.0469zM34.231 148.5544h-.028v-.1635h.028zm-.0302-.2069q0-.007.0041-.0115.0042-.005.0124-.005.0082 0 .0124.005.0042.005.0042.0115 0 .007-.0042.0113-.0042.005-.0124.005-.0082 0-.0124-.005-.004-.005-.004-.0113zM34.3167 148.3513v.0396h.0305v.0216h-.0305v.1014q0 .01.004.0148.0042.005.014.005.0048 0 .0133-.002v.0227q-.011.003-.0215.003-.0187 0-.0283-.0113-.0095-.0113-.0095-.0322v-.1014h-.0297v-.0216h.0297v-.0396zM34.478 148.511q0-.0113-.0086-.0175-.0085-.006-.0298-.0109-.0211-.005-.0337-.0109-.0124-.006-.0184-.015t-.006-.0209q0-.02.017-.034.017-.0139.0435-.0139.0278 0 .045.0144.0174.0144.0174.0367h-.028q0-.0115-.0099-.0198-.0097-.008-.0245-.008-.0152 0-.0238.007-.0086.007-.0086.0174 0 .0101.008.0153.008.005.0288.01.021.005.034.0112.013.007.0192.0157.0063.009.0063.0222 0 .022-.0175.0352-.0175.0131-.0455.0131-.0197 0-.0348-.007-.015-.007-.0237-.0193-.0085-.0125-.0085-.027h.028q.0007.014.0111.0223.0106.008.0279.008.0158 0 .0253-.006.0097-.006.0097-.0172z" style="-inkscape-font-specification:roboto"/></g></g></svg>
<svg width="3032" height="902" viewBox="0 0 3032 902" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M158.566 493.857L1 901L450.49 355.202H264.831L501.791 1H187.881L36.4218 493.857H158.566Z" fill="#FF1FE1" stroke="black"/>
<path d="M616.041 764.863V134.611H741.647V645.115H1023.41V764.863H616.041Z" fill="#1E1E1E"/>
<path d="M1079.99 764.863V134.611H1198.81L1478.31 543.014V134.611H1600.52V764.863H1478.31L1198.81 360.241V764.863H1079.99Z" fill="#1E1E1E"/>
<path d="M616.041 764.863V134.611H741.647V645.115H1023.41V764.863H616.041Z" stroke="black"/>
<path d="M1079.99 764.863V134.611H1198.81L1478.31 543.014V134.611H1600.52V764.863H1478.31L1198.81 360.241V764.863H1079.99Z" stroke="black"/>
<path d="M1693.87 768.522V148.458H1764.1V377.349H1770.05C1775.21 369.275 1782.35 358.981 1791.47 346.467C1800.8 333.751 1814.09 322.448 1831.35 312.557C1848.81 302.465 1872.41 297.419 1902.17 297.419C1940.66 297.419 1974.58 307.208 2003.94 326.787C2033.3 346.366 2056.22 374.119 2072.68 410.048C2089.15 445.976 2097.38 488.363 2097.38 537.209C2097.38 586.459 2089.15 629.149 2072.68 665.279C2056.22 701.207 2033.4 729.061 2004.24 748.842C1975.08 768.421 1941.45 778.21 1903.36 778.21C1874 778.21 1850.49 773.265 1832.84 763.375C1815.18 753.282 1801.59 741.878 1792.07 729.162C1782.55 716.244 1775.21 705.547 1770.05 697.069H1761.72V768.522H1693.87ZM1762.91 535.998C1762.91 571.119 1767.96 602.102 1778.08 628.947C1788.2 655.59 1802.98 676.481 1822.42 691.619C1841.86 706.556 1865.67 714.024 1893.84 714.024C1923.2 714.024 1947.7 706.152 1967.34 690.408C1987.18 674.463 2002.06 653.067 2011.98 626.222C2022.1 599.175 2027.15 569.1 2027.15 535.998C2027.15 503.299 2022.2 473.83 2012.28 447.59C2002.55 421.149 1987.78 400.258 1967.94 384.918C1948.3 369.376 1923.6 361.605 1893.84 361.605C1865.27 361.605 1841.27 368.972 1821.83 383.707C1802.38 398.24 1787.7 418.626 1777.78 444.866C1767.87 470.903 1762.91 501.281 1762.91 535.998Z" fill="black"/>
<path d="M2204.81 768.522V303.474H2275.04V768.522H2204.81ZM2240.52 225.966C2226.83 225.966 2215.03 221.223 2205.11 211.736C2195.39 202.25 2190.52 190.846 2190.52 177.524C2190.52 164.202 2195.39 152.798 2205.11 143.311C2215.03 133.825 2226.83 129.081 2240.52 129.081C2254.21 129.081 2265.91 133.825 2275.63 143.311C2285.55 152.798 2290.51 164.202 2290.51 177.524C2290.51 190.846 2285.55 202.25 2275.63 211.736C2265.91 221.223 2254.21 225.966 2240.52 225.966Z" fill="black"/>
<path d="M2602.45 303.474V364.027H2365.57V303.474H2602.45ZM2434.61 192.057H2504.84V635.305C2504.84 655.489 2507.72 670.628 2513.47 680.72C2519.42 690.61 2526.96 697.271 2536.09 700.702C2545.41 703.932 2555.23 705.546 2565.55 705.546C2573.28 705.546 2579.63 705.143 2584.59 704.335C2589.55 703.326 2593.52 702.519 2596.5 701.913L2610.78 766.1C2606.02 767.916 2599.37 769.733 2590.84 771.549C2582.31 773.568 2571.5 774.577 2558.41 774.577C2538.57 774.577 2519.12 770.237 2500.08 761.558C2481.23 752.879 2465.56 739.658 2453.06 721.896C2440.76 704.134 2434.61 681.729 2434.61 654.682V192.057Z" fill="black"/>
<path d="M3023.67 407.625L2960.58 425.791C2956.61 415.094 2950.76 404.699 2943.02 394.607C2935.49 384.313 2925.17 375.835 2912.08 369.174C2898.98 362.513 2882.22 359.183 2861.79 359.183C2833.81 359.183 2810.5 365.743 2791.85 378.863C2773.41 391.781 2764.18 408.231 2764.18 428.213C2764.18 445.976 2770.53 460.004 2783.23 470.298C2795.92 480.592 2815.76 489.17 2842.74 496.033L2910.59 512.988C2951.46 523.08 2981.91 538.521 3001.94 559.311C3021.98 579.899 3032 606.441 3032 638.938C3032 665.581 3024.46 689.399 3009.38 710.391C2994.51 731.383 2973.67 747.934 2946.89 760.044C2920.11 772.155 2888.96 778.21 2853.45 778.21C2806.83 778.21 2768.25 767.916 2737.7 747.328C2707.14 726.74 2687.8 696.665 2679.67 657.104L2746.33 640.149C2752.67 665.178 2764.68 683.949 2782.33 696.464C2800.19 708.978 2823.5 715.235 2852.26 715.235C2885 715.235 2910.99 708.17 2930.23 694.041C2949.67 679.711 2959.39 662.554 2959.39 642.571C2959.39 626.424 2953.84 612.9 2942.73 602.001C2931.62 590.899 2914.56 582.624 2891.54 577.174L2815.36 559.008C2773.5 548.916 2742.75 533.273 2723.11 512.079C2703.67 490.684 2693.95 463.94 2693.95 431.847C2693.95 405.607 2701.19 382.395 2715.67 362.211C2730.36 342.026 2750.29 326.182 2775.49 314.676C2800.88 303.171 2829.65 297.419 2861.79 297.419C2907.02 297.419 2942.53 307.511 2968.32 327.695C2994.31 347.88 3012.76 374.523 3023.67 407.625Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+4 -1
View File
@@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g transform="translate(-2.9464 -5.625) scale(.17717)"><circle cx="101.2976" cy="116.4167" r="84.6667" fill="#673ab7" fill-rule="evenodd"/><path fill="#eee" d="M79.1105 71.9667v49.0613h13.3803v40.141l31.2208-53.5213h-17.8404l17.8404-35.681z"/></g></svg>
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="450" cy="450" r="450" fill="#1E1E1E"/>
<path d="M387.802 476.423L281.613 730.887L584.54 389.763H459.418L619.113 168.387H407.558L305.485 476.423H387.802Z" fill="#FF1FE1"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 290 B

Generated
+88 -11
View File
@@ -1,12 +1,15 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1676283394,
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -15,18 +18,39 @@
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1703863825,
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1678470307,
"narHash": "sha256-OEeMUr3ueLIXyW/OaFUX5jUdimyQwMg/7e+/Q0gC/QE=",
"lastModified": 1735563628,
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0c4800d579af4ed98ecc47d464a5e7b0870c4b1f",
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
@@ -34,16 +58,19 @@
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nixpkgs"
]
],
"systems": "systems_2",
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1678513806,
"narHash": "sha256-bEto1lp9bIQ+DlJsXQyRxN5l6B/oy3Skb7DueYRJJBo=",
"lastModified": 1724134185,
"narHash": "sha256-nDqpGjz7cq3ThdC98BPe1ANCNlsJds/LLZ3/MdIXjA0=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "bf239d53fafb53cf439a72b3a50e86dd6a3984a5",
"rev": "5ee730a8752264e463c0eaf06cc060fd07f6dae9",
"type": "github"
},
"original": {
@@ -57,6 +84,56 @@
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"id": "systems",
"type": "indirect"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719749022,
"narHash": "sha256-ddPKHcqaKCIFSFc/cvxS14goUhCOAwsM1PbMr0ZtHMg=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "8df5ff62195d4e67e2264df0b7f5e8c9995fd0bd",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
+21 -11
View File
@@ -2,7 +2,7 @@
description = "LNbits, free and open-source Lightning wallet and accounts system";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
poetry2nix = {
url = "github:nix-community/poetry2nix";
inputs.nixpkgs.follows = "nixpkgs";
@@ -13,28 +13,38 @@
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forSystems = systems: f:
nixpkgs.lib.genAttrs systems
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; }));
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlays.default self.overlays.default ]; }));
forAllSystems = forSystems supportedSystems;
projectName = "lnbits";
in
{
devShells = forAllSystems (system: pkgs: {
default = pkgs.mkShell {
buildInputs = with pkgs; [
nodePackages.prettier
poetry
];
};
});
overlays = {
default = final: prev: {
${projectName} = self.packages.${final.hostPlatform.system}.${projectName};
${projectName} = self.packages.${prev.stdenv.hostPlatform.system}.${projectName};
};
};
packages = forAllSystems (system: pkgs: {
default = self.packages.${system}.${projectName};
${projectName} = pkgs.poetry2nix.mkPoetryApplication {
projectDir = ./.;
meta.rev = self.dirtyRev or self.rev;
meta.mainProgram = projectName;
overrides = pkgs.poetry2nix.overrides.withDefaults (final: prev: {
coincurve = prev.coincurve.override { preferWheel = true; };
protobuf = prev.protobuf.override { preferWheel = true; };
ruff = prev.ruff.override { preferWheel = true; };
wallycore = prev.wallycore.override { preferWheel = true; };
tlv8 = prev.tlv8.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
prev.setuptools
];
});
pynostr = prev.pynostr.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [
prev.setuptools-scm
];
});
});
};
});
nixosModules = {
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Check install has not already run
if [ ! -d lnbits/data ]; then
# Update package list and install prerequisites non-interactively
sudo apt update -y
sudo apt install -y software-properties-common
# Add the deadsnakes PPA repository non-interactively
sudo add-apt-repository -y ppa:deadsnakes/ppa
# Install Python 3.9 and distutils non-interactively
sudo apt install -y python3.9 python3.9-distutils
# Install Poetry
curl -sSL https://install.python-poetry.org | python3.9 -
# Add Poetry to PATH for the current session
export PATH="/home/$USER/.local/bin:$PATH"
if [ ! -d lnbits/wallets ]; then
# Clone the LNbits repository
git clone https://github.com/lnbits/lnbits.git
if [ $? -ne 0 ]; then
echo "Failed to clone the repository ... FAIL"
exit 1
fi
# Ensure we are in the lnbits directory
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
fi
git checkout main
# Make data folder
mkdir data
# Copy the .env.example to .env
cp .env.example .env
elif [ ! -d lnbits/wallets ]; then
# cd into lnbits
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
fi
# Install the dependencies using Poetry
poetry env use python3.9
poetry install --only main
# Set environment variables for LNbits
export LNBITS_ADMIN_UI=true
export HOST=0.0.0.0
# Run LNbits
poetry run lnbits
+24
View File
@@ -0,0 +1,24 @@
from .core.services import create_invoice, pay_invoice
from .decorators import (
check_admin,
check_super_user,
check_user_exists,
require_admin_key,
require_invoice_key,
)
from .exceptions import InvoiceError, PaymentError
__all__ = [
# decorators
"require_admin_key",
"require_invoice_key",
"check_admin",
"check_super_user",
"check_user_exists",
# services
"pay_invoice",
"create_invoice",
# exceptions
"PaymentError",
"InvoiceError",
]
+338 -300
View File
@@ -1,69 +1,174 @@
import asyncio
import glob
import importlib
import logging
import os
import shutil
import signal
import sys
import traceback
from http import HTTPStatus
from typing import Callable, List
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Callable, Optional
from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from loguru import logger
from slowapi import Limiter
from slowapi.util import get_remote_address
from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.sessions import SessionMiddleware
from lnbits.core.crud import get_installed_extensions
from lnbits.core.helpers import migrate_extension_database
from lnbits.core.tasks import register_task_listeners
from lnbits.settings import get_wallet_class, set_wallet_class, settings
from .commands import db_versions, load_disabled_extension_list, migrate_databases
from .core import (
add_installed_extension,
core_app,
core_app_extra,
from lnbits.core.crud import (
get_db_version,
get_installed_extensions,
update_installed_extension_state,
)
from .core.services import check_admin_settings
from .core.views.generic import core_html_routes
from .extension_manager import Extension, InstallableExtension, get_valid_extensions
from .helpers import template_renderer
from .middleware import ExtensionsRedirectMiddleware, InstalledExtensionMiddleware
from lnbits.core.crud.extensions import create_installed_extension
from lnbits.core.helpers import migrate_extension_database
from lnbits.core.models.notifications import NotificationType
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
from lnbits.core.services.notifications import enqueue_notification
from lnbits.core.tasks import (
audit_queue,
collect_exchange_rates_data,
purge_audit_data,
run_by_the_minute_tasks,
wait_for_audit_data,
wait_for_paid_invoices,
wait_notification_messages,
)
from lnbits.exceptions import register_exception_handlers
from lnbits.helpers import version_parse
from lnbits.settings import settings
from lnbits.tasks import (
cancel_all_tasks,
create_permanent_task,
register_invoice_listener,
)
from lnbits.utils.cache import cache
from lnbits.utils.logger import (
configure_logger,
initialize_server_websocket_logger,
log_server_info,
)
from lnbits.wallets import get_funding_source, set_funding_source
from .commands import migrate_databases
from .core import init_core_routers
from .core.db import core_app_extra
from .core.models.extensions import Extension, ExtensionMeta, InstallableExtension
from .core.services import check_admin_settings, check_webpush_settings
from .middleware import (
AuditMiddleware,
ExtensionsRedirectMiddleware,
InstalledExtensionMiddleware,
add_first_install_middleware,
add_ip_block_middleware,
add_ratelimit_middleware,
)
from .requestvars import g
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
internal_invoice_listener,
invoice_listener,
webhook_handler,
)
async def startup(app: FastAPI):
settings.lnbits_running = True
# wait till migration is done
await migrate_databases()
# setup admin settings
await check_admin_settings()
core_app_extra.register_new_ratelimiter()
await check_webpush_settings()
# check extensions after restart
if not settings.lnbits_extensions_deactivate_all:
await check_and_register_extensions(app)
log_server_info()
# initialize WALLET
try:
set_funding_source()
except Exception as e:
logger.error(f"Error initializing {settings.lnbits_backend_wallet_class}: {e}")
set_void_wallet_class()
# initialize funding source
await check_funding_source()
# register core routes
init_core_routers(app)
# initialize tasks
register_async_tasks()
enqueue_notification(
NotificationType.server_start_stop,
{
"message": "LNbits server started.",
"up_time": settings.lnbits_server_up_time,
},
)
async def shutdown():
logger.warning("LNbits shutting down...")
enqueue_notification(
NotificationType.server_start_stop,
{
"message": "LNbits server shutting down...",
"up_time": settings.lnbits_server_up_time,
},
)
settings.lnbits_running = False
# shutdown event
cancel_all_tasks()
# wait a bit to allow them to finish, so that cleanup can run without problems
await asyncio.sleep(0.1)
funding_source = get_funding_source()
await funding_source.cleanup()
@asynccontextmanager
async def lifespan(app: FastAPI):
await startup(app)
yield
await shutdown()
def create_app() -> FastAPI:
configure_logger()
app = FastAPI(
title="LNbits API",
description="API for LNbits, the free and open source bitcoin wallet and accounts system with plugins.",
title=settings.lnbits_title,
description=(
"API for LNbits, the free and open source bitcoin wallet and "
"accounts system with plugins."
),
version=settings.version,
lifespan=lifespan,
license_info={
"name": "MIT License",
"url": "https://raw.githubusercontent.com/lnbits/lnbits/main/LICENSE",
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
# Allow registering new extensions routes without direct access to the `app` object
core_app_extra.register_new_ext_routes = register_new_ext_routes(app)
core_app_extra.register_new_ratelimiter = register_new_ratelimiter(app)
# register static files
app.mount("/static", StaticFiles(directory=Path("lnbits", "static")), name="static")
Path(settings.lnbits_data_folder, "images").mkdir(parents=True, exist_ok=True)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
name="core_static",
"/library",
StaticFiles(directory=Path(settings.lnbits_data_folder, "images")),
name="library",
)
g().base_url = f"http://{settings.host}:{settings.port}"
@@ -71,381 +176,314 @@ def create_app() -> FastAPI:
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
app.add_middleware(GZipMiddleware, minimum_size=1000)
app.add_middleware(AuditMiddleware, audit_queue=audit_queue)
# required for SSO login
app.add_middleware(SessionMiddleware, secret_key=settings.auth_secret_key)
# order of these two middlewares is important
app.add_middleware(InstalledExtensionMiddleware)
app.add_middleware(ExtensionsRedirectMiddleware)
register_startup(app)
register_routes(app)
register_async_tasks(app)
register_exception_handlers(app)
register_custom_extensions_path()
# Allow registering new extensions routes without direct access to the `app` object
setattr(core_app_extra, "register_new_ext_routes", register_new_ext_routes(app))
add_first_install_middleware(app)
# adds security middleware
add_ip_block_middleware(app)
add_ratelimit_middleware(app)
register_exception_handlers(app)
return app
async def check_funding_source() -> None:
funding_source = get_funding_source()
original_sigint_handler = signal.getsignal(signal.SIGINT)
def signal_handler(signal, frame):
logger.debug("SIGINT received, terminating LNbits.")
sys.exit(1)
signal.signal(signal.SIGINT, signal_handler)
WALLET = get_wallet_class()
# fallback to void after 30 seconds of failures
sleep_time = 5
timeout = int(30 / sleep_time)
balance = 0
max_retries = settings.funding_source_max_retries
retry_counter = 0
while True:
while settings.lnbits_running:
try:
error_message, balance = await WALLET.status()
logger.info(f"Connecting to backend {funding_source.__class__.__name__}...")
error_message, balance = await funding_source.status()
if not error_message:
retry_counter = 0
logger.success(
f"✔️ Backend {funding_source.__class__.__name__} connected "
f"and with a balance of {balance} msat."
)
break
logger.error(
f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
f"The backend for {funding_source.__class__.__name__} isn't "
f"working properly: '{error_message}'",
RuntimeWarning,
)
except:
pass
if settings.lnbits_admin_ui and retry_counter == timeout:
logger.warning(
f"Fallback to VoidWallet, because the backend for {WALLET.__class__.__name__} isn't working properly"
except Exception as e:
logger.error(
f"Error connecting to {funding_source.__class__.__name__}: {e}"
)
set_wallet_class("VoidWallet")
WALLET = get_wallet_class()
if retry_counter >= max_retries:
set_void_wallet_class()
funding_source = get_funding_source()
break
else:
logger.warning(f"Retrying connection to backend in {sleep_time} seconds...")
retry_counter += 1
await asyncio.sleep(sleep_time)
signal.signal(signal.SIGINT, original_sigint_handler)
retry_counter += 1
sleep_time = min(0.25 * (2**retry_counter), 60)
logger.warning(
f"Retrying connection to backend in {sleep_time} seconds... "
f"({retry_counter}/{max_retries})"
)
await asyncio.sleep(sleep_time)
logger.info(
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
def set_void_wallet_class():
logger.warning(
"Fallback to VoidWallet, because the backend for "
f"{settings.lnbits_backend_wallet_class} isn't working properly"
)
set_funding_source("VoidWallet")
async def check_installed_extensions(app: FastAPI):
"""
Check extensions that have been installed, but for some reason no longer present in the 'lnbits/extensions' directory.
One reason might be a docker-container that was re-created.
The 'data' directory (where the '.zip' files live) is expected to persist state.
Zips that are missing will be re-downloaded.
Check extensions that have been installed, but for some reason no longer present in
the 'lnbits/extensions' directory. One reason might be a docker-container that was
re-created. The 'data' directory (where the '.zip' files live) is expected to
persist state. Zips that are missing will be re-downloaded.
"""
shutil.rmtree(os.path.join("lnbits", "upgrades"), True)
await load_disabled_extension_list()
installed_extensions = await build_all_installed_extensions_list()
installed_extensions = await build_all_installed_extensions_list(False)
for ext in installed_extensions:
try:
installed = check_installed_extension(ext)
installed = await check_installed_extension_files(ext)
if not installed:
await restore_installed_extension(app, ext)
logger.info(f"✔️ Successfully re-installed extension: {ext.id}")
logger.info(
"✔️ Successfully re-installed extension: "
f"{ext.id} ({ext.installed_version})"
)
except Exception as e:
logger.warning(e)
logger.warning(f"Failed to re-install extension: {ext.id}")
await deactivate_extension(ext.id)
logger.warning(
f"Failed to re-install extension: {ext.id} ({ext.installed_version})"
)
logger.info(f"Installed Extensions ({len(installed_extensions)}):")
for ext in installed_extensions:
logger.info(f"{ext.id} ({ext.installed_version})")
async def build_all_installed_extensions_list() -> List[InstallableExtension]:
async def build_all_installed_extensions_list(
include_deactivated: Optional[bool] = True,
) -> list[InstallableExtension]:
"""
Returns a list of all the installed extensions plus the extensions that
MUST be installed by default (see LNBITS_EXTENSIONS_DEFAULT_INSTALL).
"""
installed_extensions = await get_installed_extensions()
settings.lnbits_installed_extensions_ids = {e.id for e in installed_extensions}
for ext_dir in Path(settings.lnbits_extensions_path, "extensions").iterdir():
try:
if not ext_dir.is_dir():
continue
ext_id = ext_dir.name
if ext_id in settings.lnbits_installed_extensions_ids:
continue
ext_info = InstallableExtension.from_ext_dir(ext_id)
if not ext_info:
continue
installed_extensions.append(ext_info)
await create_installed_extension(ext_info)
current_version = await get_db_version(ext_id)
await migrate_extension_database(ext_info, current_version)
except Exception as e:
logger.warning(e)
installed_extensions_ids = [e.id for e in installed_extensions]
for ext_id in settings.lnbits_extensions_default_install:
if ext_id in installed_extensions_ids:
if ext_id in settings.lnbits_installed_extensions_ids:
continue
ext_releases = await InstallableExtension.get_extension_releases(ext_id)
release = ext_releases[0] if len(ext_releases) else None
ext_releases = sorted(
ext_releases, key=lambda r: version_parse(r.version), reverse=True
)
release = next((e for e in ext_releases if e.is_version_compatible), None)
if release:
ext_meta = ExtensionMeta(installed_release=release)
ext_info = InstallableExtension(
id=ext_id, name=ext_id, installed_release=release, icon=release.icon
id=ext_id,
name=ext_id,
version=release.version,
icon=release.icon,
meta=ext_meta,
)
installed_extensions.append(ext_info)
await add_installed_extension(ext_info)
await update_installed_extension_state(ext_id=ext_id, active=True)
return installed_extensions
if include_deactivated:
return installed_extensions
if settings.lnbits_extensions_deactivate_all:
return []
return [
e
for e in installed_extensions
if e.id not in settings.lnbits_deactivated_extensions
]
def check_installed_extension(ext: InstallableExtension) -> bool:
async def check_installed_extension_files(ext: InstallableExtension) -> bool:
if ext.has_installed_version:
return True
zip_files = glob.glob(
os.path.join(settings.lnbits_data_folder, "extensions", "*.zip")
)
zip_files = glob.glob(os.path.join(settings.lnbits_data_folder, "zips", "*.zip"))
if f"./{str(ext.zip_path)}" not in zip_files:
ext.download_archive()
if f"./{ext.zip_path!s}" not in zip_files:
await ext.download_archive()
ext.extract_archive()
return False
async def restore_installed_extension(app: FastAPI, ext: InstallableExtension):
await update_installed_extension_state(ext_id=ext.id, active=True)
extension = Extension.from_installable_ext(ext)
register_ext_routes(app, extension)
current_version = (await db_versions()).get(ext.id, 0)
await migrate_extension_database(extension, current_version)
current_version = await get_db_version(ext.id)
await migrate_extension_database(ext, current_version)
# mount routes for the new version
core_app_extra.register_new_ext_routes(extension)
if extension.upgrade_hash:
ext.nofiy_upgrade()
def register_routes(app: FastAPI) -> None:
"""Register FastAPI routes / LNbits extensions."""
app.include_router(core_app)
app.include_router(core_html_routes)
def register_custom_extensions_path():
upgrades_dir = settings.lnbits_extensions_upgrade_path
shutil.rmtree(upgrades_dir, True)
Path(upgrades_dir).mkdir(parents=True, exist_ok=True)
sys.path.append(str(upgrades_dir))
for ext in get_valid_extensions():
try:
register_ext_routes(app, ext)
except Exception as e:
logger.error(str(e))
raise ImportError(
f"Please make sure that the extension `{ext.code}` follows conventions."
)
if settings.has_default_extension_path:
return
default_ext_path = os.path.join("lnbits", "extensions")
if os.path.isdir(default_ext_path) and len(os.listdir(default_ext_path)) != 0:
logger.warning(
"You are using a custom extensions path, "
+ "but the default extensions directory is not empty. "
+ f"Please clean-up the '{default_ext_path}' directory."
)
logger.warning(
f"You can move the existing '{default_ext_path}' directory to: "
+ f" '{settings.lnbits_extensions_path}/extensions'"
)
extensions_dir = Path(settings.lnbits_extensions_path, "extensions")
Path(extensions_dir).mkdir(parents=True, exist_ok=True)
sys.path.append(str(extensions_dir))
def register_new_ext_routes(app: FastAPI) -> Callable:
# Returns a function that registers new routes for an extension.
# The returned function encapsulates (creates a closure around) the `app` object but does expose it.
# The returned function encapsulates (creates a closure around)
# the `app` object but does expose it.
def register_new_ext_routes_fn(ext: Extension):
register_ext_routes(app, ext)
return register_new_ext_routes_fn
def register_new_ratelimiter(app: FastAPI) -> Callable:
def register_new_ratelimiter_fn():
limiter = Limiter(
key_func=get_remote_address,
default_limits=[
f"{settings.lnbits_rate_limit_no}/{settings.lnbits_rate_limit_unit}"
],
)
app.state.limiter = limiter
return register_new_ratelimiter_fn
def register_ext_tasks(ext: Extension) -> None:
"""Register extension async tasks."""
ext_module = importlib.import_module(ext.module_name)
if hasattr(ext_module, f"{ext.code}_start"):
ext_start_func = getattr(ext_module, f"{ext.code}_start")
ext_start_func()
def register_ext_routes(app: FastAPI, ext: Extension) -> None:
"""Register FastAPI routes for extension."""
ext_module = importlib.import_module(ext.module_name)
ext_route = getattr(ext_module, f"{ext.code}_ext")
if hasattr(ext_module, f"{ext.code}_start"):
ext_start_func = getattr(ext_module, f"{ext.code}_start")
ext_start_func()
if hasattr(ext_module, f"{ext.code}_static_files"):
ext_statics = getattr(ext_module, f"{ext.code}_static_files")
for s in ext_statics:
app.mount(s["path"], s["app"], s["name"])
static_dir = Path(
settings.lnbits_extensions_path, "extensions", *s["path"].split("/")
)
app.mount(s["path"], StaticFiles(directory=static_dir), s["name"])
if hasattr(ext_module, f"{ext.code}_redirect_paths"):
ext_redirects = getattr(ext_module, f"{ext.code}_redirect_paths")
settings.lnbits_extensions_redirects = [
r for r in settings.lnbits_extensions_redirects if r["ext_id"] != ext.code
]
for r in ext_redirects:
r["ext_id"] = ext.code
settings.lnbits_extensions_redirects.append(r)
ext_redirects = (
getattr(ext_module, f"{ext.code}_redirect_paths")
if hasattr(ext_module, f"{ext.code}_redirect_paths")
else []
)
logger.trace(f"adding route for extension {ext_module}")
settings.activate_extension_paths(ext.code, ext.upgrade_hash, ext_redirects)
logger.trace(f"Adding route for extension {ext_module}.")
prefix = f"/upgrades/{ext.upgrade_hash}" if ext.upgrade_hash != "" else ""
app.include_router(router=ext_route, prefix=prefix)
def register_startup(app: FastAPI):
@app.on_event("startup")
async def lnbits_startup():
async def check_and_register_extensions(app: FastAPI):
await check_installed_extensions(app)
for ext in await get_valid_extensions(False):
try:
# wait till migration is done
await migrate_databases()
# setup admin settings
await check_admin_settings()
log_server_info()
# initialize WALLET
set_wallet_class()
# initialize funding source
await check_funding_source()
# check extensions after restart
await check_installed_extensions(app)
except Exception as e:
logger.error(str(e))
raise ImportError("Failed to run 'startup' event.")
register_ext_routes(app, ext)
register_ext_tasks(ext)
except Exception as exc:
logger.error(f"Could not load extension `{ext.code}`: {exc!s}")
def log_server_info():
logger.info("Starting LNbits")
logger.info(f"Version: {settings.version}")
logger.info(f"Baseurl: {settings.lnbits_baseurl}")
logger.info(f"Host: {settings.host}")
logger.info(f"Port: {settings.port}")
logger.info(f"Debug: {settings.debug}")
logger.info(f"Site title: {settings.lnbits_site_title}")
logger.info(f"Funding source: {settings.lnbits_backend_wallet_class}")
logger.info(f"Data folder: {settings.lnbits_data_folder}")
logger.info(f"Git version: {settings.lnbits_commit}")
logger.info(f"Database: {get_db_vendor_name()}")
logger.info(f"Service fee: {settings.lnbits_service_fee}")
def register_async_tasks():
create_permanent_task(wait_for_audit_data)
create_permanent_task(wait_notification_messages)
def get_db_vendor_name():
db_url = settings.lnbits_database_url
return (
"PostgreSQL"
if db_url and db_url.startswith("postgres://")
else "CockroachDB"
if db_url and db_url.startswith("cockroachdb://")
else "SQLite"
)
create_permanent_task(check_pending_payments)
create_permanent_task(invoice_listener)
create_permanent_task(internal_invoice_listener)
create_permanent_task(cache.invalidate_forever)
# core invoice listener
invoice_queue: asyncio.Queue = asyncio.Queue()
register_invoice_listener(invoice_queue, "core")
create_permanent_task(lambda: wait_for_paid_invoices(invoice_queue))
def register_async_tasks(app):
@app.route("/wallet/webhook")
async def webhook_listener():
return await webhook_handler()
create_permanent_task(run_by_the_minute_tasks)
create_permanent_task(purge_audit_data)
create_permanent_task(collect_exchange_rates_data)
@app.on_event("startup")
async def listeners():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(check_pending_payments))
loop.create_task(catch_everything_and_restart(invoice_listener))
loop.create_task(catch_everything_and_restart(internal_invoice_listener))
await register_task_listeners()
# await run_deferred_async() # calle: doesn't do anyting?
@app.on_event("shutdown")
async def stop_listeners():
pass
def register_exception_handlers(app: FastAPI):
@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
etype, _, tb = sys.exc_info()
traceback.print_exception(etype, exc, tb)
logger.error(f"Exception: {str(exc)}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response
if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": f"Error: {str(exc)}"}
)
return JSONResponse(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
content={"detail": str(exc)},
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
request: Request, exc: RequestValidationError
):
logger.error(f"RequestValidationError: {str(exc)}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response
if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
return template_renderer().TemplateResponse(
"error.html",
{"request": request, "err": f"Error: {str(exc)}"},
)
return JSONResponse(
status_code=HTTPStatus.BAD_REQUEST,
content={"detail": str(exc)},
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
logger.error(f"HTTPException {exc.status_code}: {exc.detail}")
# Only the browser sends "text/html" request
# not fail proof, but everything else get's a JSON response
if (
request.headers
and "accept" in request.headers
and "text/html" in request.headers["accept"]
):
return template_renderer().TemplateResponse(
"error.html",
{
"request": request,
"err": f"HTTP Error {exc.status_code}: {exc.detail}",
},
)
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
)
def configure_logger() -> None:
logger.remove()
log_level: str = "DEBUG" if settings.debug else "INFO"
formatter = Formatter()
logger.add(sys.stderr, level=log_level, format=formatter.format)
logging.getLogger("uvicorn").handlers = [InterceptHandler()]
logging.getLogger("uvicorn.access").handlers = [InterceptHandler()]
class Formatter:
def __init__(self):
self.padding = 0
self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
if settings.debug:
self.fmt: str = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
)
else:
self.fmt: str = self.minimal_fmt
def format(self, record):
function = "{function}".format(**record) # pylint: disable=C0209
if function == "emit": # uvicorn logs
return self.minimal_fmt
return self.fmt
class InterceptHandler(logging.Handler):
def emit(self, record):
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
logger.log(level, record.getMessage())
# server logs for websocket
if settings.lnbits_admin_ui:
server_log_task = initialize_server_websocket_logger()
create_permanent_task(server_log_task)
+7 -366
View File
@@ -1,366 +1,7 @@
import hashlib
import re
import time
from decimal import Decimal
from typing import List, NamedTuple, Optional
import bitstring
import secp256k1
from bech32 import CHARSET, bech32_decode, bech32_encode
from ecdsa import SECP256k1, VerifyingKey
from ecdsa.util import sigdecode_string
class Route(NamedTuple):
pubkey: str
short_channel_id: str
base_fee_msat: int
ppm_fee: int
cltv: int
class Invoice:
payment_hash: str
amount_msat: int = 0
description: Optional[str] = None
description_hash: Optional[str] = None
payee: Optional[str] = None
date: int
expiry: int = 3600
secret: Optional[str] = None
route_hints: List[Route] = []
min_final_cltv_expiry: int = 18
def decode(pr: str) -> Invoice:
"""bolt11 decoder,
based on https://github.com/rustyrussell/lightning-payencode/blob/master/lnaddr.py
"""
hrp, decoded_data = bech32_decode(pr)
if hrp is None or decoded_data is None:
raise ValueError("Bad bech32 checksum")
if not hrp.startswith("ln"):
raise ValueError("Does not start with ln")
bitarray = _u5_to_bitarray(decoded_data)
# final signature 65 bytes, split it off.
if len(bitarray) < 65 * 8:
raise ValueError("Too short to contain signature")
# extract the signature
signature = bitarray[-65 * 8 :].tobytes()
# the tagged fields as a bitstream
data = bitstring.ConstBitStream(bitarray[: -65 * 8])
# build the invoice object
invoice = Invoice()
# decode the amount from the hrp
m = re.search(r"[^\d]+", hrp[2:])
if m:
amountstr = hrp[2 + m.end() :]
if amountstr != "":
invoice.amount_msat = _unshorten_amount(amountstr)
# pull out date
date_bin = data.read(35)
invoice.date = date_bin.uint # type: ignore
while data.pos != data.len:
tag, tagdata, data = _pull_tagged(data)
data_length = len(tagdata or []) / 5
if tag == "d":
invoice.description = _trim_to_bytes(tagdata).decode()
elif tag == "h" and data_length == 52:
invoice.description_hash = _trim_to_bytes(tagdata).hex()
elif tag == "p" and data_length == 52:
invoice.payment_hash = _trim_to_bytes(tagdata).hex()
elif tag == "x":
invoice.expiry = tagdata.uint # type: ignore
elif tag == "n":
invoice.payee = _trim_to_bytes(tagdata).hex()
# this won't work in most cases, we must extract the payee
# from the signature
elif tag == "s":
invoice.secret = _trim_to_bytes(tagdata).hex()
elif tag == "r":
s = bitstring.ConstBitStream(tagdata)
while s.pos + 264 + 64 + 32 + 32 + 16 < s.len:
route = Route(
pubkey=s.read(264).tobytes().hex(), # type: ignore
short_channel_id=_readable_scid(s.read(64).intbe), # type: ignore
base_fee_msat=s.read(32).intbe, # type: ignore
ppm_fee=s.read(32).intbe, # type: ignore
cltv=s.read(16).intbe, # type: ignore
)
invoice.route_hints.append(route)
# BOLT #11:
# A reader MUST check that the `signature` is valid (see the `n` tagged
# field specified below).
# A reader MUST use the `n` field to validate the signature instead of
# performing signature recovery if a valid `n` field is provided.
message = bytearray([ord(c) for c in hrp]) + data.tobytes()
sig = signature[0:64]
if invoice.payee:
key = VerifyingKey.from_string(bytes.fromhex(invoice.payee), curve=SECP256k1)
key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string)
else:
keys = VerifyingKey.from_public_key_recovery(
sig, message, SECP256k1, hashlib.sha256
)
signaling_byte = signature[64]
key = keys[int(signaling_byte)]
invoice.payee = key.to_string("compressed").hex()
return invoice
def encode(options):
"""Convert options into LnAddr and pass it to the encoder"""
addr = LnAddr()
addr.currency = options["currency"]
addr.fallback = options["fallback"] if options["fallback"] else None
if options["amount"]:
addr.amount = options["amount"]
if options["timestamp"]:
addr.date = int(options["timestamp"])
addr.paymenthash = bytes.fromhex(options["paymenthash"])
if options["description"]:
addr.tags.append(("d", options["description"]))
if options["description_hash"]:
addr.tags.append(("h", options["description_hash"]))
if options["expires"]:
addr.tags.append(("x", options["expires"]))
if options["fallback"]:
addr.tags.append(("f", options["fallback"]))
if options["route"]:
for r in options["route"]:
splits = r.split("/")
route = []
while len(splits) >= 5:
route.append(
(
bytes.fromhex(splits[0]),
bytes.fromhex(splits[1]),
int(splits[2]),
int(splits[3]),
int(splits[4]),
)
)
splits = splits[5:]
assert len(splits) == 0
addr.tags.append(("r", route))
return lnencode(addr, options["privkey"])
def lnencode(addr, privkey):
if addr.amount:
amount = Decimal(str(addr.amount))
# We can only send down to millisatoshi.
if amount * 10**12 % 10:
raise ValueError(f"Cannot encode {addr.amount}: too many decimal places")
amount = addr.currency + shorten_amount(amount)
else:
amount = addr.currency if addr.currency else ""
hrp = f"ln{amount}0n"
# Start with the timestamp
data = bitstring.pack("uint:35", addr.date)
# Payment hash
data += tagged_bytes("p", addr.paymenthash)
tags_set = set()
for k, v in addr.tags:
# BOLT #11:
#
# A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields,
if k in ("d", "h", "n", "x"):
if k in tags_set:
raise ValueError(f"Duplicate '{k}' tag")
if k == "r":
route = bitstring.BitArray()
for step in v:
pubkey, channel, feebase, feerate, cltv = step
route.append(
bitstring.BitArray(pubkey)
+ bitstring.BitArray(channel)
+ bitstring.pack("intbe:32", feebase)
+ bitstring.pack("intbe:32", feerate)
+ bitstring.pack("intbe:16", cltv)
)
data += tagged("r", route)
elif k == "f":
# NOTE: there was an error fallback here that's now removed
continue
elif k == "d":
data += tagged_bytes("d", v.encode())
elif k == "x":
# Get minimal length by trimming leading 5 bits at a time.
expirybits = bitstring.pack("intbe:64", v)[4:64]
while expirybits.startswith("0b00000"):
expirybits = expirybits[5:]
data += tagged("x", expirybits)
elif k == "h":
data += tagged_bytes("h", v)
elif k == "n":
data += tagged_bytes("n", v)
else:
# FIXME: Support unknown tags?
raise ValueError(f"Unknown tag {k}")
tags_set.add(k)
# BOLT #11:
#
# A writer MUST include either a `d` or `h` field, and MUST NOT include
# both.
if "d" in tags_set and "h" in tags_set:
raise ValueError("Cannot include both 'd' and 'h'")
if "d" not in tags_set and "h" not in tags_set:
raise ValueError("Must include either 'd' or 'h'")
# We actually sign the hrp, then data (padded to 8 bits with zeroes).
privkey = secp256k1.PrivateKey(bytes.fromhex(privkey))
sig = privkey.ecdsa_sign_recoverable(
bytearray([ord(c) for c in hrp]) + data.tobytes()
)
# This doesn't actually serialize, but returns a pair of values :(
sig, recid = privkey.ecdsa_recoverable_serialize(sig)
data += bytes(sig) + bytes([recid])
return bech32_encode(hrp, bitarray_to_u5(data))
class LnAddr:
def __init__(
self,
paymenthash=None,
amount=None,
currency="bc",
tags=None,
date=None,
fallback=None,
):
self.date = int(time.time()) if not date else int(date)
self.tags = [] if not tags else tags
self.unknown_tags = []
self.paymenthash = paymenthash
self.signature = None
self.pubkey = None
self.fallback = fallback
self.currency = currency
self.amount = amount
def __str__(self):
assert self.pubkey, "LnAddr, pubkey must be set"
pubkey = bytes.hex(self.pubkey.serialize())
tags = ", ".join([f"{k}={v}" for k, v in self.tags])
return f"LnAddr[{pubkey}, amount={self.amount}{self.currency} tags=[{tags}]]"
def shorten_amount(amount):
"""Given an amount in bitcoin, shorten it"""
# Convert to pico initially
amount = int(amount * 10**12)
units = ["p", "n", "u", "m", ""]
unit = ""
for unit in units:
if amount % 1000 == 0:
amount //= 1000
else:
break
return str(amount) + unit
def _unshorten_amount(amount: str) -> int:
"""Given a shortened amount, return millisatoshis"""
# BOLT #11:
# The following `multiplier` letters are defined:
#
# * `m` (milli): multiply by 0.001
# * `u` (micro): multiply by 0.000001
# * `n` (nano): multiply by 0.000000001
# * `p` (pico): multiply by 0.000000000001
units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3}
unit = str(amount)[-1]
# BOLT #11:
# A reader SHOULD fail if `amount` contains a non-digit, or is followed by
# anything except a `multiplier` in the table above.
if not re.fullmatch(r"\d+[pnum]?", str(amount)):
raise ValueError(f"Invalid amount '{amount}'")
if unit in units:
return int(int(amount[:-1]) * 100_000_000_000 / units[unit])
else:
return int(amount) * 100_000_000_000
def _pull_tagged(stream):
tag = stream.read(5).uint
length = stream.read(5).uint * 32 + stream.read(5).uint
return (CHARSET[tag], stream.read(length * 5), stream)
# Tagged field containing BitArray
def tagged(char, bits):
# Tagged fields need to be zero-padded to 5 bits.
while bits.len % 5 != 0:
bits.append("0b0")
return (
bitstring.pack(
"uint:5, uint:5, uint:5",
CHARSET.find(char),
(bits.len / 5) / 32,
(bits.len / 5) % 32,
)
+ bits
)
def tagged_bytes(char, bits):
return tagged(char, bitstring.BitArray(bits))
def _trim_to_bytes(barr):
# Adds a byte if necessary.
b = barr.tobytes()
if barr.len % 8 != 0:
return b[:-1]
return b
def _readable_scid(short_channel_id: int) -> str:
blockheight = (short_channel_id >> 40) & 0xFFFFFF
transactionindex = (short_channel_id >> 16) & 0xFFFFFF
outputindex = short_channel_id & 0xFFFF
return f"{blockheight}x{transactionindex}x{outputindex}"
def _u5_to_bitarray(arr: List[int]) -> bitstring.BitArray:
ret = bitstring.BitArray()
for a in arr:
ret += bitstring.pack("uint:5", a)
return ret
def bitarray_to_u5(barr):
assert barr.len % 5 == 0
ret = []
s = bitstring.ConstBitStream(barr)
while s.pos != s.len:
ret.append(s.read(5).uint) # type: ignore
return ret
from bolt11 import (
Bolt11 as Invoice, # noqa: F401
)
from bolt11 import (
decode, # noqa: F401
encode, # noqa: F401
)
+671 -55
View File
@@ -1,77 +1,693 @@
import asyncio
import os
import warnings
import importlib
import sys
import time
from functools import wraps
from pathlib import Path
from typing import Optional
from uuid import uuid4
import click
import httpx
from fastapi.exceptions import HTTPException
from loguru import logger
from packaging import version
from lnbits.core import db as core_db
from lnbits.core.crud import (
delete_accounts_no_wallets,
delete_unused_wallets,
delete_wallet_by_id,
delete_wallet_payment,
get_db_versions,
get_installed_extension,
get_installed_extensions,
get_payment,
get_payments,
remove_deleted_wallets,
update_payment,
)
from lnbits.core.helpers import is_valid_url, migrate_databases
from lnbits.core.models import Account, Payment, PaymentState
from lnbits.core.models.extensions import (
CreateExtension,
ExtensionRelease,
InstallableExtension,
)
from lnbits.core.services import check_admin_settings, create_user_account_no_ckeck
from lnbits.core.views.extension_api import (
api_install_extension,
api_uninstall_extension,
)
from lnbits.settings import settings
from .core import db as core_db
from .core import migrations as core_migrations
from .core.crud import get_dbversions, get_inactive_extensions
from .core.helpers import migrate_extension_database, run_migration
from .db import COCKROACH, POSTGRES, SQLITE
from .extension_manager import get_valid_extensions
from lnbits.wallets.base import Wallet
@click.command("migrate")
def db_migrate():
asyncio.create_task(migrate_databases())
def coro(f):
@wraps(f)
def wrapper(*args, **kwargs):
return asyncio.run(f(*args, **kwargs))
return wrapper
@click.command("assets")
def handle_assets():
transpile_scss()
@click.group()
def lnbits_cli():
"""
Python CLI for LNbits
"""
def transpile_scss():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from scss.compiler import compile_string # type: ignore
with open(os.path.join(settings.lnbits_path, "static/scss/base.scss")) as scss:
with open(
os.path.join(settings.lnbits_path, "static/css/base.css"), "w"
) as css:
css.write(compile_string(scss.read()))
@lnbits_cli.group()
def db():
"""
Database related commands
"""
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
@lnbits_cli.group()
def users():
"""
Users related commands
"""
@lnbits_cli.group()
def extensions():
"""
Extensions related commands
"""
def get_super_user() -> Optional[str]:
"""Get the superuser"""
superuser_file = Path(settings.lnbits_data_folder, ".super_user")
if not superuser_file.exists() or not superuser_file.is_file():
raise ValueError(
"Superuser id not found. Please check that the file "
+ f"'{superuser_file.absolute()}' exists and has read permissions."
)
with open(superuser_file) as file:
return file.readline()
@lnbits_cli.command("superuser")
def superuser():
"""Prints the superuser"""
try:
click.echo(get_super_user())
except ValueError as e:
click.echo(str(e))
@lnbits_cli.command("superuser-url")
def superuser_url():
"""Prints the superuser"""
try:
click.echo(
f"http://{settings.host}:{settings.port}/wallet?usr={get_super_user()}"
)
except ValueError as e:
click.echo(str(e))
@lnbits_cli.command("delete-settings")
@coro
async def delete_settings():
"""Deletes the settings"""
async with core_db.connect() as conn:
exists = False
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='dbversions'"
)
elif conn.type in {POSTGRES, COCKROACH}:
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'dbversions'"
)
if not exists:
await core_migrations.m000_create_migrations_table(conn)
current_versions = await get_dbversions(conn)
core_version = current_versions.get("core", 0)
await run_migration(conn, core_migrations, core_version)
for ext in get_valid_extensions():
current_version = current_versions.get(ext.code, 0)
await migrate_extension_database(ext, current_version)
logger.info("✔️ All migrations done.")
await conn.execute("DELETE from system_settings")
@db.command("migrate")
def database_migrate():
"""Migrate databases"""
loop = asyncio.get_event_loop()
loop.run_until_complete(migrate_databases())
@db.command("versions")
@coro
async def db_versions():
"""Show current database versions"""
async with core_db.connect() as conn:
current_versions = await get_dbversions(conn)
return current_versions
click.echo(await get_db_versions(conn))
async def load_disabled_extension_list() -> None:
"""Update list of extensions that have been explicitly disabled"""
inactive_extensions = await get_inactive_extensions()
settings.lnbits_deactivated_extensions += inactive_extensions
@db.command("cleanup-wallets")
@click.argument("days", type=int, required=False)
@coro
async def database_cleanup_wallets(days: Optional[int] = None):
"""Delete all wallets that never had any transaction"""
async with core_db.connect() as conn:
delta = days or settings.cleanup_wallets_days
delta = delta * 24 * 60 * 60
await delete_unused_wallets(delta, conn)
@db.command("cleanup-deleted-wallets")
@coro
async def database_cleanup_deleted_wallets():
"""Delete all wallets that has been marked deleted"""
async with core_db.connect() as conn:
await remove_deleted_wallets(conn)
@db.command("delete-wallet")
@click.option("-w", "--wallet", required=True, help="ID of wallet to be deleted.")
@coro
async def database_delete_wallet(wallet: str):
"""Mark wallet as deleted"""
async with core_db.connect() as conn:
count = await delete_wallet_by_id(wallet_id=wallet, conn=conn)
click.echo(f"Marked as deleted '{count}' rows.")
@db.command("delete-wallet-payment")
@click.option("-w", "--wallet", required=True, help="ID of wallet to be deleted.")
@click.option("-c", "--checking-id", required=True, help="Payment checking Id.")
@coro
async def database_delete_wallet_payment(wallet: str, checking_id: str):
"""Delete wallet payment"""
async with core_db.connect() as conn:
await delete_wallet_payment(
wallet_id=wallet, checking_id=checking_id, conn=conn
)
@db.command("mark-payment-pending")
@click.option("-c", "--checking-id", required=True, help="Payment checking Id.")
@coro
async def database_revert_payment(checking_id: str):
"""Mark payment as pending"""
async with core_db.connect() as conn:
payment = await get_payment(checking_id=checking_id, conn=conn)
payment.status = PaymentState.PENDING
await update_payment(payment, conn=conn)
click.echo(f"Payment '{checking_id}' marked as pending.")
@db.command("check-payments")
@click.option("-d", "--days", help="Maximum age of payments in days.")
@click.option("-l", "--limit", help="Maximum number of payments to be checked.")
@click.option("-w", "--wallet", help="Only check for this wallet.")
@click.option("-v", "--verbose", is_flag=True, help="Detailed log.")
@coro
async def check_invalid_payments(
days: Optional[int] = None,
limit: Optional[int] = None,
wallet: Optional[str] = None,
verbose: Optional[bool] = False,
):
"""Check payments that are settled in the DB but pending on the Funding Source"""
await check_admin_settings()
settled_db_payments = []
if verbose:
click.echo(f"Get Payments: days={days}, limit={limit}, wallet={wallet}")
async with core_db.connect() as conn:
delta = int(days) if days else 3 # default to 3 days
limit = int(limit) if limit else 1000
since = int(time.time()) - delta * 24 * 60 * 60
settled_db_payments = await get_payments(
complete=True,
incoming=True,
exclude_uncheckable=True,
since=since,
limit=limit,
wallet_id=wallet,
conn=conn,
)
click.echo("Settled Payments: " + str(len(settled_db_payments)))
wallets_module = importlib.import_module("lnbits.wallets")
wallet_class = getattr(wallets_module, settings.lnbits_backend_wallet_class)
funding_source: Wallet = wallet_class()
click.echo("Funding source: " + str(funding_source))
# payments that are settled in the DB, but not at the Funding source level
invalid_payments: list[Payment] = []
invalid_wallets = {}
for db_payment in settled_db_payments:
if verbose:
click.echo(
f"Checking Payment: '{db_payment.checking_id}' for wallet"
+ f" '{db_payment.wallet_id}'."
)
payment_status = await funding_source.get_invoice_status(db_payment.checking_id)
if payment_status.pending:
invalid_payments.append(db_payment)
if db_payment.wallet_id not in invalid_wallets:
invalid_wallets[f"{db_payment.wallet_id}"] = [0, 0]
invalid_wallets[f"{db_payment.wallet_id}"][0] += 1
invalid_wallets[f"{db_payment.wallet_id}"][1] += db_payment.amount
click.echo(
"Invalid Payment: '"
+ " ".join(
[
db_payment.checking_id,
db_payment.wallet_id,
str(db_payment.amount / 1000).ljust(10),
db_payment.memo or "",
]
)
+ "'"
)
click.echo("Invalid Payments: " + str(len(invalid_payments)))
click.echo("\nInvalid Wallets: " + str(len(invalid_wallets)))
for w in invalid_wallets:
data = invalid_wallets[f"{w}"]
click.echo(" ".join([w, str(data[0]), str(data[1] / 1000).ljust(10)]))
@users.command("new")
@click.option("-u", "--username", required=True, help="Username.")
@click.option("-p", "--password", required=True, help="Password.")
@coro
async def create_user(username: str, password: str):
"""Create a new user bypassing the system 'new_accounts_allowed' rules"""
account = Account(
id=uuid4().hex,
username=username,
)
account.hash_password(password)
user = await create_user_account_no_ckeck(account)
click.echo(f"User '{user.username}' created. Id: '{user.id}'")
@users.command("cleanup-accounts")
@click.argument("days", type=int, required=False)
@coro
async def database_cleanup_accounts(days: Optional[int] = None):
"""Delete all accounts that have no wallets"""
async with core_db.connect() as conn:
delta = days or settings.cleanup_wallets_days
delta = delta * 24 * 60 * 60
await delete_accounts_no_wallets(delta, conn)
@extensions.command("list")
@coro
async def extensions_list():
"""Show currently installed extensions"""
click.echo("Installed extensions:")
from lnbits.app import build_all_installed_extensions_list
for ext in await build_all_installed_extensions_list():
assert (
ext.meta and ext.meta.installed_release
), f"Extension {ext.id} has no installed_release"
click.echo(f" - {ext.id} ({ext.meta.installed_release.version})")
@extensions.command("update")
@click.argument("extension", required=False)
@click.option("-a", "--all-extensions", is_flag=True, help="Update all extensions.")
@click.option(
"-i", "--repo-index", help="Select the index of the repository to be used."
)
@click.option(
"-s",
"--source-repo",
help="""
Provide the repository URL to be used for updating.
The URL must be one present in `LNBITS_EXTENSIONS_MANIFESTS`
or configured via the Admin UI. This option is required only
if an extension is present in more than one repository.
""",
)
@click.option(
"-u",
"--url",
help="Use this option to update a running server. Eg: 'http://localhost:5000'.",
)
@click.option(
"-d",
"--admin-user",
help="Admin user ID (must have permissions to install extensions).",
)
@coro
async def extensions_update(
extension: Optional[str] = None,
all_extensions: Optional[bool] = False,
repo_index: Optional[str] = None,
source_repo: Optional[str] = None,
url: Optional[str] = None,
admin_user: Optional[str] = None,
):
"""
Update extension to the latest version.
If an extension is not present it will be instaled.
"""
if not extension and not all_extensions:
click.echo("Extension ID is required.")
click.echo("Or specify the '--all-extensions' flag to update all extensions")
return
if extension and all_extensions:
click.echo("Only one of extension ID or the '--all' flag must be specified")
return
if url and not is_valid_url(url):
click.echo(f"Invalid '--url' option value: {url}")
return
if not await _can_run_operation(url):
return
upgrades_dir = settings.lnbits_extensions_upgrade_path
Path(upgrades_dir).mkdir(parents=True, exist_ok=True)
sys.path.append(str(upgrades_dir))
if extension:
await update_extension(extension, repo_index, source_repo, url, admin_user)
return
click.echo("Updating all extensions:")
installed_extensions = await get_installed_extensions()
click.echo(f" {[e.id for e in installed_extensions]}")
updated_extensions = []
for e in installed_extensions:
try:
click.echo(f"""{"="*50} {e.id} {"="*(50-len(e.id))} """)
success, msg = await update_extension(
e.id, repo_index, source_repo, url, admin_user
)
if version:
updated_extensions.append(
{"id": e.id, "success": success, "message": msg}
)
except Exception as ex:
click.echo(f"Failed to install extension '{e.id}': {ex}")
if len(updated_extensions) == 0:
click.echo("No extension was updated.")
return
for u in sorted(updated_extensions, key=lambda d: str(d["id"])):
status = "updated to " if u["success"] else "not updated "
click.echo(
f"""'{u["id"]}' {" "*(20-len(str(u["id"])))}"""
+ f""" - {status}: '{u["message"]}'"""
)
@extensions.command("install")
@click.argument("extension")
@click.option(
"-i", "--repo-index", help="Select the index of the repository to be used."
)
@click.option(
"-s",
"--source-repo",
help="""
Provide the repository URL to be used for updating.
The URL must be one present in `LNBITS_EXTENSIONS_MANIFESTS`
or configured via the Admin UI. This option is required only
if an extension is present in more than one repository.
""",
)
@click.option(
"-u",
"--url",
help="Use this option to update a running server. Eg: 'http://localhost:5000'.",
)
@click.option(
"-d",
"--admin-user",
help="Admin user ID (must have permissions to install extensions).",
)
@coro
async def extensions_install(
extension: str,
repo_index: Optional[str] = None,
source_repo: Optional[str] = None,
url: Optional[str] = None,
admin_user: Optional[str] = None,
):
"""Install a extension"""
click.echo(f"Installing {extension}... {repo_index}")
if url and not is_valid_url(url):
click.echo(f"Invalid '--url' option value: {url}")
return
if not await _can_run_operation(url):
return
await install_extension(extension, repo_index, source_repo, url, admin_user)
@extensions.command("uninstall")
@click.argument("extension")
@click.option(
"-u",
"--url",
help="Use this option to update a running server. Eg: 'http://localhost:5000'.",
)
@click.option(
"-d",
"--admin-user",
help="Admin user ID (must have permissions to install extensions).",
)
@coro
async def extensions_uninstall(
extension: str, url: Optional[str] = None, admin_user: Optional[str] = None
):
"""Uninstall a extension"""
click.echo(f"Uninstalling '{extension}'...")
if url and not is_valid_url(url):
click.echo(f"Invalid '--url' option value: {url}")
return
if not await _can_run_operation(url):
return
try:
await _call_uninstall_extension(extension, url, admin_user)
click.echo(f"Extension '{extension}' uninstalled.")
except HTTPException as ex:
click.echo(f"Failed to uninstall '{extension}' Error: '{ex.detail}'.")
return False, ex.detail
except Exception as ex:
click.echo(f"Failed to uninstall '{extension}': {ex!s}.")
return False, str(ex)
def main():
"""main function"""
lnbits_cli()
if __name__ == "__main__":
main()
async def install_extension(
extension: str,
repo_index: Optional[str] = None,
source_repo: Optional[str] = None,
url: Optional[str] = None,
admin_user: Optional[str] = None,
) -> tuple[bool, str]:
try:
release = await _select_release(extension, repo_index, source_repo)
if not release:
return False, "No release selected"
data = CreateExtension(
ext_id=extension,
archive=release.archive,
source_repo=release.source_repo,
version=release.version,
)
await _call_install_extension(data, url, admin_user)
click.echo(f"Extension '{extension}' ({release.version}) installed.")
return True, release.version
except HTTPException as ex:
click.echo(f"Failed to install '{extension}' Error: '{ex.detail}'.")
return False, ex.detail
except Exception as ex:
click.echo(f"Failed to install '{extension}': {ex!s}.")
return False, str(ex)
async def update_extension(
extension: str,
repo_index: Optional[str] = None,
source_repo: Optional[str] = None,
url: Optional[str] = None,
admin_user: Optional[str] = None,
) -> tuple[bool, str]:
try:
click.echo(f"Updating '{extension}' extension.")
installed_ext = await get_installed_extension(extension)
if not installed_ext:
click.echo(
f"Extension '{extension}' is not installed. Preparing to install..."
)
return await install_extension(extension, repo_index, source_repo, url)
click.echo(f"Current '{extension}' version: {installed_ext.installed_version}.")
assert (
installed_ext.meta and installed_ext.meta.installed_release
), "Cannot find previously installed release. Please uninstall first."
release = await _select_release(extension, repo_index, source_repo)
if not release:
return False, "No release selected."
if (
release.version == installed_ext.installed_version
and release.source_repo == installed_ext.meta.installed_release.source_repo
):
click.echo(f"Extension '{extension}' already up to date.")
return False, "Already up to date"
click.echo(f"Updating '{extension}' extension to version: {release.version }")
data = CreateExtension(
ext_id=extension,
archive=release.archive,
source_repo=release.source_repo,
version=release.version,
)
await _call_install_extension(data, url, admin_user)
click.echo(f"Extension '{extension}' updated.")
return True, release.version
except HTTPException as ex:
click.echo(f"Failed to update '{extension}' Error: '{ex.detail}.")
return False, ex.detail
except Exception as ex:
click.echo(f"Failed to update '{extension}': {ex!s}.")
return False, str(ex)
async def _select_release(
extension: str,
repo_index: Optional[str] = None,
source_repo: Optional[str] = None,
) -> Optional[ExtensionRelease]:
all_releases = await InstallableExtension.get_extension_releases(extension)
if len(all_releases) == 0:
click.echo(f"No repository found for extension '{extension}'.")
return None
latest_repo_releases = _get_latest_release_per_repo(all_releases)
if source_repo:
if source_repo not in latest_repo_releases:
click.echo(f"Repository not found: '{source_repo}'")
return None
return latest_repo_releases[source_repo]
if len(latest_repo_releases) == 1:
return latest_repo_releases[next(iter(latest_repo_releases.keys()))]
repos = list(latest_repo_releases.keys())
repos.sort()
if not repo_index:
click.echo("Multiple repos found. Please select one using:")
click.echo(" --repo-index option for index of the repo, or")
click.echo(" --source-repo option for the manifest URL")
click.echo("")
click.echo("Repositories: ")
for index, repo in enumerate(repos):
release = latest_repo_releases[repo]
click.echo(f" [{index}] {repo} --> {release.version}")
click.echo("")
return None
if not repo_index.isnumeric() or not 0 <= int(repo_index) < len(repos):
click.echo(f"--repo-index must be between '0' and '{len(repos) - 1}'")
return None
return latest_repo_releases[repos[int(repo_index)]]
def _get_latest_release_per_repo(all_releases):
latest_repo_releases = {}
for release in all_releases:
try:
if not release.is_version_compatible:
continue
# do not remove, parsing also validates
release_version = version.parse(release.version)
if release.source_repo not in latest_repo_releases:
latest_repo_releases[release.source_repo] = release
continue
if release_version > version.parse(
latest_repo_releases[release.source_repo].version
):
latest_repo_releases[release.source_repo] = release
except version.InvalidVersion as ex:
logger.warning(f"Invalid version {release.name}: {ex}")
return latest_repo_releases
async def _call_install_extension(
data: CreateExtension, url: Optional[str], user_id: Optional[str] = None
):
if url:
user_id = user_id or get_super_user()
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{url}/api/v1/extension?usr={user_id}", json=data.dict(), timeout=40
)
resp.raise_for_status()
else:
await api_install_extension(data)
async def _call_uninstall_extension(
extension: str, url: Optional[str], user_id: Optional[str] = None
):
if url:
user_id = user_id or get_super_user()
async with httpx.AsyncClient() as client:
resp = await client.delete(
f"{url}/api/v1/extension/{extension}?usr={user_id}", timeout=40
)
resp.raise_for_status()
else:
await api_uninstall_extension(extension)
async def _can_run_operation(url) -> bool:
await check_admin_settings()
if await _is_lnbits_started(url):
if not url:
click.echo("LNbits server is started. Please either:")
click.echo(
f" - use the '--url' option. Eg: --url=http://{settings.host}:{settings.port}"
)
click.echo(
f" - stop the server running at 'http://{settings.host}:{settings.port}'"
)
return False
elif url:
click.echo(
"The option '--url' has been provided,"
f" but no server found running at '{url}'"
)
return False
return True
async def _is_lnbits_started(url: Optional[str]):
try:
url = url or f"http://{settings.host}:{settings.port}/api/v1/health"
async with httpx.AsyncClient() as client:
await client.get(url)
return True
except Exception:
return False
+38 -10
View File
@@ -1,15 +1,43 @@
from fastapi.routing import APIRouter
from fastapi import APIRouter, FastAPI
from lnbits.core.models import CoreAppExtra
from lnbits.db import Database
from .db import core_app_extra, db
from .views.admin_api import admin_router
from .views.api import api_router
from .views.audit_api import audit_router
from .views.auth_api import auth_router
from .views.extension_api import extension_router
db = Database("database")
# this compat is needed for usermanager extension
from .views.generic import generic_router
from .views.node_api import node_router, public_node_router, super_node_router
from .views.payment_api import payment_router
from .views.tinyurl_api import tinyurl_router
from .views.user_api import users_router
from .views.wallet_api import wallet_router
from .views.webpush_api import webpush_router
from .views.websocket_api import websocket_router
core_app: APIRouter = APIRouter()
# backwards compatibility for extensions
core_app = APIRouter(tags=["Core"])
core_app_extra: CoreAppExtra = CoreAppExtra()
from .views.admin_api import * # noqa: F401,F403
from .views.api import * # noqa: F401,F403
from .views.generic import * # noqa: F401,F403
from .views.public_api import * # noqa: F401,F403
def init_core_routers(app: FastAPI):
app.include_router(core_app)
app.include_router(generic_router)
app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(node_router)
app.include_router(extension_router)
app.include_router(super_node_router)
app.include_router(public_node_router)
app.include_router(payment_router)
app.include_router(wallet_router)
app.include_router(api_router)
app.include_router(websocket_router)
app.include_router(tinyurl_router)
app.include_router(webpush_router)
app.include_router(users_router)
app.include_router(audit_router)
__all__ = ["core_app", "core_app_extra", "db"]
-790
View File
@@ -1,790 +0,0 @@
import datetime
import json
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
from uuid import uuid4
import shortuuid
from lnbits import bolt11
from lnbits.db import COCKROACH, POSTGRES, Connection, Filters
from lnbits.extension_manager import InstallableExtension
from lnbits.settings import AdminSettings, EditableSettings, SuperSettings, settings
from . import db
from .models import BalanceCheck, Payment, TinyURL, User, Wallet
# accounts
# --------
async def create_account(conn: Optional[Connection] = None) -> User:
user_id = uuid4().hex
await (conn or db).execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
new_account = await get_account(user_id=user_id, conn=conn)
assert new_account, "Newly created account couldn't be retrieved"
return new_account
async def get_account(
user_id: str, conn: Optional[Connection] = None
) -> Optional[User]:
row = await (conn or db).fetchone(
"SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,)
)
return User(**row) if row else None
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
user = await (conn or db).fetchone(
"SELECT id, email FROM accounts WHERE id = ?", (user_id,)
)
if user:
extensions = await (conn or db).fetchall(
"""SELECT extension FROM extensions WHERE "user" = ? AND active""",
(user_id,),
)
wallets = await (conn or db).fetchall(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
WHERE "user" = ?
""",
(user_id,),
)
else:
return None
return User(
id=user["id"],
email=user["email"],
extensions=[
e[0] for e in extensions if User.is_extension_for_user(e[0], user["id"])
],
wallets=[Wallet(**w) for w in wallets],
admin=user["id"] == settings.super_user
or user["id"] in settings.lnbits_admin_users,
)
# extensions
# -------
async def add_installed_extension(
ext: InstallableExtension,
conn: Optional[Connection] = None,
) -> None:
meta = {
"installed_release": dict(ext.installed_release)
if ext.installed_release
else None,
"dependencies": ext.dependencies,
}
version = ext.installed_release.version if ext.installed_release else ""
await (conn or db).execute(
"""
INSERT INTO installed_extensions (id, version, name, short_description, icon, stars, meta) VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO
UPDATE SET (version, name, active, short_description, icon, stars, meta) = (?, ?, ?, ?, ?, ?, ?)
""",
(
ext.id,
version,
ext.name,
ext.short_description,
ext.icon,
ext.stars,
json.dumps(meta),
version,
ext.name,
False,
ext.short_description,
ext.icon,
ext.stars,
json.dumps(meta),
),
)
async def update_installed_extension_state(
*, ext_id: str, active: bool, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
UPDATE installed_extensions SET active = ? WHERE id = ?
""",
(active, ext_id),
)
async def delete_installed_extension(
*, ext_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
DELETE from installed_extensions WHERE id = ?
""",
(ext_id,),
)
async def get_installed_extension(ext_id: str, conn: Optional[Connection] = None):
row = await (conn or db).fetchone(
"SELECT * FROM installed_extensions WHERE id = ?",
(ext_id,),
)
return dict(row) if row else None
async def get_installed_extensions(
conn: Optional[Connection] = None,
) -> List["InstallableExtension"]:
rows = await (conn or db).fetchall(
"SELECT * FROM installed_extensions",
(),
)
return [InstallableExtension.from_row(row) for row in rows]
async def get_inactive_extensions(*, conn: Optional[Connection] = None) -> List[str]:
inactive_extensions = await (conn or db).fetchall(
"""SELECT id FROM installed_extensions WHERE NOT active""",
(),
)
return [ext[0] for ext in inactive_extensions]
async def update_user_extension(
*, user_id: str, extension: str, active: bool, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
INSERT INTO extensions ("user", extension, active) VALUES (?, ?, ?)
ON CONFLICT ("user", extension) DO UPDATE SET active = ?
""",
(user_id, extension, active, active),
)
# wallets
# -------
async def create_wallet(
*,
user_id: str,
wallet_name: Optional[str] = None,
conn: Optional[Connection] = None,
) -> Wallet:
wallet_id = uuid4().hex
await (conn or db).execute(
"""
INSERT INTO wallets (id, name, "user", adminkey, inkey)
VALUES (?, ?, ?, ?, ?)
""",
(
wallet_id,
wallet_name or settings.lnbits_default_wallet_name,
user_id,
uuid4().hex,
uuid4().hex,
),
)
new_wallet = await get_wallet(wallet_id=wallet_id, conn=conn)
assert new_wallet, "Newly created wallet couldn't be retrieved"
return new_wallet
async def update_wallet(
wallet_id: str, new_name: str, conn: Optional[Connection] = None
) -> Optional[Wallet]:
await (conn or db).execute(
"""
UPDATE wallets SET
name = ?
WHERE id = ?
""",
(new_name, wallet_id),
)
wallet = await get_wallet(wallet_id=wallet_id, conn=conn)
assert wallet, "updated created wallet couldn't be retrieved"
return wallet
async def delete_wallet(
*, user_id: str, wallet_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
UPDATE wallets AS w
SET
"user" = 'del:' || w."user",
adminkey = 'del:' || w.adminkey,
inkey = 'del:' || w.inkey
WHERE id = ? AND "user" = ?
""",
(wallet_id, user_id),
)
async def get_wallet(
wallet_id: str, conn: Optional[Connection] = None
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
WHERE id = ?
""",
(wallet_id,),
)
return Wallet(**row) if row else None
async def get_wallet_for_key(
key: str, key_type: str = "invoice", conn: Optional[Connection] = None
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
WHERE adminkey = ? OR inkey = ?
""",
(key, key),
)
if not row:
return None
if key_type == "admin" and row["adminkey"] != key:
return None
return Wallet(**row)
async def get_total_balance(conn: Optional[Connection] = None):
row = await (conn or db).fetchone("SELECT SUM(balance) FROM balances")
return 0 if row[0] is None else row[0]
# wallet payments
# ---------------
async def get_standalone_payment(
checking_id_or_hash: str,
conn: Optional[Connection] = None,
incoming: Optional[bool] = False,
wallet_id: Optional[str] = None,
) -> Optional[Payment]:
clause: str = "checking_id = ? OR hash = ?"
values = [checking_id_or_hash, checking_id_or_hash]
if incoming:
clause = f"({clause}) AND amount > 0"
if wallet_id:
clause = f"({clause}) AND wallet = ?"
values.append(wallet_id)
row = await (conn or db).fetchone(
f"""
SELECT *
FROM apipayments
WHERE {clause}
LIMIT 1
""",
tuple(values),
)
return Payment.from_row(row) if row else None
async def get_wallet_payment(
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
) -> Optional[Payment]:
row = await (conn or db).fetchone(
"""
SELECT *
FROM apipayments
WHERE wallet = ? AND hash = ?
""",
(wallet_id, payment_hash),
)
return Payment.from_row(row) if row else None
async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5):
rows = await db.fetchall(
f"""
SELECT * FROM apipayments
WHERE pending = 'false'
AND extra LIKE ?
AND extra LIKE ?
ORDER BY time DESC LIMIT {limit}
""",
(
f"%{ext_name}%",
f"%{ext_id}%",
),
)
return rows
async def get_payments(
*,
wallet_id: Optional[str] = None,
complete: bool = False,
pending: bool = False,
outgoing: bool = False,
incoming: bool = False,
since: Optional[int] = None,
exclude_uncheckable: bool = False,
filters: Optional[Filters[Payment]] = None,
conn: Optional[Connection] = None,
) -> List[Payment]:
"""
Filters payments to be returned by complete | pending | outgoing | incoming.
"""
args: List[Any] = []
clause: List[str] = []
if since is not None:
if db.type == POSTGRES:
clause.append("time > to_timestamp(?)")
elif db.type == COCKROACH:
clause.append("time > cast(? AS timestamp)")
else:
clause.append("time > ?")
args.append(since)
if wallet_id:
clause.append("wallet = ?")
args.append(wallet_id)
if complete and pending:
pass
elif complete:
clause.append("((amount > 0 AND pending = false) OR amount < 0)")
elif pending:
clause.append("pending = true")
else:
pass
if outgoing and incoming:
pass
elif outgoing:
clause.append("amount < 0")
elif incoming:
clause.append("amount > 0")
else:
pass
if exclude_uncheckable: # checkable means it has a checking_id that isn't internal
clause.append("checking_id NOT LIKE 'temp_%'")
clause.append("checking_id NOT LIKE 'internal_%'")
if not filters:
filters = Filters(limit=None, offset=None)
rows = await (conn or db).fetchall(
f"""
SELECT *
FROM apipayments
{filters.where(clause)}
ORDER BY time DESC
{filters.pagination()}
""",
filters.values(args),
)
return [Payment.from_row(row) for row in rows]
async def delete_expired_invoices(
conn: Optional[Connection] = None,
) -> None:
# first we delete all invoices older than one month
await (conn or db).execute(
f"""
DELETE FROM apipayments
WHERE pending = true AND amount > 0
AND time < {db.timestamp_now} - {db.interval_seconds(2592000)}
"""
)
# then we delete all invoices whose expiry date is in the past
await (conn or db).execute(
f"""
DELETE FROM apipayments
WHERE pending = true AND amount > 0
AND expiry < {db.timestamp_now}
"""
)
# payments
# --------
async def create_payment(
*,
wallet_id: str,
checking_id: str,
payment_request: str,
payment_hash: str,
amount: int,
memo: str,
fee: int = 0,
preimage: Optional[str] = None,
pending: bool = True,
extra: Optional[Dict] = None,
webhook: Optional[str] = None,
conn: Optional[Connection] = None,
) -> Payment:
# todo: add this when tests are fixed
# previous_payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
# assert previous_payment is None, "Payment already exists"
try:
invoice = bolt11.decode(payment_request)
expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
except:
# assume maximum bolt11 expiry of 31 days to be on the safe side
expiration_date = datetime.datetime.now() + datetime.timedelta(days=31)
await (conn or db).execute(
"""
INSERT INTO apipayments
(wallet, checking_id, bolt11, hash, preimage,
amount, pending, memo, fee, extra, webhook, expiry)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
wallet_id,
checking_id,
payment_request,
payment_hash,
preimage,
amount,
pending,
memo,
fee,
json.dumps(extra)
if extra and extra != {} and type(extra) is dict
else None,
webhook,
db.datetime_to_timestamp(expiration_date),
),
)
new_payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
assert new_payment, "Newly created payment couldn't be retrieved"
return new_payment
async def update_payment_status(
checking_id: str, pending: bool, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"UPDATE apipayments SET pending = ? WHERE checking_id = ?",
(pending, checking_id),
)
async def update_payment_details(
checking_id: str,
pending: Optional[bool] = None,
fee: Optional[int] = None,
preimage: Optional[str] = None,
new_checking_id: Optional[str] = None,
conn: Optional[Connection] = None,
) -> None:
set_clause: List[str] = []
set_variables: List[Any] = []
if new_checking_id is not None:
set_clause.append("checking_id = ?")
set_variables.append(new_checking_id)
if pending is not None:
set_clause.append("pending = ?")
set_variables.append(pending)
if fee is not None:
set_clause.append("fee = ?")
set_variables.append(fee)
if preimage is not None:
set_clause.append("preimage = ?")
set_variables.append(preimage)
set_variables.append(checking_id)
await (conn or db).execute(
f"UPDATE apipayments SET {', '.join(set_clause)} WHERE checking_id = ?",
tuple(set_variables),
)
return
async def update_payment_extra(
payment_hash: str,
extra: dict,
outgoing: bool = False,
conn: Optional[Connection] = None,
) -> None:
"""
Only update the `extra` field for the payment.
Old values in the `extra` JSON object will be kept unless the new `extra` overwrites them.
"""
amount_clause = "AND amount < 0" if outgoing else "AND amount > 0"
row = await (conn or db).fetchone(
f"SELECT hash, extra from apipayments WHERE hash = ? {amount_clause}",
(payment_hash,),
)
if not row:
return
db_extra = json.loads(row["extra"] if row["extra"] else "{}")
db_extra.update(extra)
await (conn or db).execute(
f"UPDATE apipayments SET extra = ? WHERE hash = ? {amount_clause} ",
(json.dumps(db_extra), payment_hash),
)
async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
)
async def delete_wallet_payment(
checking_id: str, wallet_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ? AND wallet = ?",
(checking_id, wallet_id),
)
async def check_internal(
payment_hash: str, conn: Optional[Connection] = None
) -> Optional[str]:
row = await (conn or db).fetchone(
"""
SELECT checking_id FROM apipayments
WHERE hash = ? AND pending AND amount > 0
""",
(payment_hash,),
)
if not row:
return None
else:
return row["checking_id"]
async def check_internal_pending(
payment_hash: str, conn: Optional[Connection] = None
) -> bool:
"""Returns False if the internal payment is not pending anymore (and thus paid), otherwise True"""
row = await (conn or db).fetchone(
"""
SELECT pending FROM apipayments
WHERE hash = ? AND amount > 0
""",
(payment_hash,),
)
if not row:
return True
else:
return row["pending"]
# balance_check
# -------------
async def save_balance_check(
wallet_id: str, url: str, conn: Optional[Connection] = None
):
domain = urlparse(url).netloc
await (conn or db).execute(
"""
INSERT INTO balance_check (wallet, service, url) VALUES (?, ?, ?)
ON CONFLICT (wallet, service) DO UPDATE SET url = ?
""",
(wallet_id, domain, url, url),
)
async def get_balance_check(
wallet_id: str, domain: str, conn: Optional[Connection] = None
) -> Optional[BalanceCheck]:
row = await (conn or db).fetchone(
"""
SELECT wallet, service, url
FROM balance_check
WHERE wallet = ? AND service = ?
""",
(wallet_id, domain),
)
return BalanceCheck.from_row(row) if row else None
async def get_balance_checks(conn: Optional[Connection] = None) -> List[BalanceCheck]:
rows = await (conn or db).fetchall("SELECT wallet, service, url FROM balance_check")
return [BalanceCheck.from_row(row) for row in rows]
# balance_notify
# --------------
async def save_balance_notify(
wallet_id: str, url: str, conn: Optional[Connection] = None
):
await (conn or db).execute(
"""
INSERT INTO balance_notify (wallet, url) VALUES (?, ?)
ON CONFLICT (wallet) DO UPDATE SET url = ?
""",
(wallet_id, url, url),
)
async def get_balance_notify(
wallet_id: str, conn: Optional[Connection] = None
) -> Optional[str]:
row = await (conn or db).fetchone(
"""
SELECT url
FROM balance_notify
WHERE wallet = ?
""",
(wallet_id,),
)
return row[0] if row else None
# admin
# --------
async def get_super_settings() -> Optional[SuperSettings]:
row = await db.fetchone("SELECT * FROM settings")
if not row:
return None
editable_settings = json.loads(row["editable_settings"])
return SuperSettings(**{"super_user": row["super_user"], **editable_settings})
async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSettings]:
sets = await get_super_settings()
if not sets:
return None
row_dict = dict(sets)
row_dict.pop("super_user")
admin_settings = AdminSettings(
super_user=is_super_user,
lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
**row_dict,
)
return admin_settings
async def delete_admin_settings():
await db.execute("DELETE FROM settings")
async def update_admin_settings(data: EditableSettings):
await db.execute("UPDATE settings SET editable_settings = ?", (json.dumps(data),))
async def update_super_user(super_user: str) -> SuperSettings:
await db.execute("UPDATE settings SET super_user = ?", (super_user,))
settings = await get_super_settings()
assert settings, "updated super_user settings could not be retrieved"
return settings
async def create_admin_settings(super_user: str, new_settings: dict):
sql = "INSERT INTO settings (super_user, editable_settings) VALUES (?, ?)"
await db.execute(sql, (super_user, json.dumps(new_settings)))
settings = await get_super_settings()
assert settings, "created admin settings could not be retrieved"
return settings
# db versions
# --------------
async def get_dbversions(conn: Optional[Connection] = None):
rows = await (conn or db).fetchall("SELECT * FROM dbversions")
return {row["db"]: row["version"] for row in rows}
async def update_migration_version(conn, db_name, version):
await (conn or db).execute(
"""
INSERT INTO dbversions (db, version) VALUES (?, ?)
ON CONFLICT (db) DO UPDATE SET version = ?
""",
(db_name, version, version),
)
# tinyurl
# -------
async def create_tinyurl(domain: str, endless: bool, wallet: str):
tinyurl_id = shortuuid.uuid()[:8]
await db.execute(
"INSERT INTO tiny_url (id, url, endless, wallet) VALUES (?, ?, ?, ?)",
(
tinyurl_id,
domain,
endless,
wallet,
),
)
return await get_tinyurl(tinyurl_id)
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
row = await db.fetchone(
"SELECT * FROM tiny_url WHERE id = ?",
(tinyurl_id,),
)
return TinyURL.from_row(row) if row else None
async def get_tinyurl_by_url(url: str) -> List[TinyURL]:
rows = await db.fetchall(
"SELECT * FROM tiny_url WHERE url = ?",
(url,),
)
return [TinyURL.from_row(row) for row in rows]
async def delete_tinyurl(tinyurl_id: str):
await db.execute(
"DELETE FROM tiny_url WHERE id = ?",
(tinyurl_id,),
)
+169
View File
@@ -0,0 +1,169 @@
from .audit import create_audit_entry
from .db_versions import (
delete_dbversion,
get_db_version,
get_db_versions,
update_migration_version,
)
from .extensions import (
create_installed_extension,
create_user_extension,
delete_installed_extension,
drop_extension_db,
get_installed_extension,
get_installed_extensions,
get_user_active_extensions_ids,
get_user_extension,
get_user_extensions,
update_installed_extension,
update_installed_extension_state,
update_user_extension,
)
from .payments import (
DateTrunc,
check_internal,
create_payment,
delete_expired_invoices,
delete_wallet_payment,
get_latest_payments_by_extension,
get_payment,
get_payments,
get_payments_history,
get_payments_paginated,
get_standalone_payment,
get_wallet_payment,
is_internal_status_success,
mark_webhook_sent,
update_payment,
update_payment_checking_id,
update_payment_extra,
)
from .settings import (
create_admin_settings,
delete_admin_settings,
get_admin_settings,
get_super_settings,
update_admin_settings,
update_super_user,
)
from .tinyurl import create_tinyurl, delete_tinyurl, get_tinyurl, get_tinyurl_by_url
from .users import (
create_account,
delete_account,
delete_accounts_no_wallets,
get_account,
get_account_by_email,
get_account_by_pubkey,
get_account_by_username,
get_account_by_username_or_email,
get_accounts,
get_user,
get_user_access_control_lists,
get_user_from_account,
update_account,
)
from .wallets import (
create_wallet,
delete_unused_wallets,
delete_wallet,
delete_wallet_by_id,
force_delete_wallet,
get_total_balance,
get_wallet,
get_wallet_for_key,
get_wallets,
remove_deleted_wallets,
update_wallet,
)
from .webpush import (
create_webpush_subscription,
delete_webpush_subscription,
delete_webpush_subscriptions,
get_webpush_subscription,
get_webpush_subscriptions_for_user,
)
__all__ = [
# audit
"create_audit_entry",
# db_versions
"get_db_version",
"get_db_versions",
"update_migration_version",
"delete_dbversion",
# extensions
"create_installed_extension",
"create_user_extension",
"delete_installed_extension",
"drop_extension_db",
"get_installed_extension",
"get_installed_extensions",
"get_user_active_extensions_ids",
"get_user_extension",
"update_installed_extension",
"update_installed_extension_state",
"update_user_extension",
"get_user_extensions",
# payments
"DateTrunc",
"check_internal",
"create_payment",
"delete_expired_invoices",
"delete_wallet_payment",
"get_latest_payments_by_extension",
"get_payment",
"get_payments",
"get_payments_history",
"get_payments_paginated",
"get_standalone_payment",
"get_wallet_payment",
"is_internal_status_success",
"mark_webhook_sent",
"update_payment",
"update_payment_checking_id",
"update_payment_extra",
# settings
"create_admin_settings",
"delete_admin_settings",
"get_admin_settings",
"get_super_settings",
"update_admin_settings",
"update_super_user",
# tinyurl
"create_tinyurl",
"delete_tinyurl",
"get_tinyurl",
"get_tinyurl_by_url",
# users
"create_account",
"delete_account",
"delete_accounts_no_wallets",
"get_account",
"get_account_by_email",
"get_account_by_pubkey",
"get_account_by_username",
"get_account_by_username_or_email",
"get_accounts",
"get_user",
"get_user_from_account",
"get_user_access_control_lists",
"update_account",
# wallets
"create_wallet",
"delete_unused_wallets",
"delete_wallet",
"delete_wallet_by_id",
"force_delete_wallet",
"get_total_balance",
"get_wallet",
"get_wallet_for_key",
"get_wallets",
"remove_deleted_wallets",
"update_wallet",
# webpush
"create_webpush_subscription",
"delete_webpush_subscription",
"delete_webpush_subscriptions",
"get_webpush_subscription",
"get_webpush_subscriptions_for_user",
]
+84
View File
@@ -0,0 +1,84 @@
from typing import Optional
from lnbits.core.db import db
from lnbits.core.models import AuditEntry, AuditFilters
from lnbits.core.models.audit import AuditCountStat
from lnbits.db import Connection, Filters, Page
async def create_audit_entry(
entry: AuditEntry,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).insert("audit", entry)
async def get_audit_entries(
filters: Optional[Filters[AuditFilters]] = None,
conn: Optional[Connection] = None,
) -> Page[AuditEntry]:
return await (conn or db).fetch_page(
"SELECT * from audit",
[],
{},
filters=filters,
model=AuditEntry,
)
async def delete_expired_audit_entries(
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
DELETE from audit
WHERE delete_at < {db.timestamp_now}
""",
)
async def get_count_stats(
field: str,
filters: Optional[Filters[AuditFilters]] = None,
conn: Optional[Connection] = None,
) -> list[AuditCountStat]:
if field not in ["request_method", "component", "response_code"]:
return []
if not filters:
filters = Filters()
clause = filters.where()
data = await (conn or db).fetchall(
query=f"""
SELECT {field} as field, count({field}) as total
FROM audit
{clause}
GROUP BY {field}
ORDER BY {field}
""",
values=filters.values(),
model=AuditCountStat,
)
return data
async def get_long_duration_stats(
filters: Optional[Filters[AuditFilters]] = None,
conn: Optional[Connection] = None,
) -> list[AuditCountStat]:
if not filters:
filters = Filters()
clause = filters.where()
long_duration_paths = await (conn or db).fetchall(
query=f"""
SELECT path as field, max(duration) as total FROM audit
{clause}
GROUP BY path
ORDER BY total DESC
LIMIT 5
""",
values=filters.values(),
model=AuditCountStat,
)
return long_duration_paths
+39
View File
@@ -0,0 +1,39 @@
from typing import Optional
from lnbits.core.db import db
from lnbits.db import Connection
from ..models import DbVersion
async def get_db_version(
ext_id: str, conn: Optional[Connection] = None
) -> Optional[DbVersion]:
return await (conn or db).fetchone(
"SELECT * FROM dbversions WHERE db = :ext_id",
{"ext_id": ext_id},
model=DbVersion,
)
async def get_db_versions(conn: Optional[Connection] = None) -> list[DbVersion]:
return await (conn or db).fetchall("SELECT * FROM dbversions", model=DbVersion)
async def update_migration_version(conn, db_name, version):
await (conn or db).execute(
"""
INSERT INTO dbversions (db, version) VALUES (:db, :version)
ON CONFLICT (db) DO UPDATE SET version = :version
""",
{"db": db_name, "version": version},
)
async def delete_dbversion(*, ext_id: str, conn: Optional[Connection] = None) -> None:
await (conn or db).execute(
"""
DELETE FROM dbversions WHERE db = :ext
""",
{"ext": ext_id},
)
+137
View File
@@ -0,0 +1,137 @@
from typing import Optional
from lnbits.core.db import db
from lnbits.core.models.extensions import (
InstallableExtension,
UserExtension,
)
from lnbits.db import Connection, Database
async def create_installed_extension(
ext: InstallableExtension,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).insert("installed_extensions", ext)
async def update_installed_extension(
ext: InstallableExtension,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).update("installed_extensions", ext)
async def update_installed_extension_state(
*, ext_id: str, active: bool, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
UPDATE installed_extensions SET active = :active WHERE id = :ext
""",
{"ext": ext_id, "active": active},
)
async def delete_installed_extension(
*, ext_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
DELETE from installed_extensions WHERE id = :ext
""",
{"ext": ext_id},
)
async def drop_extension_db(ext_id: str, conn: Optional[Connection] = None) -> None:
row: dict = await (conn or db).fetchone(
"SELECT * FROM dbversions WHERE db = :id",
{"id": ext_id},
)
# Check that 'ext_id' is a valid extension id and not a malicious string
assert row, f"Extension '{ext_id}' db version cannot be found"
is_file_based_db = await Database.clean_ext_db_files(ext_id)
if is_file_based_db:
return
# String formatting is required, params are not accepted for 'DROP SCHEMA'.
# The `ext_id` value is verified above.
await (conn or db).execute(
f"DROP SCHEMA IF EXISTS {ext_id} CASCADE",
)
async def get_installed_extension(
ext_id: str, conn: Optional[Connection] = None
) -> Optional[InstallableExtension]:
extension = await (conn or db).fetchone(
"SELECT * FROM installed_extensions WHERE id = :id",
{"id": ext_id},
InstallableExtension,
)
return extension
async def get_installed_extensions(
active: Optional[bool] = None,
conn: Optional[Connection] = None,
) -> list[InstallableExtension]:
where = "WHERE active = :active" if active is not None else ""
values = {"active": active} if active is not None else {}
all_extensions = await (conn or db).fetchall(
f"SELECT * FROM installed_extensions {where}",
values,
model=InstallableExtension,
)
return all_extensions
async def get_user_extension(
user_id: str, extension: str, conn: Optional[Connection] = None
) -> Optional[UserExtension]:
return await (conn or db).fetchone(
"""
SELECT * FROM extensions
WHERE "user" = :user AND extension = :ext
""",
{"user": user_id, "ext": extension},
model=UserExtension,
)
async def get_user_extensions(
user_id: str, conn: Optional[Connection] = None
) -> list[UserExtension]:
return await (conn or db).fetchall(
"""SELECT * FROM extensions WHERE "user" = :user""",
{"user": user_id},
model=UserExtension,
)
async def create_user_extension(
user_extension: UserExtension, conn: Optional[Connection] = None
) -> None:
await (conn or db).insert("extensions", user_extension)
async def update_user_extension(
user_extension: UserExtension, conn: Optional[Connection] = None
) -> None:
where = """WHERE extension = :extension AND "user" = :user"""
await (conn or db).update("extensions", user_extension, where)
async def get_user_active_extensions_ids(
user_id: str, conn: Optional[Connection] = None
) -> list[str]:
exts = await (conn or db).fetchall(
"""
SELECT * FROM extensions WHERE "user" = :user AND active
""",
{"user": user_id},
UserExtension,
)
return [ext.extension for ext in exts]
+518
View File
@@ -0,0 +1,518 @@
from time import time
from typing import Any, Optional, Tuple
from lnbits.core.crud.wallets import get_total_balance, get_wallet
from lnbits.core.db import db
from lnbits.core.models import PaymentState
from lnbits.db import Connection, DateTrunc, Filters, Page
from ..models import (
CreatePayment,
Payment,
PaymentCountField,
PaymentCountStat,
PaymentDailyStats,
PaymentFilters,
PaymentHistoryPoint,
PaymentsStatusCount,
PaymentWalletStats,
)
def update_payment_extra():
pass
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
return await (conn or db).fetchone(
"SELECT * FROM apipayments WHERE checking_id = :checking_id",
{"checking_id": checking_id},
Payment,
)
async def get_standalone_payment(
checking_id_or_hash: str,
conn: Optional[Connection] = None,
incoming: Optional[bool] = False,
wallet_id: Optional[str] = None,
) -> Optional[Payment]:
clause: str = "checking_id = :checking_id OR payment_hash = :hash"
values = {
"wallet_id": wallet_id,
"checking_id": checking_id_or_hash,
"hash": checking_id_or_hash,
}
if incoming:
clause = f"({clause}) AND amount > 0"
if wallet_id:
clause = f"({clause}) AND wallet_id = :wallet_id"
row = await (conn or db).fetchone(
f"""
SELECT * FROM apipayments
WHERE {clause}
ORDER BY amount LIMIT 1
""",
values,
Payment,
)
return row
async def get_wallet_payment(
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
) -> Optional[Payment]:
payment = await (conn or db).fetchone(
"""
SELECT *
FROM apipayments
WHERE wallet_id = :wallet AND payment_hash = :hash
""",
{"wallet": wallet_id, "hash": payment_hash},
Payment,
)
return payment
async def get_latest_payments_by_extension(
ext_name: str, ext_id: str, limit: int = 5
) -> list[Payment]:
return await db.fetchall(
f"""
SELECT * FROM apipayments
WHERE status = '{PaymentState.SUCCESS}'
AND extra LIKE :ext_name
AND extra LIKE :ext_id
ORDER BY time DESC LIMIT {int(limit)}
""",
{"ext_name": f"%{ext_name}%", "ext_id": f"%{ext_id}%"},
Payment,
)
async def get_payments_paginated(
*,
wallet_id: Optional[str] = None,
complete: bool = False,
pending: bool = False,
failed: bool = False,
outgoing: bool = False,
incoming: bool = False,
since: Optional[int] = None,
exclude_uncheckable: bool = False,
filters: Optional[Filters[PaymentFilters]] = None,
conn: Optional[Connection] = None,
) -> Page[Payment]:
"""
Filters payments to be returned by:
- complete | pending | failed | outgoing | incoming.
"""
values: dict[str, Any] = {
"time": since,
}
clause: list[str] = []
if since is not None:
clause.append(f"time > {db.timestamp_placeholder('time')}")
if wallet_id:
values["wallet_id"] = wallet_id
clause.append("wallet_id = :wallet_id")
if complete and pending:
clause.append(
f"(status = '{PaymentState.SUCCESS}' OR status = '{PaymentState.PENDING}')"
)
elif complete:
clause.append(
f"""
(
status = '{PaymentState.SUCCESS}'
OR (amount < 0 AND status = '{PaymentState.PENDING}')
)
"""
)
elif pending:
clause.append(f"status = '{PaymentState.PENDING}'")
elif failed:
clause.append(f"status = '{PaymentState.FAILED}'")
if outgoing and incoming:
pass
elif outgoing:
clause.append("amount < 0")
elif incoming:
clause.append("amount > 0")
else:
pass
if exclude_uncheckable: # checkable means it has a checking_id that isn't internal
clause.append("checking_id NOT LIKE 'temp_%'")
clause.append("checking_id NOT LIKE 'internal_%'")
return await (conn or db).fetch_page(
"SELECT * FROM apipayments",
clause,
values,
filters=filters,
model=Payment,
)
async def get_payments(
*,
wallet_id: Optional[str] = None,
complete: bool = False,
pending: bool = False,
outgoing: bool = False,
incoming: bool = False,
since: Optional[int] = None,
exclude_uncheckable: bool = False,
filters: Optional[Filters[PaymentFilters]] = None,
conn: Optional[Connection] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> list[Payment]:
"""
Filters payments to be returned by complete | pending | outgoing | incoming.
"""
filters = filters or Filters()
filters.sortby = filters.sortby or "time"
filters.direction = filters.direction or "desc"
filters.limit = limit or filters.limit
filters.offset = offset or filters.offset
page = await get_payments_paginated(
wallet_id=wallet_id,
complete=complete,
pending=pending,
outgoing=outgoing,
incoming=incoming,
since=since,
exclude_uncheckable=exclude_uncheckable,
filters=filters,
conn=conn,
)
return page.data
async def get_payments_status_count() -> PaymentsStatusCount:
empty_page: Filters = Filters(limit=0)
in_payments = await get_payments_paginated(incoming=True, filters=empty_page)
out_payments = await get_payments_paginated(outgoing=True, filters=empty_page)
pending_payments = await get_payments_paginated(pending=True, filters=empty_page)
failed_payments = await get_payments_paginated(failed=True, filters=empty_page)
return PaymentsStatusCount(
incoming=in_payments.total,
outgoing=out_payments.total,
pending=pending_payments.total,
failed=failed_payments.total,
)
async def delete_expired_invoices(
conn: Optional[Connection] = None,
) -> None:
# first we delete all invoices older than one month
await (conn or db).execute(
f"""
DELETE FROM apipayments
WHERE status = '{PaymentState.PENDING}' AND amount > 0
AND time < {db.timestamp_placeholder("delta")}
""",
{"delta": int(time() - 2592000)},
)
# then we delete all invoices whose expiry date is in the past
await (conn or db).execute(
f"""
DELETE FROM apipayments
WHERE status = '{PaymentState.PENDING}' AND amount > 0
AND expiry < {db.timestamp_placeholder("now")}
""",
{"now": int(time())},
)
async def create_payment(
checking_id: str,
data: CreatePayment,
status: PaymentState = PaymentState.PENDING,
conn: Optional[Connection] = None,
) -> Payment:
# we don't allow the creation of the same invoice twice
# note: this can be removed if the db uniqueness constraints are set appropriately
previous_payment = await get_standalone_payment(checking_id, conn=conn)
assert previous_payment is None, "Payment already exists"
extra = data.extra or {}
payment = Payment(
checking_id=checking_id,
status=status,
wallet_id=data.wallet_id,
payment_hash=data.payment_hash,
bolt11=data.bolt11,
amount=data.amount_msat,
memo=data.memo,
preimage=data.preimage,
expiry=data.expiry,
webhook=data.webhook,
fee=data.fee,
tag=extra.get("tag", None),
extra=extra,
)
await (conn or db).insert("apipayments", payment)
return payment
async def update_payment_checking_id(
checking_id: str, new_checking_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id",
{"new_id": new_checking_id, "old_id": checking_id},
)
async def update_payment(
payment: Payment,
new_checking_id: Optional[str] = None,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).update(
"apipayments", payment, "WHERE checking_id = :checking_id"
)
if new_checking_id and new_checking_id != payment.checking_id:
await update_payment_checking_id(payment.checking_id, new_checking_id, conn)
async def get_payments_history(
wallet_id: Optional[str] = None,
group: DateTrunc = "day",
filters: Optional[Filters] = None,
) -> list[PaymentHistoryPoint]:
if not filters:
filters = Filters()
date_trunc = db.datetime_grouping(group)
values = {
"wallet_id": wallet_id,
}
# count outgoing payments if they are still pending
where = [
f"""
wallet_id = :wallet_id AND (
status = '{PaymentState.SUCCESS}'
OR (amount < 0 AND status = '{PaymentState.PENDING}')
)
"""
]
transactions: list[dict] = await db.fetchall(
f"""
SELECT {date_trunc} date,
SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) income,
SUM(CASE WHEN amount < 0 THEN abs(amount) + abs(fee) ELSE 0 END) spending
FROM apipayments
{filters.where(where)}
GROUP BY date
ORDER BY date DESC
""",
filters.values(values),
)
if wallet_id:
wallet = await get_wallet(wallet_id)
if wallet:
balance = wallet.balance_msat
else:
raise ValueError("Unknown wallet")
else:
balance = await get_total_balance()
# since we dont know the balance at the starting point,
# we take the current balance and walk backwards
results: list[PaymentHistoryPoint] = []
for row in transactions:
results.insert(
0,
PaymentHistoryPoint(
balance=balance,
date=row.get("date", 0),
income=row.get("income", 0),
spending=row.get("spending", 0),
),
)
balance -= row.get("income", 0) - row.get("spending", 0)
return results
async def get_payment_count_stats(
field: PaymentCountField,
filters: Optional[Filters[PaymentFilters]] = None,
conn: Optional[Connection] = None,
) -> list[PaymentCountStat]:
if not filters:
filters = Filters()
clause = filters.where()
data = await (conn or db).fetchall(
query=f"""
SELECT {field} as field, count(*) as total
FROM apipayments
{clause}
GROUP BY {field}
ORDER BY {field}
""",
values=filters.values(),
model=PaymentCountStat,
)
return data
async def get_daily_stats(
filters: Optional[Filters[PaymentFilters]] = None,
conn: Optional[Connection] = None,
) -> Tuple[list[PaymentDailyStats], list[PaymentDailyStats]]:
if not filters:
filters = Filters()
in_clause = filters.where(
["(apipayments.status = 'success' AND apipayments.amount > 0)"]
)
out_clause = filters.where(
["(apipayments.status IN ('success', 'pending') AND apipayments.amount < 0)"]
)
date_trunc = db.datetime_grouping("day")
query = """
SELECT {date_trunc} date,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance,
ABS(SUM(apipayments.fee)) as fee,
COUNT(*) as payments_count
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet_id = wallets.id
{clause}
AND (wallets.deleted = false OR wallets.deleted is NULL)
GROUP BY date
ORDER BY date ASC
"""
data_in = await (conn or db).fetchall(
query=query.format(date_trunc=date_trunc, clause=in_clause),
values=filters.values(),
model=PaymentDailyStats,
)
data_out = await (conn or db).fetchall(
query=query.format(date_trunc=date_trunc, clause=out_clause),
values=filters.values(),
model=PaymentDailyStats,
)
return data_in, data_out
async def get_wallets_stats(
filters: Optional[Filters[PaymentFilters]] = None,
conn: Optional[Connection] = None,
) -> list[PaymentWalletStats]:
if not filters:
filters = Filters()
where_stmts = [
"(wallets.deleted = false OR wallets.deleted is NULL)",
"""
(
(apipayments.status = 'success' AND apipayments.amount > 0)
OR (
apipayments.status IN ('success', 'pending')
AND apipayments.amount < 0
)
)
""",
]
clauses = filters.where(where_stmts)
data = await (conn or db).fetchall(
query=f"""
SELECT apipayments.wallet_id,
MAX(wallets.name) AS wallet_name,
MAX(wallets.user) AS user_id,
COUNT(*) as payments_count,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet_id = wallets.id
{clauses}
GROUP BY apipayments.wallet_id
ORDER BY payments_count
""",
values=filters.values(),
model=PaymentWalletStats,
)
return data
async def delete_wallet_payment(
checking_id: str, wallet_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = :checking_id AND wallet = :wallet",
{"checking_id": checking_id, "wallet": wallet_id},
)
async def check_internal(
payment_hash: str, conn: Optional[Connection] = None
) -> Optional[Payment]:
"""
Returns the checking_id of the internal payment if it exists,
otherwise None
"""
return await (conn or db).fetchone(
f"""
SELECT * FROM apipayments
WHERE payment_hash = :hash AND status = '{PaymentState.PENDING}' AND amount > 0
""",
{"hash": payment_hash},
Payment,
)
async def is_internal_status_success(
payment_hash: str, conn: Optional[Connection] = None
) -> bool:
"""
Returns True if the internal payment was found and is successful,
"""
payment = await (conn or db).fetchone(
"""
SELECT * FROM apipayments
WHERE payment_hash = :payment_hash AND amount > 0
""",
{"payment_hash": payment_hash},
Payment,
)
if not payment:
return False
return payment.status == PaymentState.SUCCESS.value
async def mark_webhook_sent(payment_hash: str, status: int) -> None:
await db.execute(
"""
UPDATE apipayments SET webhook_status = :status
WHERE payment_hash = :hash
""",
{"status": status, "hash": payment_hash},
)
+122
View File
@@ -0,0 +1,122 @@
import json
from typing import Any, Optional
from loguru import logger
from lnbits.core.db import db
from lnbits.db import dict_to_model
from lnbits.settings import (
AdminSettings,
EditableSettings,
SettingsField,
SuperSettings,
settings,
)
async def get_super_settings() -> Optional[SuperSettings]:
data = await get_settings_by_tag("core")
if data:
super_user = await get_settings_field("super_user")
super_user_id = super_user.value if super_user else None
settings_dict = {"super_user": super_user_id, **data}
return dict_to_model(settings_dict, SuperSettings)
return None
async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSettings]:
sets = await get_super_settings()
if not sets:
return None
row_dict = dict(sets)
row_dict.pop("super_user")
row_dict.pop("auth_all_methods")
admin_settings = AdminSettings(
is_super_user=is_super_user,
lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
**row_dict,
)
return admin_settings
async def update_admin_settings(
data: EditableSettings, tag: Optional[str] = "core"
) -> None:
editable_settings = await get_settings_by_tag("core") or {}
editable_settings.update(data.dict(exclude_unset=True))
for key, value in editable_settings.items():
try:
await set_settings_field(key, value, tag)
except Exception as exc:
logger.warning(exc)
logger.warning(f"Failed to update settings for '{tag}.{key}'.")
async def update_super_user(super_user: str) -> SuperSettings:
await set_settings_field("super_user", super_user)
settings = await get_super_settings()
assert settings, "updated super_user settings could not be retrieved"
return settings
async def delete_admin_settings(tag: Optional[str] = "core") -> None:
await db.execute("DELETE FROM settings WHERE tag = :tag", {"tag": tag})
async def create_admin_settings(super_user: str, new_settings: dict) -> SuperSettings:
data = {"super_user": super_user, **new_settings}
for key, value in data.items():
await set_settings_field(key, value)
settings = await get_super_settings()
assert settings, "created admin settings could not be retrieved"
return settings
async def get_settings_field(
id_: str, tag: Optional[str] = "core"
) -> Optional[SettingsField]:
row: dict = await db.fetchone(
"""
SELECT * FROM system_settings
WHERE id = :id AND tag = :tag
""",
{"id": id_, "tag": tag},
)
if not row:
return None
return SettingsField(id=row["id"], value=json.loads(row["value"]), tag=row["tag"])
async def set_settings_field(
id_: str, value: Optional[Any], tag: Optional[str] = "core"
):
value = json.dumps(value) if value is not None else None
await db.execute(
"""
INSERT INTO system_settings (id, value, tag)
VALUES (:id, :value, :tag)
ON CONFLICT (id, tag) DO UPDATE SET value = :value
""",
{"id": id_, "value": value, "tag": tag or "core"},
)
async def get_settings_by_tag(tag: str) -> Optional[dict[str, Any]]:
rows: list[dict] = await db.fetchall(
"SELECT * FROM system_settings WHERE tag = :tag", {"tag": tag}
)
if len(rows) == 0:
return None
data: dict[str, Any] = {}
for row in rows:
try:
data[row["id"]] = json.loads(row["value"]) if row["value"] else None
except Exception as _:
logger.warning(
f"""Failed to load settings value for '{tag}.{row["id"]}'."""
)
data.pop("super_user")
return data
+42
View File
@@ -0,0 +1,42 @@
from typing import Optional
import shortuuid
from lnbits.core.db import db
from ..models import TinyURL
async def create_tinyurl(domain: str, endless: bool, wallet: str):
tinyurl_id = shortuuid.uuid()[:8]
await db.execute(
"""
INSERT INTO tiny_url (id, url, endless, wallet)
VALUES (:tinyurl, :domain, :endless, :wallet)
""",
{"tinyurl": tinyurl_id, "domain": domain, "endless": endless, "wallet": wallet},
)
return await get_tinyurl(tinyurl_id)
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
return await db.fetchone(
"SELECT * FROM tiny_url WHERE id = :tinyurl",
{"tinyurl": tinyurl_id},
TinyURL,
)
async def get_tinyurl_by_url(url: str) -> list[TinyURL]:
return await db.fetchall(
"SELECT * FROM tiny_url WHERE url = :url",
{"url": url},
TinyURL,
)
async def delete_tinyurl(tinyurl_id: str):
await db.execute(
"DELETE FROM tiny_url WHERE id = :tinyurl",
{"tinyurl": tinyurl_id},
)
+211
View File
@@ -0,0 +1,211 @@
from datetime import datetime, timezone
from time import time
from typing import Any, Optional
from uuid import uuid4
from lnbits.core.crud.extensions import get_user_active_extensions_ids
from lnbits.core.crud.wallets import get_wallets
from lnbits.core.db import db
from lnbits.core.models import UserAcls
from lnbits.db import Connection, Filters, Page
from ..models import (
Account,
AccountFilters,
AccountOverview,
User,
)
async def create_account(
account: Optional[Account] = None,
conn: Optional[Connection] = None,
) -> Account:
if account:
account.validate_fields()
else:
now = datetime.now(timezone.utc)
account = Account(id=uuid4().hex, created_at=now, updated_at=now)
await (conn or db).insert("accounts", account)
return account
async def update_account(account: Account) -> Account:
account.updated_at = datetime.now(timezone.utc)
await db.update("accounts", account)
return account
async def delete_account(user_id: str, conn: Optional[Connection] = None) -> None:
await (conn or db).execute(
"DELETE from accounts WHERE id = :user",
{"user": user_id},
)
async def get_accounts(
filters: Optional[Filters[AccountFilters]] = None,
conn: Optional[Connection] = None,
) -> Page[AccountOverview]:
where_clauses = []
values: dict[str, Any] = {}
# Make wallet filter explicit
wallet_filter = (
next((f for f in filters.filters if f.field == "wallet_id"), None)
if filters
else None
)
if filters and wallet_filter and wallet_filter.values:
where_clauses.append("wallets.id = :wallet_id")
values = {**values, "wallet_id": next(iter(wallet_filter.values.values()))}
filters.filters = [f for f in filters.filters if f.field != "wallet_id"]
return await (conn or db).fetch_page(
"""
SELECT
accounts.id,
accounts.username,
accounts.email,
accounts.pubkey,
SUM(COALESCE((
SELECT balance FROM balances WHERE wallet_id = wallets.id
), 0)) as balance_msat,
SUM((
SELECT COUNT(*) FROM apipayments WHERE wallet_id = wallets.id
)) as transaction_count,
(
SELECT COUNT(*) FROM wallets WHERE wallets.user = accounts.id
) as wallet_count,
MAX((
SELECT time FROM apipayments
WHERE wallet_id = wallets.id ORDER BY time DESC LIMIT 1
)) as last_payment
FROM accounts LEFT JOIN wallets ON accounts.id = wallets.user
""",
where_clauses,
values,
filters=filters,
model=AccountOverview,
group_by=["accounts.id"],
)
async def get_account(
user_id: str, conn: Optional[Connection] = None
) -> Optional[Account]:
if len(user_id) == 0:
return None
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE id = :id",
{"id": user_id},
Account,
)
async def delete_accounts_no_wallets(
time_delta: int,
conn: Optional[Connection] = None,
) -> None:
delta = int(time()) - time_delta
await (conn or db).execute(
f"""
DELETE FROM accounts
WHERE NOT EXISTS (
SELECT wallets.id FROM wallets WHERE wallets.user = accounts.id
) AND (
(updated_at is null AND created_at < :delta)
OR updated_at < {db.timestamp_placeholder("delta")}
)
""",
{"delta": delta},
)
async def get_account_by_username(
username: str, conn: Optional[Connection] = None
) -> Optional[Account]:
if len(username) == 0:
return None
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE username = :username",
{"username": username},
Account,
)
async def get_account_by_pubkey(
pubkey: str, conn: Optional[Connection] = None
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE pubkey = :pubkey",
{"pubkey": pubkey},
Account,
)
async def get_account_by_email(
email: str, conn: Optional[Connection] = None
) -> Optional[Account]:
if len(email) == 0:
return None
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE email = :email",
{"email": email},
Account,
)
async def get_account_by_username_or_email(
username_or_email: str, conn: Optional[Connection] = None
) -> Optional[Account]:
return await (conn or db).fetchone(
"SELECT * FROM accounts WHERE email = :value or username = :value",
{"value": username_or_email},
Account,
)
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
account = await get_account(user_id, conn)
if not account:
return None
return await get_user_from_account(account, conn)
async def get_user_from_account(
account: Account, conn: Optional[Connection] = None
) -> Optional[User]:
extensions = await get_user_active_extensions_ids(account.id, conn)
wallets = await get_wallets(account.id, False, conn=conn)
return User(
id=account.id,
email=account.email,
username=account.username,
pubkey=account.pubkey,
extra=account.extra,
created_at=account.created_at,
updated_at=account.updated_at,
extensions=extensions,
wallets=wallets,
admin=account.is_admin,
super_user=account.is_super_user,
has_password=account.password_hash is not None,
)
async def update_user_access_control_list(user_acls: UserAcls):
user_acls.updated_at = datetime.now(timezone.utc)
await db.update("accounts", user_acls)
async def get_user_access_control_lists(
user_id: str, conn: Optional[Connection] = None
) -> UserAcls:
user_acls = await (conn or db).fetchone(
"SELECT id, access_control_list FROM accounts WHERE id = :id",
{"id": user_id},
UserAcls,
)
return user_acls or UserAcls(id=user_id)
+164
View File
@@ -0,0 +1,164 @@
from datetime import datetime, timezone
from time import time
from typing import Optional
from uuid import uuid4
from lnbits.core.db import db
from lnbits.db import Connection
from lnbits.settings import settings
from ..models import Wallet
async def create_wallet(
*,
user_id: str,
wallet_name: Optional[str] = None,
conn: Optional[Connection] = None,
) -> Wallet:
wallet_id = uuid4().hex
wallet = Wallet(
id=wallet_id,
name=wallet_name or settings.lnbits_default_wallet_name,
user=user_id,
adminkey=uuid4().hex,
inkey=uuid4().hex,
currency=settings.lnbits_default_accounting_currency or "USD",
)
await (conn or db).insert("wallets", wallet)
return wallet
async def update_wallet(
wallet: Wallet,
conn: Optional[Connection] = None,
) -> Optional[Wallet]:
wallet.updated_at = datetime.now(timezone.utc)
await (conn or db).update("wallets", wallet)
return wallet
async def delete_wallet(
*,
user_id: str,
wallet_id: str,
deleted: bool = True,
conn: Optional[Connection] = None,
) -> None:
now = int(time())
await (conn or db).execute(
f"""
UPDATE wallets
SET deleted = :deleted, updated_at = {db.timestamp_placeholder('now')}
WHERE id = :wallet AND "user" = :user
""",
{"wallet": wallet_id, "user": user_id, "deleted": deleted, "now": now},
)
async def force_delete_wallet(
wallet_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"DELETE FROM wallets WHERE id = :wallet",
{"wallet": wallet_id},
)
async def delete_wallet_by_id(
wallet_id: str, conn: Optional[Connection] = None
) -> Optional[int]:
now = int(time())
result = await (conn or db).execute(
f"""
UPDATE wallets
SET deleted = true, updated_at = {db.timestamp_placeholder('now')}
WHERE id = :wallet
""",
{"wallet": wallet_id, "now": now},
)
return result.rowcount
async def remove_deleted_wallets(conn: Optional[Connection] = None) -> None:
await (conn or db).execute("DELETE FROM wallets WHERE deleted = true")
async def delete_unused_wallets(
time_delta: int,
conn: Optional[Connection] = None,
) -> None:
delta = int(time()) - time_delta
await (conn or db).execute(
"""
DELETE FROM wallets
WHERE (
SELECT COUNT(*) FROM apipayments WHERE wallet_id = wallets.id
) = 0 AND (
(updated_at is null AND created_at < :delta)
OR updated_at < :delta
)
""",
{"delta": delta},
)
async def get_wallet(
wallet_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
) -> Optional[Wallet]:
where = "AND deleted = :deleted" if deleted is not None else ""
return await (conn or db).fetchone(
f"""
SELECT *, COALESCE((
SELECT balance FROM balances WHERE wallet_id = wallets.id
), 0) AS balance_msat FROM wallets
WHERE id = :wallet {where}
""",
{"wallet": wallet_id, "deleted": deleted},
Wallet,
)
async def get_wallets(
user_id: str, deleted: Optional[bool] = None, conn: Optional[Connection] = None
) -> list[Wallet]:
where = "AND deleted = :deleted" if deleted is not None else ""
return await (conn or db).fetchall(
f"""
SELECT *, COALESCE((
SELECT balance FROM balances WHERE wallet_id = wallets.id
), 0) AS balance_msat FROM wallets
WHERE "user" = :user {where}
""",
{"user": user_id, "deleted": deleted},
Wallet,
)
async def get_wallets_count():
result = await db.execute("SELECT COUNT(*) as count FROM wallets")
row = result.mappings().first()
return row.get("count", 0)
async def get_wallet_for_key(
key: str,
conn: Optional[Connection] = None,
) -> Optional[Wallet]:
return await (conn or db).fetchone(
"""
SELECT *, COALESCE((
SELECT balance FROM balances WHERE wallet_id = wallets.id
), 0)
AS balance_msat FROM wallets
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
""",
{"key": key},
Wallet,
)
async def get_total_balance(conn: Optional[Connection] = None):
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
row = result.mappings().first()
return row.get("balance", 0) or 0
+59
View File
@@ -0,0 +1,59 @@
from typing import Optional
from lnbits.core.db import db
from ..models import WebPushSubscription
async def get_webpush_subscription(
endpoint: str, user: str
) -> Optional[WebPushSubscription]:
return await db.fetchone(
"""
SELECT * FROM webpush_subscriptions
WHERE endpoint = :endpoint AND "user" = :user
""",
{"endpoint": endpoint, "user": user},
WebPushSubscription,
)
async def get_webpush_subscriptions_for_user(user: str) -> list[WebPushSubscription]:
return await db.fetchall(
"""SELECT * FROM webpush_subscriptions WHERE "user" = :user""",
{"user": user},
WebPushSubscription,
)
async def create_webpush_subscription(
endpoint: str, user: str, data: str, host: str
) -> WebPushSubscription:
await db.execute(
"""
INSERT INTO webpush_subscriptions (endpoint, "user", data, host)
VALUES (:endpoint, :user, :data, :host)
""",
{"endpoint": endpoint, "user": user, "data": data, "host": host},
)
subscription = await get_webpush_subscription(endpoint, user)
assert subscription, "Newly created webpush subscription couldn't be retrieved"
return subscription
async def delete_webpush_subscription(endpoint: str, user: str) -> int:
resp = await db.execute(
"""
DELETE FROM webpush_subscriptions WHERE endpoint = :endpoint AND "user" = :user
""",
{"endpoint": endpoint, "user": user},
)
return resp.rowcount
async def delete_webpush_subscriptions(endpoint: str) -> int:
resp = await db.execute(
"DELETE FROM webpush_subscriptions WHERE endpoint = :endpoint",
{"endpoint": endpoint},
)
return resp.rowcount
+5
View File
@@ -0,0 +1,5 @@
from lnbits.core.models import CoreAppExtra
from lnbits.db import Database
db = Database("database")
core_app_extra: CoreAppExtra = CoreAppExtra()
+98 -33
View File
@@ -1,40 +1,52 @@
import importlib
import re
from typing import Any
from typing import Any, Optional
from urllib.parse import urlparse
from uuid import UUID
import httpx
from loguru import logger
from lnbits.db import Connection
from lnbits.extension_manager import Extension
from lnbits.core import migrations as core_migrations
from lnbits.core.crud import (
get_db_versions,
get_installed_extensions,
update_migration_version,
)
from lnbits.core.db import db as core_db
from lnbits.core.models import DbVersion
from lnbits.core.models.extensions import InstallableExtension
from lnbits.db import COCKROACH, POSTGRES, SQLITE, Connection
from lnbits.settings import settings
from . import db as core_db
from .crud import update_migration_version
async def migrate_extension_database(
ext: InstallableExtension, current_version: Optional[DbVersion] = None
):
async def migrate_extension_database(ext: Extension, current_version):
try:
ext_migrations = importlib.import_module(f"{ext.module_name}.migrations")
ext_db = importlib.import_module(ext.module_name).db
except ImportError as e:
logger.error(e)
raise ImportError(
f"Please make sure that the extension `{ext.code}` has a migrations file."
)
except ImportError as exc:
logger.error(exc)
raise ImportError(f"Cannot import module for extension '{ext.id}'.") from exc
async with ext_db.connect() as ext_conn:
await run_migration(ext_conn, ext_migrations, current_version)
await run_migration(ext_conn, ext_migrations, ext.id, current_version)
async def run_migration(db: Connection, migrations_module: Any, current_version: int):
async def run_migration(
db: Connection,
migrations_module: Any,
db_name: str,
current_version: Optional[DbVersion] = None,
):
matcher = re.compile(r"^m(\d\d\d)_")
db_name = migrations_module.__name__.split(".")[-2]
for key, migrate in migrations_module.__dict__.items():
for key, migrate in list(migrations_module.__dict__.items()):
match = matcher.match(key)
if match:
version = int(match.group(1))
if version > current_version:
if not current_version or version > current_version.version:
logger.debug(f"running migration {db_name}.{version}")
print(f"running migration {db_name}.{version}")
await migrate(db)
@@ -46,20 +58,73 @@ async def run_migration(db: Connection, migrations_module: Any, current_version:
await update_migration_version(conn, db_name, version)
async def stop_extension_background_work(ext_id: str, user: str):
"""
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
Extensions SHOULD expose a DELETE enpoint at the root level of their API.
This function tries first to call the endpoint using `http` and if if fails it tries using `https`.
"""
async with httpx.AsyncClient() as client:
def to_valid_user_id(user_id: str) -> UUID:
if len(user_id) < 32:
raise ValueError("User ID must have at least 128 bits")
try:
int(user_id, 16)
except Exception as exc:
raise ValueError("Invalid hex string for User ID.") from exc
return UUID(hex=user_id[:32], version=4)
async def load_disabled_extension_list() -> None:
"""Update list of extensions that have been explicitly disabled"""
inactive_extensions = await get_installed_extensions(active=False)
settings.lnbits_deactivated_extensions.update([e.id for e in inactive_extensions])
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
async with core_db.connect() as conn:
exists = False
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='dbversions'"
)
elif conn.type in {POSTGRES, COCKROACH}:
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_schema = 'public'"
" AND table_name = 'dbversions'"
)
if not exists:
await core_migrations.m000_create_migrations_table(conn)
current_versions = await get_db_versions(conn)
core_version = next(
(v for v in current_versions if v.db == "core"),
DbVersion(db="core", version=0),
)
await run_migration(conn, core_migrations, "core", core_version)
# here is the first place we can be sure that the
# `installed_extensions` table has been created
await load_disabled_extension_list()
for ext in await get_installed_extensions():
current_version = next(
(v for v in current_versions if v.db == ext.id),
DbVersion(db=ext.id, version=0),
)
if current_version is None:
logger.warning(
f"Extension {ext.id} has no migration version. This should not happen."
)
continue
try:
url = f"http://{settings.host}:{settings.port}/{ext_id}/api/v1?usr={user}"
await client.delete(url)
except Exception as ex:
logger.warning(ex)
try:
# try https
url = f"https://{settings.host}:{settings.port}/{ext_id}/api/v1?usr={user}"
except Exception as ex:
logger.warning(ex)
await migrate_extension_database(ext, current_version)
except Exception as e:
logger.exception(f"Error migrating extension {ext.id}: {e}")
logger.info("✔️ All migrations done.")
def is_valid_url(url):
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
except ValueError:
return False
+458 -48
View File
@@ -1,12 +1,15 @@
import datetime
import json
from time import time
from typing import Any
from loguru import logger
from sqlalchemy.exc import OperationalError
from lnbits import bolt11
from lnbits.db import Connection
async def m000_create_migrations_table(db):
async def m000_create_migrations_table(db: Connection):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS dbversions (
@@ -17,7 +20,7 @@ async def m000_create_migrations_table(db):
)
async def m001_initial(db):
async def m001_initial(db: Connection):
"""
Initial LNbits tables.
"""
@@ -86,7 +89,7 @@ async def m001_initial(db):
)
async def m002_add_fields_to_apipayments(db):
async def m002_add_fields_to_apipayments(db: Connection):
"""
Adding fields to apipayments for better accounting,
and renaming payhash to checking_id since that is what it really is.
@@ -99,9 +102,8 @@ async def m002_add_fields_to_apipayments(db):
await db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT")
await db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT")
import json
rows = await (await db.execute("SELECT * FROM apipayments")).fetchall()
result = await db.execute("SELECT * FROM apipayments")
rows = result.mappings().all()
for row in rows:
if not row["memo"] or not row["memo"].startswith("#"):
continue
@@ -112,15 +114,15 @@ async def m002_add_fields_to_apipayments(db):
new = row["memo"][len(prefix) :]
await db.execute(
"""
UPDATE apipayments SET extra = ?, memo = ?
WHERE checking_id = ? AND memo = ?
UPDATE apipayments SET extra = :extra, memo = :memo1
WHERE checking_id = :checking_id AND memo = :memo2
""",
(
json.dumps({"tag": ext}),
new,
row["checking_id"],
row["memo"],
),
{
"extra": json.dumps({"tag": ext}),
"memo1": new,
"checking_id": row["checking_id"],
"memo2": row["memo"],
},
)
break
except OperationalError:
@@ -131,7 +133,7 @@ async def m002_add_fields_to_apipayments(db):
pass
async def m003_add_invoice_webhook(db):
async def m003_add_invoice_webhook(db: Connection):
"""
Special column for webhook endpoints that can be assigned
to each different invoice.
@@ -141,7 +143,7 @@ async def m003_add_invoice_webhook(db):
await db.execute("ALTER TABLE apipayments ADD COLUMN webhook_status TEXT")
async def m004_ensure_fees_are_always_negative(db):
async def m004_ensure_fees_are_always_negative(db: Connection):
"""
Use abs() so wallet backends don't have to care about the sign of the fees.
"""
@@ -166,7 +168,7 @@ async def m004_ensure_fees_are_always_negative(db):
)
async def m005_balance_check_balance_notify(db):
async def m005_balance_check_balance_notify(db: Connection):
"""
Keep track of balanceCheck-enabled lnurl-withdrawals to be consumed by an
LNbits wallet and of balanceNotify URLs supplied by users to empty their wallets.
@@ -196,7 +198,7 @@ async def m005_balance_check_balance_notify(db):
)
async def m006_add_invoice_expiry_to_apipayments(db):
async def m006_add_invoice_expiry_to_apipayments(db: Connection):
"""
Adds invoice expiry column to apipayments.
"""
@@ -206,24 +208,23 @@ async def m006_add_invoice_expiry_to_apipayments(db):
pass
async def m007_set_invoice_expiries(db):
async def m007_set_invoice_expiries(db: Connection):
"""
Precomputes invoice expiry for existing pending incoming payments.
"""
try:
rows = await (
await db.execute(
f"""
SELECT bolt11, checking_id
FROM apipayments
WHERE pending = true
AND amount > 0
AND bolt11 IS NOT NULL
AND expiry IS NULL
AND time < {db.timestamp_now}
"""
)
).fetchall()
result = await db.execute(
f"""
SELECT bolt11, checking_id
FROM apipayments
WHERE pending = true
AND amount > 0
AND bolt11 IS NOT NULL
AND expiry IS NULL
AND time < {db.timestamp_now}
"""
)
rows = result.mappings().all()
if len(rows):
logger.info(f"Migration: Checking expiry of {len(rows)} invoices")
for i, (
@@ -235,23 +236,19 @@ async def m007_set_invoice_expiries(db):
if invoice.expiry is None:
continue
expiration_date = datetime.datetime.fromtimestamp(
invoice.date + invoice.expiry
)
expiration_date = invoice.date + invoice.expiry
logger.info(
f"Migration: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}"
f"Migration: {i+1}/{len(rows)} setting expiry of invoice"
f" {invoice.payment_hash} to {expiration_date}"
)
await db.execute(
"""
UPDATE apipayments SET expiry = ?
WHERE checking_id = ? AND amount > 0
f"""
UPDATE apipayments SET expiry = {db.timestamp_placeholder('expiry')}
WHERE checking_id = :checking_id AND amount > 0
""",
(
db.datetime_to_timestamp(expiration_date),
checking_id,
),
{"expiry": expiration_date, "checking_id": checking_id},
)
except:
except Exception:
continue
except OperationalError:
# this is necessary now because it may be the case that this migration will
@@ -261,7 +258,7 @@ async def m007_set_invoice_expiries(db):
pass
async def m008_create_admin_settings_table(db):
async def m008_create_admin_settings_table(db: Connection):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS settings (
@@ -272,7 +269,7 @@ async def m008_create_admin_settings_table(db):
)
async def m009_create_tinyurl_table(db):
async def m009_create_tinyurl_table(db: Connection):
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS tiny_url (
@@ -286,7 +283,7 @@ async def m009_create_tinyurl_table(db):
)
async def m010_create_installed_extensions_table(db):
async def m010_create_installed_extensions_table(db: Connection):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS installed_extensions (
@@ -301,3 +298,416 @@ async def m010_create_installed_extensions_table(db):
);
"""
)
async def m011_optimize_balances_view(db: Connection):
"""
Make the calculation of the balance a single aggregation
over the payments table instead of 2.
"""
await db.execute("DROP VIEW balances")
await db.execute(
"""
CREATE VIEW balances AS
SELECT wallet, SUM(amount - abs(fee)) AS balance
FROM apipayments
WHERE (pending = false AND amount > 0) OR amount < 0
GROUP BY wallet
"""
)
async def m012_add_currency_to_wallet(db: Connection):
await db.execute(
"""
ALTER TABLE wallets ADD COLUMN currency TEXT
"""
)
async def m013_add_deleted_to_wallets(db: Connection):
"""
Adds deleted column to wallets.
"""
try:
await db.execute(
"ALTER TABLE wallets ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT false"
)
except OperationalError:
pass
async def m014_set_deleted_wallets(db: Connection):
"""
Sets deleted column to wallets.
"""
try:
result = await db.execute(
"""
SELECT *
FROM wallets
WHERE user LIKE 'del:%'
AND adminkey LIKE 'del:%'
AND inkey LIKE 'del:%'
"""
)
rows = result.mappings().all()
for row in rows:
try:
user = row["user"].split(":")[1]
adminkey = row["adminkey"].split(":")[1]
inkey = row["inkey"].split(":")[1]
await db.execute(
"""
UPDATE wallets SET
"user" = :user, adminkey = :adminkey, inkey = :inkey, deleted = true
WHERE id = :wallet
""",
{
"user": user,
"adminkey": adminkey,
"inkey": inkey,
"wallet": row.get("id"),
},
)
except Exception:
continue
except OperationalError:
# this is necessary now because it may be the case that this migration will
# run twice in some environments.
# catching errors like this won't be necessary in anymore now that we
# keep track of db versions so no migration ever runs twice.
pass
async def m015_create_push_notification_subscriptions_table(db: Connection):
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS webpush_subscriptions (
endpoint TEXT NOT NULL,
"user" TEXT NOT NULL,
data TEXT NOT NULL,
host TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
PRIMARY KEY (endpoint, "user")
);
"""
)
async def m016_add_username_column_to_accounts(db: Connection):
"""
Adds username column to accounts.
"""
try:
await db.execute("ALTER TABLE accounts ADD COLUMN username TEXT")
await db.execute("ALTER TABLE accounts ADD COLUMN extra TEXT")
except OperationalError:
pass
async def m017_add_timestamp_columns_to_accounts_and_wallets(db: Connection):
"""
Adds created_at and updated_at column to accounts and wallets.
"""
try:
await db.execute(
"ALTER TABLE accounts "
f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
)
await db.execute(
"ALTER TABLE accounts "
f"ADD COLUMN updated_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
)
await db.execute(
"ALTER TABLE wallets "
f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
)
await db.execute(
"ALTER TABLE wallets "
f"ADD COLUMN updated_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
)
# # set their wallets created_at with the first payment
# await db.execute(
# """
# UPDATE wallets SET created_at = (
# SELECT time FROM apipayments
# WHERE apipayments.wallet = wallets.id
# ORDER BY time ASC LIMIT 1
# )
# """
# )
# # then set their accounts created_at with the wallet
# await db.execute(
# """
# UPDATE accounts SET created_at = (
# SELECT created_at FROM wallets
# WHERE wallets.user = accounts.id
# ORDER BY created_at ASC LIMIT 1
# )
# """
# )
# set all to now where they are null
now = int(time())
await db.execute(
f"""
UPDATE wallets SET created_at = {db.timestamp_placeholder('now')}
WHERE created_at IS NULL
""",
{"now": now},
)
await db.execute(
f"""
UPDATE accounts SET created_at = {db.timestamp_placeholder('now')}
WHERE created_at IS NULL
""",
{"now": now},
)
except OperationalError as exc:
logger.error(f"Migration 17 failed: {exc}")
pass
async def m018_balances_view_exclude_deleted(db: Connection):
"""
Make deleted wallets not show up in the balances view.
"""
await db.execute("DROP VIEW balances")
await db.execute(
"""
CREATE VIEW balances AS
SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
FROM apipayments
LEFT JOIN wallets ON apipayments.wallet = wallets.id
WHERE (wallets.deleted = false OR wallets.deleted is NULL)
AND ((apipayments.pending = false AND apipayments.amount > 0)
OR apipayments.amount < 0)
GROUP BY wallet
"""
)
async def m019_balances_view_based_on_wallets(db: Connection):
"""
Make deleted wallets not show up in the balances view.
Important for querying whole lnbits balances.
"""
await db.execute("DROP VIEW balances")
await db.execute(
"""
CREATE VIEW balances AS
SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet = wallets.id
WHERE (wallets.deleted = false OR wallets.deleted is NULL)
AND ((apipayments.pending = false AND apipayments.amount > 0)
OR apipayments.amount < 0)
GROUP BY apipayments.wallet
"""
)
async def m020_add_column_column_to_user_extensions(db: Connection):
"""
Adds extra column to user extensions.
"""
await db.execute("ALTER TABLE extensions ADD COLUMN extra TEXT")
async def m021_add_success_failed_to_apipayments(db: Connection):
"""
Adds success and failed columns to apipayments.
"""
await db.execute("ALTER TABLE apipayments ADD COLUMN status TEXT DEFAULT 'pending'")
# set all not pending to success true, failed payments were deleted until now
await db.execute("UPDATE apipayments SET status = 'success' WHERE NOT pending")
await db.execute("DROP VIEW balances")
await db.execute(
"""
CREATE VIEW balances AS
SELECT apipayments.wallet,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet = wallets.id
WHERE (wallets.deleted = false OR wallets.deleted is NULL)
AND (
(apipayments.status = 'success' AND apipayments.amount > 0)
OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0)
)
GROUP BY apipayments.wallet
"""
)
async def m022_add_pubkey_to_accounts(db: Connection):
"""
Adds pubkey column to accounts.
"""
try:
await db.execute("ALTER TABLE accounts ADD COLUMN pubkey TEXT")
except OperationalError:
pass
async def m023_add_column_column_to_apipayments(db: Connection):
"""
renames hash to payment_hash and drops unused index
"""
await db.execute("DROP INDEX by_hash")
await db.execute("ALTER TABLE apipayments RENAME COLUMN hash TO payment_hash")
await db.execute("ALTER TABLE apipayments RENAME COLUMN wallet TO wallet_id")
await db.execute("ALTER TABLE accounts RENAME COLUMN pass TO password_hash")
await db.execute("CREATE INDEX by_hash ON apipayments (payment_hash)")
async def m024_drop_pending(db: Connection):
await db.execute("ALTER TABLE apipayments DROP COLUMN pending")
async def m025_refresh_view(db: Connection):
await db.execute("DROP VIEW balances")
await db.execute(
"""
CREATE VIEW balances AS
SELECT apipayments.wallet_id,
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet_id = wallets.id
WHERE (wallets.deleted = false OR wallets.deleted is NULL)
AND (
(apipayments.status = 'success' AND apipayments.amount > 0)
OR (apipayments.status IN ('success', 'pending') AND apipayments.amount < 0)
)
GROUP BY apipayments.wallet_id
"""
)
async def m026_update_payment_table(db: Connection):
await db.execute("ALTER TABLE apipayments ADD COLUMN tag TEXT")
await db.execute("ALTER TABLE apipayments ADD COLUMN extension TEXT")
await db.execute("ALTER TABLE apipayments ADD COLUMN created_at TIMESTAMP")
await db.execute("ALTER TABLE apipayments ADD COLUMN updated_at TIMESTAMP")
async def m027_update_apipayments_data(db: Connection):
result = None
try:
result = await db.execute("SELECT * FROM apipayments LIMIT 100")
except Exception as exc:
logger.warning("Could not select, trying again after cache cleared.")
logger.debug(exc)
await db.execute("COMMIT")
offset = 0
limit = 1000
payments: list[dict[Any, Any]] = []
logger.info("Updating payments")
while len(payments) > 0 or offset == 0:
logger.info(f"Updating {offset} to {offset+limit}")
result = await db.execute(
f"SELECT * FROM apipayments ORDER BY time LIMIT {limit} OFFSET {offset}"
)
payments = result.mappings().all()
logger.info(f"Payments count: {len(payments)}")
for payment in payments:
tag = None
created_at = payment.get("time")
if payment.get("extra"):
extra = json.loads(str(payment.get("extra")))
tag = extra.get("tag")
tsph = db.timestamp_placeholder("created_at")
await db.execute(
f"""
UPDATE apipayments
SET tag = :tag, created_at = {tsph}, updated_at = {tsph}
WHERE checking_id = :checking_id
""",
{
"tag": tag,
"created_at": created_at,
"checking_id": payment.get("checking_id"),
},
)
offset += limit
logger.info("Payments updated")
async def m028_update_settings(db: Connection):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS system_settings (
id TEXT PRIMARY KEY,
value TEXT,
tag TEXT NOT NULL DEFAULT 'core',
UNIQUE (id, tag)
);
"""
)
async def _insert_key_value(id_: str, value: Any):
await db.execute(
"""
INSERT INTO system_settings (id, value, tag)
VALUES (:id, :value, :tag)
""",
{"id": id_, "value": json.dumps(value), "tag": "core"},
)
row: dict = await db.fetchone("SELECT * FROM settings")
if row:
await _insert_key_value("super_user", row["super_user"])
editable_settings = json.loads(row["editable_settings"])
for key, value in editable_settings.items():
await _insert_key_value(key, value)
await db.execute("drop table settings")
async def m029_create_audit_table(db: Connection):
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS audit (
component TEXT,
ip_address TEXT,
user_id TEXT,
path TEXT,
request_type TEXT,
request_method TEXT,
request_details TEXT,
response_code TEXT,
duration REAL NOT NULL,
delete_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
);
"""
)
async def m030_add_user_api_tokens_column(db: Connection):
await db.execute(
"""
ALTER TABLE accounts ADD COLUMN access_control_list TEXT
"""
)
async def m031_add_color_and_icon_to_wallets(db: Connection):
"""
Adds icon and color columns to wallets.
"""
await db.execute("ALTER TABLE wallets ADD COLUMN extra TEXT")
-240
View File
@@ -1,240 +0,0 @@
import datetime
import hashlib
import hmac
import json
import time
from sqlite3 import Row
from typing import Callable, Dict, List, Optional
from ecdsa import SECP256k1, SigningKey
from lnurl import encode as lnurl_encode
from loguru import logger
from pydantic import BaseModel
from lnbits.db import Connection
from lnbits.helpers import url_for
from lnbits.settings import get_wallet_class, settings
from lnbits.wallets.base import PaymentStatus
class Wallet(BaseModel):
id: str
name: str
user: str
adminkey: str
inkey: str
balance_msat: int
@property
def balance(self) -> int:
return self.balance_msat // 1000
@property
def withdrawable_balance(self) -> int:
from .services import fee_reserve
return self.balance_msat - fee_reserve(self.balance_msat)
@property
def lnurlwithdraw_full(self) -> str:
url = url_for("/withdraw", external=True, usr=self.user, wal=self.id)
try:
return lnurl_encode(url)
except:
return ""
def lnurlauth_key(self, domain: str) -> SigningKey:
hashing_key = hashlib.sha256(self.id.encode()).digest()
linking_key = hmac.digest(hashing_key, domain.encode(), "sha256")
return SigningKey.from_string(
linking_key, curve=SECP256k1, hashfunc=hashlib.sha256
)
async def get_payment(self, payment_hash: str) -> Optional["Payment"]:
from .crud import get_standalone_payment
return await get_standalone_payment(payment_hash)
class User(BaseModel):
id: str
email: Optional[str] = None
extensions: List[str] = []
wallets: List[Wallet] = []
password: Optional[str] = None
admin: bool = False
super_user: bool = False
@property
def wallet_ids(self) -> List[str]:
return [wallet.id for wallet in self.wallets]
def get_wallet(self, wallet_id: str) -> Optional["Wallet"]:
w = [wallet for wallet in self.wallets if wallet.id == wallet_id]
return w[0] if w else None
@classmethod
def is_extension_for_user(cls, ext: str, user: str) -> bool:
if ext not in settings.lnbits_admin_extensions:
return True
if user == settings.super_user:
return True
if user in settings.lnbits_admin_users:
return True
return False
class Payment(BaseModel):
checking_id: str
pending: bool
amount: int
fee: int
memo: Optional[str]
time: int
bolt11: str
preimage: str
payment_hash: str
expiry: Optional[float]
extra: Dict = {}
wallet_id: str
webhook: Optional[str]
webhook_status: Optional[int]
@classmethod
def from_row(cls, row: Row):
return cls(
checking_id=row["checking_id"],
payment_hash=row["hash"] or "0" * 64,
bolt11=row["bolt11"] or "",
preimage=row["preimage"] or "0" * 64,
extra=json.loads(row["extra"] or "{}"),
pending=row["pending"],
amount=row["amount"],
fee=row["fee"],
memo=row["memo"],
time=row["time"],
expiry=row["expiry"],
wallet_id=row["wallet"],
webhook=row["webhook"],
webhook_status=row["webhook_status"],
)
@property
def tag(self) -> Optional[str]:
if self.extra is None:
return ""
return self.extra.get("tag")
@property
def msat(self) -> int:
return self.amount
@property
def sat(self) -> int:
return self.amount // 1000
@property
def is_in(self) -> bool:
return self.amount > 0
@property
def is_out(self) -> bool:
return self.amount < 0
@property
def is_expired(self) -> bool:
return self.expiry < time.time() if self.expiry else False
@property
def is_uncheckable(self) -> bool:
return self.checking_id.startswith("internal_")
async def update_status(
self,
status: PaymentStatus,
conn: Optional[Connection] = None,
) -> None:
from .crud import update_payment_details
await update_payment_details(
checking_id=self.checking_id,
pending=status.pending,
fee=status.fee_msat,
preimage=status.preimage,
conn=conn,
)
async def set_pending(self, pending: bool) -> None:
from .crud import update_payment_status
await update_payment_status(self.checking_id, pending)
async def check_status(
self,
conn: Optional[Connection] = None,
) -> PaymentStatus:
if self.is_uncheckable:
return PaymentStatus(None)
logger.debug(
f"Checking {'outgoing' if self.is_out else 'incoming'} pending payment {self.checking_id}"
)
WALLET = get_wallet_class()
if self.is_out:
status = await WALLET.get_payment_status(self.checking_id)
else:
status = await WALLET.get_invoice_status(self.checking_id)
logger.debug(f"Status: {status}")
if self.is_in and status.pending and self.is_expired and self.expiry:
expiration_date = datetime.datetime.fromtimestamp(self.expiry)
logger.debug(
f"Deleting expired incoming pending payment {self.checking_id}: expired {expiration_date}"
)
await self.delete(conn)
elif self.is_out and status.failed:
logger.warning(
f"Deleting outgoing failed payment {self.checking_id}: {status}"
)
await self.delete(conn)
elif not status.pending:
logger.info(
f"Marking '{'in' if self.is_in else 'out'}' {self.checking_id} as not pending anymore: {status}"
)
await self.update_status(status, conn=conn)
return status
async def delete(self, conn: Optional[Connection] = None) -> None:
from .crud import delete_payment
await delete_payment(self.checking_id, conn=conn)
class BalanceCheck(BaseModel):
wallet: str
service: str
url: str
@classmethod
def from_row(cls, row: Row):
return cls(wallet=row["wallet"], service=row["service"], url=row["url"])
class CoreAppExtra:
register_new_ext_routes: Callable
class TinyURL(BaseModel):
id: str
url: str
endless: bool
wallet: str
time: float
@classmethod
def from_row(cls, row: Row):
return cls(**dict(row))
+109
View File
@@ -0,0 +1,109 @@
from .audit import AuditEntry, AuditFilters
from .lnurl import CreateLnurl, CreateLnurlAuth, PayLnurlWData
from .misc import (
BalanceDelta,
Callback,
ConversionData,
CoreAppExtra,
DbVersion,
SimpleStatus,
)
from .payments import (
CreateInvoice,
CreatePayment,
DecodePayment,
PayInvoice,
Payment,
PaymentCountField,
PaymentCountStat,
PaymentDailyStats,
PaymentExtra,
PaymentFilters,
PaymentHistoryPoint,
PaymentsStatusCount,
PaymentState,
PaymentWalletStats,
)
from .tinyurl import TinyURL
from .users import (
AccessTokenPayload,
Account,
AccountFilters,
AccountOverview,
CreateUser,
LoginUsernamePassword,
LoginUsr,
RegisterUser,
ResetUserPassword,
UpdateBalance,
UpdateSuperuserPassword,
UpdateUser,
UpdateUserPassword,
UpdateUserPubkey,
User,
UserAcls,
UserExtra,
)
from .wallets import BaseWallet, CreateWallet, KeyType, Wallet, WalletTypeInfo
from .webpush import CreateWebPushSubscription, WebPushSubscription
__all__ = [
# audit
"AuditEntry",
"AuditFilters",
# lnurl
"CreateLnurl",
"CreateLnurlAuth",
"PayLnurlWData",
# misc
"BalanceDelta",
"Callback",
"ConversionData",
"CoreAppExtra",
"DbVersion",
"SimpleStatus",
# payments
"CreateInvoice",
"CreatePayment",
"DecodePayment",
"PayInvoice",
"Payment",
"PaymentCountField",
"PaymentCountStat",
"PaymentDailyStats",
"PaymentsStatusCount",
"PaymentWalletStats",
"PaymentExtra",
"PaymentFilters",
"PaymentHistoryPoint",
"PaymentState",
# tinyurl
"TinyURL",
# users
"AccessTokenPayload",
"Account",
"AccountFilters",
"AccountOverview",
"UserAcls",
"CreateUser",
"RegisterUser",
"LoginUsernamePassword",
"LoginUsr",
"ResetUserPassword",
"UpdateBalance",
"UpdateSuperuserPassword",
"UpdateUser",
"UpdateUserPassword",
"UpdateUserPubkey",
"User",
"UserExtra",
# wallets
"BaseWallet",
"CreateWallet",
"KeyType",
"Wallet",
"WalletTypeInfo",
# webpush
"CreateWebPushSubscription",
"WebPushSubscription",
]
+61
View File
@@ -0,0 +1,61 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from pydantic import BaseModel, Field
from lnbits.db import FilterModel
from lnbits.settings import settings
class AuditEntry(BaseModel):
component: str | None = None
ip_address: str | None = None
user_id: str | None = None
path: str | None = None
request_type: str | None = None
request_method: str | None = None
request_details: str | None = None
response_code: str | None = None
duration: float
delete_at: datetime | None = None
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
def __init__(self, **data):
super().__init__(**data)
retention_days = max(0, settings.lnbits_audit_retention_days) or 365
self.delete_at = self.created_at + timedelta(days=retention_days)
class AuditFilters(FilterModel):
__search_fields__ = [
"ip_address",
"user_id",
"path",
"request_method",
"response_code",
"component",
]
__sort_fields__ = [
"created_at",
"duration",
]
ip_address: str | None = None
user_id: str | None = None
path: str | None = None
request_method: str | None = None
response_code: str | None = None
component: str | None = None
class AuditCountStat(BaseModel):
field: str = ""
total: float = 0
class AuditStats(BaseModel):
request_method: list[AuditCountStat] = []
response_code: list[AuditCountStat] = []
component: list[AuditCountStat] = []
long_duration: list[AuditCountStat] = []
+736
View File
@@ -0,0 +1,736 @@
from __future__ import annotations
import asyncio
import hashlib
import json
import os
import shutil
import zipfile
from pathlib import Path
from typing import Any
import httpx
from loguru import logger
from pydantic import BaseModel
from lnbits.helpers import (
download_url,
file_hash,
is_lnbits_version_ok,
version_parse,
)
from lnbits.settings import settings
class ExplicitRelease(BaseModel):
id: str
name: str
version: str
archive: str
hash: str
dependencies: list[str] = []
repo: str | None
icon: str | None
short_description: str | None
min_lnbits_version: str | None
max_lnbits_version: str | None
html_url: str | None # todo: release_url
warning: str | None
info_notification: str | None
critical_notification: str | None
details_link: str | None
pay_link: str | None
def is_version_compatible(self):
return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version)
class GitHubRelease(BaseModel):
id: str
organisation: str
repository: str
class Manifest(BaseModel):
featured: list[str] = []
extensions: list[ExplicitRelease] = []
repos: list[GitHubRelease] = []
class GitHubRepoRelease(BaseModel):
name: str
tag_name: str
zipball_url: str
html_url: str
def details_link(self, source_repo: str) -> str:
return f"https://raw.githubusercontent.com/{source_repo}/{self.tag_name}/config.json"
class GitHubRepo(BaseModel):
stargazers_count: str
html_url: str
default_branch: str
class ExtensionConfig(BaseModel):
name: str
short_description: str
tile: str = ""
warning: str | None = ""
min_lnbits_version: str | None
max_lnbits_version: str | None
def is_version_compatible(self) -> bool:
return is_lnbits_version_ok(self.min_lnbits_version, self.max_lnbits_version)
@classmethod
async def fetch_github_release_config(
cls, org: str, repo: str, tag_name: str
) -> ExtensionConfig | None:
config_url = (
f"https://raw.githubusercontent.com/{org}/{repo}/{tag_name}/config.json"
)
error_msg = "Cannot fetch GitHub extension config"
config = await github_api_get(config_url, error_msg)
return ExtensionConfig.parse_obj(config)
class ReleasePaymentInfo(BaseModel):
amount: int | None = None
pay_link: str | None = None
payment_hash: str | None = None
payment_request: str | None = None
class PayToEnableInfo(BaseModel):
amount: int = 0
required: bool = False
wallet: str | None = None
class UserExtensionInfo(BaseModel):
paid_to_enable: bool | None = False
payment_hash_to_enable: str | None = None
class UserExtension(BaseModel):
user: str
extension: str
active: bool
extra: UserExtensionInfo | None = None
@property
def is_paid(self) -> bool:
if not self.extra:
return False
return self.extra.paid_to_enable is True
@classmethod
def from_row(cls, data: dict) -> UserExtension:
ext = UserExtension(**data)
ext.extra = (
UserExtensionInfo(**json.loads(data["_extra"] or "{}"))
if "_extra" in data
else None
)
return ext
class Extension(BaseModel):
code: str
is_valid: bool
name: str | None = None
short_description: str | None = None
tile: str | None = None
upgrade_hash: str | None = ""
@property
def module_name(self) -> str:
if self.is_upgrade_extension:
return f"{self.code}-{self.upgrade_hash}"
if settings.has_default_extension_path:
return f"lnbits.extensions.{self.code}"
return self.code
@property
def is_upgrade_extension(self) -> bool:
return self.upgrade_hash != ""
@classmethod
def from_installable_ext(cls, ext_info: InstallableExtension) -> Extension:
return Extension(
code=ext_info.id,
is_valid=True,
name=ext_info.name,
short_description=ext_info.short_description,
tile=ext_info.icon,
upgrade_hash=ext_info.hash if ext_info.ext_upgrade_dir.is_dir() else "",
)
class ExtensionRelease(BaseModel):
name: str
version: str
archive: str
source_repo: str
is_github_release: bool = False
hash: str | None = None
min_lnbits_version: str | None = None
max_lnbits_version: str | None = None
is_version_compatible: bool | None = True
html_url: str | None = None
description: str | None = None
warning: str | None = None
repo: str | None = None
icon: str | None = None
details_link: str | None = None
pay_link: str | None = None
cost_sats: int | None = None
paid_sats: int | None = 0
payment_hash: str | None = None
@property
def archive_url(self) -> str:
if not self.pay_link:
return self.archive
return (
f"{self.archive}?version=v{self.version}&payment_hash={self.payment_hash}"
)
async def check_payment_requirements(self):
if not self.pay_link:
return
payment_info = await self.fetch_release_payment_info()
self.cost_sats = payment_info.amount if payment_info else None
async def fetch_release_payment_info(
self, amount: int | None = None
) -> ReleasePaymentInfo | None:
url = f"{self.pay_link}?amount={amount}" if amount else self.pay_link
assert url, "Missing URL for payment info."
try:
async with httpx.AsyncClient() as client:
resp = await client.get(url)
resp.raise_for_status()
return ReleasePaymentInfo(**resp.json())
except Exception as e:
logger.warning(e)
return None
@classmethod
def from_github_release(
cls, source_repo: str, r: GitHubRepoRelease
) -> ExtensionRelease:
return ExtensionRelease(
name=r.name,
description=r.name,
version=r.tag_name,
archive=r.zipball_url,
source_repo=source_repo,
is_github_release=True,
details_link=r.details_link(source_repo),
repo=f"https://github.com/{source_repo}",
html_url=r.html_url,
)
@classmethod
def from_explicit_release(
cls, source_repo: str, e: ExplicitRelease
) -> ExtensionRelease:
return ExtensionRelease(
name=e.name,
version=e.version,
archive=e.archive,
hash=e.hash,
source_repo=source_repo,
description=e.short_description,
min_lnbits_version=e.min_lnbits_version,
max_lnbits_version=e.max_lnbits_version,
is_version_compatible=e.is_version_compatible(),
warning=e.warning,
html_url=e.html_url,
details_link=e.details_link,
pay_link=e.pay_link,
repo=e.repo,
icon=e.icon,
)
@classmethod
async def get_github_releases(cls, org: str, repo: str) -> list[ExtensionRelease]:
try:
github_releases = await cls.fetch_github_releases(org, repo)
return [
ExtensionRelease.from_github_release(f"{org}/{repo}", r)
for r in github_releases
]
except Exception as e:
logger.warning(e)
return []
@classmethod
async def fetch_github_releases(
cls, org: str, repo: str
) -> list[GitHubRepoRelease]:
releases_url = f"https://api.github.com/repos/{org}/{repo}/releases"
error_msg = "Cannot fetch extension releases"
releases = await github_api_get(releases_url, error_msg)
return [GitHubRepoRelease.parse_obj(r) for r in releases]
@classmethod
async def fetch_release_details(cls, details_link: str) -> dict | None:
try:
async with httpx.AsyncClient() as client:
resp = await client.get(details_link)
resp.raise_for_status()
data = resp.json()
if "description_md" in data:
resp = await client.get(data["description_md"])
if not resp.is_error:
data["description_md"] = resp.text
return data
except Exception as e:
logger.warning(e)
return None
class ExtensionMeta(BaseModel):
installed_release: ExtensionRelease | None = None
latest_release: ExtensionRelease | None = None
pay_to_enable: PayToEnableInfo | None = None
payments: list[ReleasePaymentInfo] = []
dependencies: list[str] = []
archive: str | None = None
featured: bool = False
class InstallableExtension(BaseModel):
id: str
name: str
version: str
active: bool | None = False
short_description: str | None = None
icon: str | None = None
stars: int = 0
meta: ExtensionMeta | None = None
@property
def hash(self) -> str:
if self.meta and self.meta.installed_release:
if self.meta.installed_release.hash:
return self.meta.installed_release.hash
m = hashlib.sha256()
m.update(f"{self.meta.installed_release.archive}".encode())
return m.hexdigest()
return "not-installed"
@property
def zip_path(self) -> Path:
extensions_data_dir = Path(settings.lnbits_data_folder, "zips")
Path(extensions_data_dir).mkdir(parents=True, exist_ok=True)
return Path(extensions_data_dir, f"{self.id}.zip")
@property
def ext_dir(self) -> Path:
return Path(settings.lnbits_extensions_path, "extensions", self.id)
@property
def ext_upgrade_dir(self) -> Path:
return Path(settings.lnbits_extensions_upgrade_path, f"{self.id}-{self.hash}")
@property
def module_name(self) -> str:
if self.ext_upgrade_dir.is_dir():
return f"{self.id}-{self.hash}"
if settings.has_default_extension_path:
return f"lnbits.extensions.{self.id}"
return self.id
@property
def has_installed_version(self) -> bool:
if not self.ext_dir.is_dir():
return False
return Path(self.ext_dir, "config.json").is_file()
@property
def installed_version(self) -> str:
if self.meta and self.meta.installed_release:
return self.meta.installed_release.version
return ""
@property
def requires_payment(self) -> bool:
if not self.meta or not self.meta.pay_to_enable:
return False
return self.meta.pay_to_enable.required is True
async def download_archive(self):
logger.info(f"Downloading extension {self.name} ({self.installed_version}).")
ext_zip_file = self.zip_path
if ext_zip_file.is_file():
os.remove(ext_zip_file)
try:
assert (
self.meta and self.meta.installed_release
), "installed_release is none."
self._restore_payment_info()
await asyncio.to_thread(
download_url, self.meta.installed_release.archive_url, ext_zip_file
)
self._remember_payment_info()
except Exception as exc:
logger.warning(exc)
raise AssertionError("Cannot fetch extension archive file") from exc
archive_hash = file_hash(ext_zip_file)
if (
self.meta
and self.meta.installed_release.hash
and self.meta.installed_release.hash != archive_hash
):
# remove downloaded archive
if ext_zip_file.is_file():
os.remove(ext_zip_file)
raise AssertionError("File hash missmatch. Will not install.")
def extract_archive(self):
logger.info(f"Extracting extension {self.name} ({self.installed_version}).")
Path(settings.lnbits_extensions_upgrade_path).mkdir(parents=True, exist_ok=True)
tmp_dir = Path(settings.lnbits_data_folder, "unzip-temp", self.hash)
shutil.rmtree(tmp_dir, True)
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
zip_ref.extractall(tmp_dir)
generated_dir_name = os.listdir(tmp_dir)[0]
shutil.rmtree(self.ext_upgrade_dir, True)
shutil.copytree(
Path(tmp_dir, generated_dir_name),
Path(self.ext_upgrade_dir),
)
shutil.rmtree(tmp_dir, True)
# Pre-packed extensions can be upgraded
# Mark the extension as installed so we know it is not the pre-packed version
with open(Path(self.ext_upgrade_dir, "config.json"), "r+") as json_file:
config_json = json.load(json_file)
self.name = config_json.get("name")
self.short_description = config_json.get("short_description")
if (
self.meta
and self.meta.installed_release
and self.meta.installed_release.is_github_release
and config_json.get("tile")
):
self.icon = icon_to_github_url(
self.meta.installed_release.source_repo, config_json.get("tile")
)
shutil.rmtree(self.ext_dir, True)
shutil.copytree(Path(self.ext_upgrade_dir), Path(self.ext_dir))
logger.info(f"Extension {self.name} ({self.installed_version}) extracted.")
def clean_extension_files(self):
# remove downloaded archive
if self.zip_path.is_file():
os.remove(self.zip_path)
# remove module from extensions
shutil.rmtree(self.ext_dir, True)
shutil.rmtree(self.ext_upgrade_dir, True)
def check_latest_version(self, release: ExtensionRelease | None):
if not release:
return
if not self.meta or not self.meta.latest_release:
meta = self.meta or ExtensionMeta()
meta.latest_release = release
self.meta = meta
return
if version_parse(self.meta.latest_release.version) < version_parse(
release.version
):
self.meta.latest_release = release
def find_existing_payment(self, pay_link: str | None) -> ReleasePaymentInfo | None:
if not pay_link or not self.meta or not self.meta.payments:
return None
return next(
(p for p in self.meta.payments if p.pay_link == pay_link),
None,
)
def _restore_payment_info(self):
if (
not self.meta
or not self.meta.installed_release
or not self.meta.installed_release.pay_link
or not self.meta.installed_release.payment_hash
):
return
payment_info = self.find_existing_payment(self.meta.installed_release.pay_link)
if payment_info:
self.meta.installed_release.payment_hash = payment_info.payment_hash
def _remember_payment_info(self):
if (
not self.meta
or not self.meta.installed_release
or not self.meta.installed_release.pay_link
):
return
payment_info = ReleasePaymentInfo(
amount=self.meta.installed_release.cost_sats,
pay_link=self.meta.installed_release.pay_link,
payment_hash=self.meta.installed_release.payment_hash,
)
self.meta.payments = [
p for p in self.meta.payments if p.pay_link != payment_info.pay_link
]
self.meta.payments.append(payment_info)
@classmethod
async def from_github_release(
cls, github_release: GitHubRelease
) -> InstallableExtension | None:
try:
repo, latest_release, config = await cls.fetch_github_repo_info(
github_release.organisation, github_release.repository
)
source_repo = f"{github_release.organisation}/{github_release.repository}"
return InstallableExtension(
id=github_release.id,
name=config.name,
version=latest_release.tag_name,
short_description=config.short_description,
stars=int(repo.stargazers_count),
icon=icon_to_github_url(
source_repo,
config.tile,
),
meta=ExtensionMeta(
latest_release=ExtensionRelease.from_github_release(
source_repo, latest_release
),
),
)
except Exception as e:
logger.warning(e)
return None
@classmethod
def from_explicit_release(cls, e: ExplicitRelease) -> InstallableExtension:
meta = ExtensionMeta(archive=e.archive, dependencies=e.dependencies)
return InstallableExtension(
id=e.id,
name=e.name,
version=e.version,
short_description=e.short_description,
icon=e.icon,
meta=meta,
)
@classmethod
def from_ext_dir(cls, ext_id: str) -> InstallableExtension | None:
try:
conf_path = Path(
settings.lnbits_extensions_path, "extensions", ext_id, "config.json"
)
if not conf_path.is_file():
return None
with open(conf_path, "r+") as json_file:
config_json = json.load(json_file)
version = config_json.get("version", "0.0")
return InstallableExtension(
id=ext_id,
name=config_json.get("name", ext_id),
active=True,
version=version,
short_description=config_json.get("short_description"),
icon=config_json.get("tile"),
meta=ExtensionMeta(
installed_release=ExtensionRelease(
name=ext_id,
version=version,
archive=f"{conf_path}",
source_repo=f"{conf_path}",
min_lnbits_version=config_json.get("min_lnbits_version"),
max_lnbits_version=config_json.get("max_lnbits_version"),
)
),
)
except Exception as e:
logger.warning(e)
return None
@classmethod
async def get_installable_extensions(
cls,
) -> list[InstallableExtension]:
extension_list: list[InstallableExtension] = []
for url in settings.lnbits_extensions_manifests:
try:
manifest = await cls.fetch_manifest(url)
for r in manifest.repos:
ext = await InstallableExtension.from_github_release(r)
if not ext:
continue
existing_ext = next(
(ee for ee in extension_list if ee.id == r.id), None
)
if existing_ext and ext.meta:
existing_ext.check_latest_version(ext.meta.latest_release)
continue
meta = ext.meta or ExtensionMeta()
meta.featured = ext.id in manifest.featured
ext.meta = meta
extension_list += [ext]
for e in manifest.extensions:
release = ExtensionRelease.from_explicit_release(url, e)
existing_ext = next(
(ee for ee in extension_list if ee.id == e.id), None
)
if existing_ext:
existing_ext.check_latest_version(release)
continue
ext = InstallableExtension.from_explicit_release(e)
ext.check_latest_version(release)
meta = ext.meta or ExtensionMeta()
meta.featured = ext.id in manifest.featured
ext.meta = meta
extension_list += [ext]
except Exception as e:
logger.warning(f"Manifest {url} failed with '{e!s}'")
settings.lnbits_all_extensions_ids = {e.id for e in extension_list}
return extension_list
@classmethod
async def get_extension_releases(cls, ext_id: str) -> list[ExtensionRelease]:
extension_releases: list[ExtensionRelease] = []
for url in settings.lnbits_extensions_manifests:
try:
manifest = await cls.fetch_manifest(url)
for r in manifest.repos:
if r.id != ext_id:
continue
repo_releases = await ExtensionRelease.get_github_releases(
r.organisation, r.repository
)
extension_releases += repo_releases
for e in manifest.extensions:
if e.id != ext_id:
continue
explicit_release = ExtensionRelease.from_explicit_release(url, e)
await explicit_release.check_payment_requirements()
extension_releases.append(explicit_release)
except Exception as e:
logger.warning(f"Manifest {url} failed with '{e!s}'")
return extension_releases
@classmethod
async def get_extension_release(
cls, ext_id: str, source_repo: str, archive: str, version: str
) -> ExtensionRelease | None:
all_releases: list[ExtensionRelease] = (
await InstallableExtension.get_extension_releases(ext_id)
)
selected_release = [
r
for r in all_releases
if r.archive == archive
and r.source_repo == source_repo
and r.version == version
]
return selected_release[0] if len(selected_release) != 0 else None
@classmethod
async def fetch_github_repo_info(
cls, org: str, repository: str
) -> tuple[GitHubRepo, GitHubRepoRelease, ExtensionConfig]:
repo_url = f"https://api.github.com/repos/{org}/{repository}"
error_msg = "Cannot fetch extension repo"
repo = await github_api_get(repo_url, error_msg)
github_repo = GitHubRepo.parse_obj(repo)
lates_release_url = (
f"https://api.github.com/repos/{org}/{repository}/releases/latest"
)
error_msg = "Cannot fetch extension releases"
latest_release: Any = await github_api_get(lates_release_url, error_msg)
config_url = f"https://raw.githubusercontent.com/{org}/{repository}/{github_repo.default_branch}/config.json"
error_msg = "Cannot fetch config for extension"
config = await github_api_get(config_url, error_msg)
return (
github_repo,
GitHubRepoRelease.parse_obj(latest_release),
ExtensionConfig.parse_obj(config),
)
@classmethod
async def fetch_manifest(cls, url) -> Manifest:
error_msg = "Cannot fetch extensions manifest"
manifest = await github_api_get(url, error_msg)
return Manifest.parse_obj(manifest)
class CreateExtension(BaseModel):
ext_id: str
archive: str
source_repo: str
version: str
cost_sats: int | None = 0
payment_hash: str | None = None
class ExtensionDetailsRequest(BaseModel):
ext_id: str
source_repo: str
version: str
async def github_api_get(url: str, error_msg: str | None) -> Any:
headers = {"User-Agent": settings.user_agent}
if settings.lnbits_ext_github_token:
headers["Authorization"] = f"Bearer {settings.lnbits_ext_github_token}"
async with httpx.AsyncClient(headers=headers) as client:
resp = await client.get(url)
if resp.status_code != 200:
logger.warning(f"{error_msg} ({url}): {resp.text}")
resp.raise_for_status()
return resp.json()
def icon_to_github_url(source_repo: str, path: str | None) -> str:
if not path:
return ""
_, _, *rest = path.split("/")
tail = "/".join(rest)
return f"https://github.com/{source_repo}/raw/main/{tail}"
+20
View File
@@ -0,0 +1,20 @@
from typing import Optional
from pydantic import BaseModel
class CreateLnurl(BaseModel):
description_hash: str
callback: str
amount: int
comment: Optional[str] = None
description: Optional[str] = None
unit: Optional[str] = None
class CreateLnurlAuth(BaseModel):
callback: str
class PayLnurlWData(BaseModel):
lnurl_w: str
+53
View File
@@ -0,0 +1,53 @@
from __future__ import annotations
from typing import Callable
from pydantic import BaseModel
def _do_nothing(*_):
pass
class CoreAppExtra:
register_new_ext_routes: Callable = _do_nothing
register_new_ratelimiter: Callable
class ConversionData(BaseModel):
from_: str = "sat"
amount: float
to: str = "usd"
class Callback(BaseModel):
callback: str
class BalanceDelta(BaseModel):
lnbits_balance_sats: int
node_balance_sats: int
@property
def delta_sats(self) -> int:
return int(self.lnbits_balance_sats - self.node_balance_sats)
class SimpleStatus(BaseModel):
success: bool
message: str
class SimpleItem(BaseModel):
id: str
name: str
class DbVersion(BaseModel):
db: str
version: int
class Image(BaseModel):
filename: str
directory: str = "library"
+65
View File
@@ -0,0 +1,65 @@
from enum import Enum
from pydantic import BaseModel
class NotificationType(Enum):
server_status = "server_status"
settings_update = "settings_update"
balance_update = "balance_update"
watchdog_check = "watchdog_check"
balance_delta = "balance_delta"
server_start_stop = "server_start_stop"
incoming_payment = "incoming_payment"
outgoing_payment = "outgoing_payment"
text_message = "text_message"
class NotificationMessage(BaseModel):
message_type: NotificationType
values: dict
NOTIFICATION_TEMPLATES = {
"text_message": "{message}",
"server_status": """*SERVER STATUS*
*Up time*: `{up_time}`.
*Accounts*: `{accounts_count}`.
*Wallets*: `{wallets_count}`.
*In/Out payments*: `{in_payments_count}`/`{out_payments_count}`.
*Pending payments*: `{pending_payments_count}`.
*Failed payments*: `{failed_payments_count}`.
*LNbits balance*: `{lnbits_balance_sats}` sats.
*Node balance*: `{node_balance_sats}` sats.""",
"server_start_stop": """*SERVER*
{message}
*Time*: `{up_time}` seconds.
""",
"settings_update": """*SETTINGS UPDATED*
User: `{username}`.
""",
"balance_update": """*WALLET CREDIT/DEBIT*
Wallet `{wallet_name}` balance updated with `{amount}` sats.
*Current balance*: `{balance}` sats.
*Wallet ID*: `{wallet_id}`
""",
"watchdog_check": """*WATCHDOG BALANCE CHECK*
*Delta*: `{delta_sats}` sats.
*LNbits balance*: `{lnbits_balance_sats}` sats.
*Node balance*: `{node_balance_sats}` sats.
*Switching to Void Wallet*: `{switch_to_void_wallet}`.
""",
"balance_delta": """*BALANCE DELTA CHANGED*
*New delta*: `{delta_sats}` sats.
*Old delta*: `{old_delta_sats}` sats.
*LNbits balance*: `{lnbits_balance_sats}` sats.
*Node balance*: `{node_balance_sats}` sats.
""",
"outgoing_payment": """*PAYMENT SENT*
*Amount*: {fiat_value_fmt}`{amount_sats}`*sats*.
*Wallet*: `{wallet_name}` ({wallet_id}).""",
"incoming_payment": """*PAYMENT RECEIVED*
*Amount*: {fiat_value_fmt}`{amount_sats}`*sats*.
*Wallet*: `{wallet_name}` ({wallet_id}).
""",
}
+222
View File
@@ -0,0 +1,222 @@
from __future__ import annotations
from datetime import datetime, timezone
from enum import Enum
from typing import Literal
from fastapi import Query
from pydantic import BaseModel, Field, validator
from lnbits.db import FilterModel
from lnbits.utils.exchange_rates import allowed_currencies
from lnbits.wallets import get_funding_source
from lnbits.wallets.base import (
PaymentFailedStatus,
PaymentPendingStatus,
PaymentStatus,
PaymentSuccessStatus,
)
class PaymentState(str, Enum):
PENDING = "pending"
SUCCESS = "success"
FAILED = "failed"
def __str__(self) -> str:
return self.value
class PaymentExtra(BaseModel):
comment: str | None = None
success_action: str | None = None
lnurl_response: str | None = None
class PayInvoice(BaseModel):
payment_request: str
description: str | None = None
max_sat: int | None = None
extra: dict | None = {}
class CreatePayment(BaseModel):
wallet_id: str
payment_hash: str
bolt11: str
amount_msat: int
memo: str
extra: dict | None = {}
preimage: str | None = None
expiry: datetime | None = None
webhook: str | None = None
fee: int = 0
class Payment(BaseModel):
checking_id: str
payment_hash: str
wallet_id: str
amount: int
fee: int
bolt11: str
status: str = PaymentState.PENDING
memo: str | None = None
expiry: datetime | None = None
webhook: str | None = None
webhook_status: int | None = None
preimage: str | None = None
tag: str | None = None
extension: str | None = None
time: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
extra: dict = {}
@property
def pending(self) -> bool:
return self.status == PaymentState.PENDING.value
@property
def success(self) -> bool:
return self.status == PaymentState.SUCCESS.value
@property
def failed(self) -> bool:
return self.status == PaymentState.FAILED.value
@property
def msat(self) -> int:
return self.amount
@property
def sat(self) -> int:
return self.amount // 1000
@property
def is_in(self) -> bool:
return self.amount > 0
@property
def is_out(self) -> bool:
return self.amount < 0
@property
def is_expired(self) -> bool:
return self.expiry < datetime.now(timezone.utc) if self.expiry else False
@property
def is_internal(self) -> bool:
return self.checking_id.startswith("internal_")
async def check_status(self) -> PaymentStatus:
if self.is_internal:
if self.success:
return PaymentSuccessStatus()
if self.failed:
return PaymentFailedStatus()
return PaymentPendingStatus()
funding_source = get_funding_source()
if self.is_out:
status = await funding_source.get_payment_status(self.checking_id)
else:
status = await funding_source.get_invoice_status(self.checking_id)
return status
class PaymentFilters(FilterModel):
__search_fields__ = ["memo", "amount", "wallet_id", "tag", "status", "time"]
__sort_fields__ = ["created_at", "amount", "fee", "memo", "time", "tag"]
status: str | None
tag: str | None
checking_id: str | None
amount: int
fee: int
memo: str | None
time: datetime
preimage: str | None
payment_hash: str | None
wallet_id: str | None
class PaymentDataPoint(BaseModel):
date: datetime
count: int
max_amount: int
min_amount: int
average_amount: int
total_amount: int
max_fee: int
min_fee: int
average_fee: int
total_fee: int
PaymentCountField = Literal["status", "tag", "extension", "wallet_id"]
class PaymentCountStat(BaseModel):
field: str = ""
total: float = 0
class PaymentWalletStats(BaseModel):
wallet_id: str = ""
wallet_name: str = ""
user_id: str = ""
payments_count: int
balance: float = 0
class PaymentDailyStats(BaseModel):
date: datetime
balance: float = 0
balance_in: float | None = 0
balance_out: float | None = 0
payments_count: int = 0
count_in: int | None = 0
count_out: int | None = 0
fee: float = 0
class PaymentHistoryPoint(BaseModel):
date: datetime
income: int
spending: int
balance: int
class DecodePayment(BaseModel):
data: str
filter_fields: list[str] | None = []
class CreateInvoice(BaseModel):
unit: str = "sat"
internal: bool = False
out: bool = True
amount: float = Query(None, ge=0)
memo: str | None = Query(None, max_length=640)
description_hash: str | None = None
unhashed_description: str | None = None
expiry: int | None = None
extra: dict | None = None
webhook: str | None = None
bolt11: str | None = None
lnurl_callback: str | None = None
@validator("unit")
@classmethod
def unit_is_from_allowed_currencies(cls, v):
if v != "sat" and v not in allowed_currencies():
raise ValueError("The provided unit is not supported")
return v
class PaymentsStatusCount(BaseModel):
incoming: int = 0
outgoing: int = 0
failed: int = 0
pending: int = 0
+37
View File
@@ -0,0 +1,37 @@
"""Keycloak SSO Login Helper
"""
from typing import Optional
import httpx
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
class KeycloakSSO(SSOBase):
"""Class providing login via Keycloak OAuth"""
provider = "keycloak"
scope = ["openid", "email", "profile"]
discovery_url = ""
async def openid_from_response(
self, response: dict, session: Optional["httpx.AsyncClient"] = None
) -> OpenID:
"""Return OpenID from user information provided by Keycloak"""
return OpenID(
email=response.get("email", ""),
provider=self.provider,
id=response.get("sub"),
first_name=response.get("given_name"),
last_name=response.get("family_name"),
display_name=response.get("name"),
picture=response.get("picture"),
)
async def get_discovery_document(self) -> DiscoveryDocument:
"""Get document containing handy urls"""
async with httpx.AsyncClient() as session:
response = await session.get(self.discovery_url)
content = response.json()
return content
+9
View File
@@ -0,0 +1,9 @@
from pydantic import BaseModel
class TinyURL(BaseModel):
id: str
url: str
endless: bool
wallet: str
time: float
+289
View File
@@ -0,0 +1,289 @@
from __future__ import annotations
from datetime import datetime, timezone
from uuid import UUID
from fastapi import Query
from passlib.context import CryptContext
from pydantic import BaseModel, Field
from lnbits.core.models.misc import SimpleItem
from lnbits.db import FilterModel
from lnbits.helpers import is_valid_email_address, is_valid_pubkey, is_valid_username
from lnbits.settings import settings
from .wallets import Wallet
class UserExtra(BaseModel):
email_verified: bool | None = False
first_name: str | None = None
last_name: str | None = None
display_name: str | None = None
picture: str | None = None
# Auth provider, possible values:
# - "env": the user was created automatically by the system
# - "lnbits": the user was created via register form (username/pass or user_id only)
# - "google | github | ...": the user was created using an SSO provider
provider: str | None = "lnbits" # auth provider
class EndpointAccess(BaseModel):
path: str
name: str
read: bool = False
write: bool = False
def supports_method(self, method: str) -> bool:
# all http methods
if method in ["GET", "OPTIONS", "HEAD"]:
return self.read
if method in ["POST", "PUT", "PATCH", "DELETE"]:
return self.write
return False
class AccessControlList(BaseModel):
id: str
name: str
endpoints: list[EndpointAccess] = []
token_id_list: list[SimpleItem] = []
def get_endpoint(self, path: str) -> EndpointAccess | None:
for e in self.endpoints:
if e.path == path:
return e
return None
def get_token_by_id(self, token_id: str) -> SimpleItem | None:
for t in self.token_id_list:
if t.id == token_id:
return t
return None
def delete_token_by_id(self, token_id: str):
self.token_id_list = [t for t in self.token_id_list if t.id != token_id]
class UserAcls(BaseModel):
id: str
access_control_list: list[AccessControlList] = []
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
def get_acl_by_id(self, acl_id: str) -> AccessControlList | None:
for acl in self.access_control_list:
if acl.id == acl_id:
return acl
return None
def delete_acl_by_id(self, acl_id: str):
self.access_control_list = [
acl for acl in self.access_control_list if acl.id != acl_id
]
def get_acl_by_token_id(self, token_id: str) -> AccessControlList | None:
for acl in self.access_control_list:
if acl.get_token_by_id(token_id):
return acl
return None
class Account(BaseModel):
id: str
username: str | None = None
password_hash: str | None = None
pubkey: str | None = None
email: str | None = None
extra: UserExtra = UserExtra()
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
is_super_user: bool = Field(default=False, no_database=True)
is_admin: bool = Field(default=False, no_database=True)
def __init__(self, **data):
super().__init__(**data)
self.is_super_user = settings.is_super_user(self.id)
self.is_admin = settings.is_admin_user(self.id)
def hash_password(self, password: str) -> str:
"""sets and returns the hashed password"""
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
self.password_hash = pwd_context.hash(password)
return self.password_hash
def verify_password(self, password: str) -> bool:
"""returns True if the password matches the hash"""
if not self.password_hash:
return False
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
return pwd_context.verify(password, self.password_hash)
def validate_fields(self):
if self.username and not is_valid_username(self.username):
raise ValueError("Invalid username.")
if self.email and not is_valid_email_address(self.email):
raise ValueError("Invalid email.")
if self.pubkey and not is_valid_pubkey(self.pubkey):
raise ValueError("Invalid pubkey.")
user_uuid4 = UUID(hex=self.id, version=4)
if user_uuid4.hex != self.id:
raise ValueError("User ID is not valid UUID4 hex string.")
class AccountOverview(Account):
transaction_count: int | None = 0
wallet_count: int | None = 0
balance_msat: int | None = 0
last_payment: datetime | None = None
class AccountFilters(FilterModel):
__search_fields__ = ["user", "email", "username", "pubkey", "wallet_id"]
__sort_fields__ = [
"balance_msat",
"email",
"username",
"transaction_count",
"wallet_count",
"last_payment",
]
email: str | None = None
user: str | None = None
username: str | None = None
pubkey: str | None = None
wallet_id: str | None = None
class User(BaseModel):
id: str
created_at: datetime
updated_at: datetime
email: str | None = None
username: str | None = None
pubkey: str | None = None
extensions: list[str] = []
wallets: list[Wallet] = []
admin: bool = False
super_user: bool = False
has_password: bool = False
extra: UserExtra = UserExtra()
@property
def wallet_ids(self) -> list[str]:
return [wallet.id for wallet in self.wallets]
def get_wallet(self, wallet_id: str) -> Wallet | None:
w = [wallet for wallet in self.wallets if wallet.id == wallet_id]
return w[0] if w else None
@classmethod
def is_extension_for_user(cls, ext: str, user: str) -> bool:
if ext not in settings.lnbits_admin_extensions:
return True
if user == settings.super_user:
return True
if user in settings.lnbits_admin_users:
return True
return False
class RegisterUser(BaseModel):
email: str | None = Query(default=None)
username: str = Query(default=..., min_length=2, max_length=20)
password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: str = Query(default=..., min_length=8, max_length=50)
class CreateUser(BaseModel):
id: str | None = Query(default=None)
email: str | None = Query(default=None)
username: str | None = Query(default=None, min_length=2, max_length=20)
password: str | None = Query(default=None, min_length=8, max_length=50)
password_repeat: str | None = Query(default=None, min_length=8, max_length=50)
pubkey: str = Query(default=None, max_length=64)
extensions: list[str] | None = None
extra: UserExtra | None = None
class UpdateUser(BaseModel):
user_id: str
email: str | None = Query(default=None)
username: str | None = Query(default=..., min_length=2, max_length=20)
extra: UserExtra | None = None
class UpdateUserPassword(BaseModel):
user_id: str
password_old: str | None = None
password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: str = Query(default=..., min_length=8, max_length=50)
username: str = Query(default=..., min_length=2, max_length=20)
class UpdateUserPubkey(BaseModel):
user_id: str
pubkey: str = Query(default=..., max_length=64)
class ResetUserPassword(BaseModel):
reset_key: str
password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: str = Query(default=..., min_length=8, max_length=50)
class UpdateSuperuserPassword(BaseModel):
username: str = Query(default=..., min_length=2, max_length=20)
password: str = Query(default=..., min_length=8, max_length=50)
password_repeat: str = Query(default=..., min_length=8, max_length=50)
class LoginUsr(BaseModel):
usr: str
class LoginUsernamePassword(BaseModel):
username: str
password: str
class AccessTokenPayload(BaseModel):
sub: str
usr: str | None = None
email: str | None = None
auth_time: int | None = 0
api_token_id: str | None = None
class UpdateBalance(BaseModel):
id: str
amount: int
class ApiTokenRequest(BaseModel):
acl_id: str
token_name: str
password: str
expiration_time_minutes: int
class ApiTokenResponse(BaseModel):
id: str
api_token: str
class UpdateAccessControlList(AccessControlList):
password: str
class DeleteAccessControlList(BaseModel):
id: str
password: str
class DeleteTokenRequest(BaseModel):
id: str
acl_id: str
password: str
+85
View File
@@ -0,0 +1,85 @@
from __future__ import annotations
import hashlib
import hmac
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from ecdsa import SECP256k1, SigningKey
from pydantic import BaseModel, Field
from lnbits.helpers import url_for
from lnbits.lnurl import encode as lnurl_encode
from lnbits.settings import settings
class BaseWallet(BaseModel):
id: str
name: str
adminkey: str
inkey: str
balance_msat: int
class WalletExtra(BaseModel):
icon: str = "flash_on"
color: str = "primary"
class Wallet(BaseModel):
id: str
user: str
name: str
adminkey: str
inkey: str
deleted: bool = False
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
currency: str | None = None
balance_msat: int = Field(default=0, no_database=True)
extra: WalletExtra = WalletExtra()
@property
def balance(self) -> int:
return int(self.balance_msat // 1000)
@property
def withdrawable_balance(self) -> int:
return self.balance_msat - settings.fee_reserve(self.balance_msat)
@property
def lnurlwithdraw_full(self) -> str:
url = url_for("/withdraw", external=True, usr=self.user, wal=self.id)
try:
return lnurl_encode(url)
except Exception:
return ""
def lnurlauth_key(self, domain: str) -> SigningKey:
hashing_key = hashlib.sha256(self.id.encode()).digest()
linking_key = hmac.digest(hashing_key, domain.encode(), "sha256")
return SigningKey.from_string(
linking_key, curve=SECP256k1, hashfunc=hashlib.sha256
)
class CreateWallet(BaseModel):
name: str | None = None
class KeyType(Enum):
admin = 0
invoice = 1
invalid = 2
# backwards compatibility
def __eq__(self, other):
return self.value == other
@dataclass
class WalletTypeInfo:
key_type: KeyType
wallet: Wallet
+15
View File
@@ -0,0 +1,15 @@
from datetime import datetime
from pydantic import BaseModel
class CreateWebPushSubscription(BaseModel):
subscription: str
class WebPushSubscription(BaseModel):
endpoint: str
user: str
data: str
host: str
timestamp: datetime
-507
View File
@@ -1,507 +0,0 @@
import asyncio
import json
from io import BytesIO
from typing import Dict, List, Optional, Tuple, TypedDict
from urllib.parse import parse_qs, urlparse
import httpx
from fastapi import Depends, WebSocket
from lnurl import LnurlErrorResponse
from lnurl import decode as decode_lnurl
from loguru import logger
from lnbits import bolt11
from lnbits.db import Connection
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.helpers import url_for
from lnbits.settings import (
FAKE_WALLET,
EditableSettings,
SuperSettings,
get_wallet_class,
readonly_variables,
send_admin_user_to_saas,
settings,
)
from lnbits.wallets.base import PaymentResponse, PaymentStatus
from . import db
from .crud import (
check_internal,
check_internal_pending,
create_account,
create_admin_settings,
create_payment,
create_wallet,
delete_wallet_payment,
get_account,
get_super_settings,
get_wallet,
get_wallet_payment,
update_payment_details,
update_payment_status,
update_super_user,
)
from .models import Payment
class PaymentFailure(Exception):
pass
class InvoiceFailure(Exception):
pass
async def create_invoice(
*,
wallet_id: str,
amount: int, # in satoshis
memo: str,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
expiry: Optional[int] = None,
extra: Optional[Dict] = None,
webhook: Optional[str] = None,
internal: Optional[bool] = False,
conn: Optional[Connection] = None,
) -> Tuple[str, str]:
if not amount > 0:
raise InvoiceFailure("Amountless invoices not supported.")
invoice_memo = None if description_hash else memo
# use the fake wallet if the invoice is for internal use only
wallet = FAKE_WALLET if internal else get_wallet_class()
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
amount=amount,
memo=invoice_memo,
description_hash=description_hash,
unhashed_description=unhashed_description,
expiry=expiry or settings.lightning_invoice_expiry,
)
if not ok:
raise InvoiceFailure(error_message or "unexpected backend error.")
invoice = bolt11.decode(payment_request)
amount_msat = amount * 1000
await create_payment(
wallet_id=wallet_id,
checking_id=checking_id,
payment_request=payment_request,
payment_hash=invoice.payment_hash,
amount=amount_msat,
memo=memo,
extra=extra,
webhook=webhook,
conn=conn,
)
return invoice.payment_hash, payment_request
async def pay_invoice(
*,
wallet_id: str,
payment_request: str,
max_sat: Optional[int] = None,
extra: Optional[Dict] = None,
description: str = "",
conn: Optional[Connection] = None,
) -> str:
"""
Pay a Lightning invoice.
First, we create a temporary payment in the database with fees set to the reserve fee.
We then check whether the balance of the payer would go negative.
We then attempt to pay the invoice through the backend.
If the payment is successful, we update the payment in the database with the payment details.
If the payment is unsuccessful, we delete the temporary payment.
If the payment is still in flight, we hope that some other process will regularly check for the payment.
"""
invoice = bolt11.decode(payment_request)
fee_reserve_msat = fee_reserve(invoice.amount_msat)
async with (db.reuse_conn(conn) if conn else db.connect()) as conn:
temp_id = invoice.payment_hash
internal_id = f"internal_{invoice.payment_hash}"
if invoice.amount_msat == 0:
raise ValueError("Amountless invoices not supported.")
if max_sat and invoice.amount_msat > max_sat * 1000:
raise ValueError("Amount in invoice is too high.")
# put all parameters that don't change here
class PaymentKwargs(TypedDict):
wallet_id: str
payment_request: str
payment_hash: str
amount: int
memo: str
extra: Optional[Dict]
payment_kwargs: PaymentKwargs = PaymentKwargs(
wallet_id=wallet_id,
payment_request=payment_request,
payment_hash=invoice.payment_hash,
amount=-invoice.amount_msat,
memo=description or invoice.description or "",
extra=extra,
)
# we check if an internal invoice exists that has already been paid (not pending anymore)
if not await check_internal_pending(invoice.payment_hash, conn=conn):
raise PaymentFailure("Internal invoice already paid.")
# check_internal() returns the checking_id of the invoice we're waiting for (pending only)
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
if internal_checking_id:
logger.debug(f"creating temporary internal payment with id {internal_id}")
# create a new payment from this wallet
await create_payment(
checking_id=internal_id,
fee=0,
pending=False,
conn=conn,
**payment_kwargs,
)
else:
logger.debug(f"creating temporary payment with id {temp_id}")
# create a temporary payment here so we can check if
# the balance is enough in the next step
try:
await create_payment(
checking_id=temp_id,
fee=-fee_reserve_msat,
conn=conn,
**payment_kwargs,
)
except Exception as e:
logger.error(f"could not create temporary payment: {e}")
# happens if the same wallet tries to pay an invoice twice
raise PaymentFailure("Could not make payment.")
# do the balance check
wallet = await get_wallet(wallet_id, conn=conn)
assert wallet, "Wallet for balancecheck could not be fetched"
if wallet.balance_msat < 0:
logger.debug("balance is too low, deleting temporary payment")
if not internal_checking_id and wallet.balance_msat > -fee_reserve_msat:
raise PaymentFailure(
f"You must reserve at least ({round(fee_reserve_msat/1000)} sat) to cover potential routing fees."
)
raise PermissionError("Insufficient balance.")
if internal_checking_id:
logger.debug(f"marking temporary payment as not pending {internal_checking_id}")
# mark the invoice from the other side as not pending anymore
# so the other side only has access to his new money when we are sure
# the payer has enough to deduct from
async with db.connect() as conn:
await update_payment_status(
checking_id=internal_checking_id, pending=False, conn=conn
)
# notify receiver asynchronously
from lnbits.tasks import internal_invoice_queue
logger.debug(f"enqueuing internal invoice {internal_checking_id}")
await internal_invoice_queue.put(internal_checking_id)
else:
logger.debug(f"backend: sending payment {temp_id}")
# actually pay the external invoice
WALLET = get_wallet_class()
payment: PaymentResponse = await WALLET.pay_invoice(
payment_request, fee_reserve_msat
)
if payment.checking_id and payment.checking_id != temp_id:
logger.warning(
f"backend sent unexpected checking_id (expected: {temp_id} got: {payment.checking_id})"
)
logger.debug(f"backend: pay_invoice finished {temp_id}")
if payment.checking_id and payment.ok is not False:
# payment.ok can be True (paid) or None (pending)!
logger.debug(f"updating payment {temp_id}")
async with db.connect() as conn:
await update_payment_details(
checking_id=temp_id,
pending=payment.ok is not True,
fee=payment.fee_msat,
preimage=payment.preimage,
new_checking_id=payment.checking_id,
conn=conn,
)
logger.debug(f"payment successful {payment.checking_id}")
elif payment.checking_id is None and payment.ok is False:
# payment failed
logger.warning("backend sent payment failure")
async with db.connect() as conn:
logger.debug(f"deleting temporary payment {temp_id}")
await delete_wallet_payment(temp_id, wallet_id, conn=conn)
raise PaymentFailure(
f"Payment failed: {payment.error_message}"
or "Payment failed, but backend didn't give us an error message."
)
else:
logger.warning(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
return invoice.payment_hash
async def redeem_lnurl_withdraw(
wallet_id: str,
lnurl_request: str,
memo: Optional[str] = None,
extra: Optional[Dict] = None,
wait_seconds: int = 0,
conn: Optional[Connection] = None,
) -> None:
if not lnurl_request:
return None
res = {}
async with httpx.AsyncClient() as client:
lnurl = decode_lnurl(lnurl_request)
r = await client.get(str(lnurl))
res = r.json()
try:
_, payment_request = await create_invoice(
wallet_id=wallet_id,
amount=int(res["maxWithdrawable"] / 1000),
memo=memo or res["defaultDescription"] or "",
extra=extra,
conn=conn,
)
except:
logger.warning(
f"failed to create invoice on redeem_lnurl_withdraw from {lnurl}. params: {res}"
)
return None
if wait_seconds:
await asyncio.sleep(wait_seconds)
params = {"k1": res["k1"], "pr": payment_request}
try:
params["balanceNotify"] = url_for(
f"/withdraw/notify/{urlparse(lnurl_request).netloc}",
external=True,
wal=wallet_id,
)
except Exception:
pass
async with httpx.AsyncClient() as client:
try:
await client.get(res["callback"], params=params)
except Exception:
pass
async def perform_lnurlauth(
callback: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
conn: Optional[Connection] = None,
) -> Optional[LnurlErrorResponse]:
cb = urlparse(callback)
k1 = bytes.fromhex(parse_qs(cb.query)["k1"][0])
key = wallet.wallet.lnurlauth_key(cb.netloc)
def int_to_bytes_suitable_der(x: int) -> bytes:
"""for strict DER we need to encode the integer with some quirks"""
b = x.to_bytes((x.bit_length() + 7) // 8, "big")
if len(b) == 0:
# ensure there's at least one byte when the int is zero
return bytes([0])
if b[0] & 0x80 != 0:
# ensure it doesn't start with a 0x80 and so it isn't
# interpreted as a negative number
return bytes([0]) + b
return b
def encode_strict_der(r: int, s: int, order: int):
# if s > order/2 verification will fail sometimes
# so we must fix it here see:
# https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147
if s > order // 2:
s = order - s
# now we do the strict DER encoding copied from
# https://github.com/KiriKiri/bip66 (without any checks)
r_temp = int_to_bytes_suitable_der(r)
s_temp = int_to_bytes_suitable_der(s)
r_len = len(r_temp)
s_len = len(s_temp)
sign_len = 6 + r_len + s_len
signature = BytesIO()
signature.write(0x30.to_bytes(1, "big", signed=False))
signature.write((sign_len - 2).to_bytes(1, "big", signed=False))
signature.write(0x02.to_bytes(1, "big", signed=False))
signature.write(r_len.to_bytes(1, "big", signed=False))
signature.write(r_temp)
signature.write(0x02.to_bytes(1, "big", signed=False))
signature.write(s_len.to_bytes(1, "big", signed=False))
signature.write(s_temp)
return signature.getvalue()
sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der)
async with httpx.AsyncClient() as client:
assert key.verifying_key, "LNURLauth verifying_key does not exist"
r = await client.get(
callback,
params={
"k1": k1.hex(),
"key": key.verifying_key.to_string("compressed").hex(),
"sig": sig.hex(),
},
)
try:
resp = json.loads(r.text)
if resp["status"] == "OK":
return None
return LnurlErrorResponse(reason=resp["reason"])
except (KeyError, json.decoder.JSONDecodeError):
return LnurlErrorResponse(
reason=r.text[:200] + "..." if len(r.text) > 200 else r.text
)
async def check_transaction_status(
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
) -> PaymentStatus:
payment: Optional[Payment] = await get_wallet_payment(
wallet_id, payment_hash, conn=conn
)
if not payment:
return PaymentStatus(None)
if not payment.pending:
# note: before, we still checked the status of the payment again
return PaymentStatus(True, fee_msat=payment.fee)
status: PaymentStatus = await payment.check_status()
return status
# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
def fee_reserve(amount_msat: int) -> int:
reserve_min = settings.lnbits_reserve_fee_min
reserve_percent = settings.lnbits_reserve_fee_percent
return max(int(reserve_min), int(amount_msat * reserve_percent / 100.0))
async def update_wallet_balance(wallet_id: str, amount: int):
payment_hash, _ = await create_invoice(
wallet_id=wallet_id,
amount=amount,
memo="Admin top up",
internal=True,
)
async with db.connect() as conn:
checking_id = await check_internal(payment_hash, conn=conn)
assert checking_id, "newly created checking_id cannot be retrieved"
await update_payment_status(checking_id=checking_id, pending=False, conn=conn)
# notify receiver asynchronously
from lnbits.tasks import internal_invoice_queue
await internal_invoice_queue.put(checking_id)
async def check_admin_settings():
if settings.lnbits_admin_ui:
settings_db = await get_super_settings()
if not settings_db:
# create new settings if table is empty
logger.warning("Settings DB empty. Inserting default settings.")
settings_db = await init_admin_settings(settings.super_user)
logger.warning("Initialized settings from enviroment variables.")
if settings.super_user and settings.super_user != settings_db.super_user:
# .env super_user overwrites DB super_user
settings_db = await update_super_user(settings.super_user)
update_cached_settings(settings_db.dict())
# printing settings for debugging
logger.debug("Admin settings:")
for key, value in settings.dict(exclude_none=True).items():
logger.debug(f"{key}: {value}")
admin_url = f"{settings.lnbits_baseurl}wallet?usr={settings.super_user}"
logger.success(f"✔️ Access super user account at: {admin_url}")
# callback for saas
if (
settings.lnbits_saas_callback
and settings.lnbits_saas_secret
and settings.lnbits_saas_instance_id
):
send_admin_user_to_saas()
def update_cached_settings(sets_dict: dict):
for key, value in sets_dict.items():
if key not in readonly_variables:
try:
setattr(settings, key, value)
except:
logger.error(f"error overriding setting: {key}, value: {value}")
if "super_user" in sets_dict:
setattr(settings, "super_user", sets_dict["super_user"])
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
account = None
if super_user:
account = await get_account(super_user)
if not account:
account = await create_account()
super_user = account.id
if not account.wallets or len(account.wallets) == 0:
await create_wallet(user_id=account.id)
editable_settings = EditableSettings.from_dict(settings.dict())
return await create_admin_settings(account.id, editable_settings.dict())
class WebsocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
logger.debug(websocket)
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_data(self, message: str, item_id: str):
for connection in self.active_connections:
if connection.path_params["item_id"] == item_id:
await connection.send_text(message)
websocketManager = WebsocketConnectionManager()
async def websocketUpdater(item_id, data):
return await websocketManager.send_data(f"{data}", item_id)
+65
View File
@@ -0,0 +1,65 @@
from .funding_source import (
get_balance_delta,
switch_to_voidwallet,
)
from .lnurl import perform_lnurlauth, redeem_lnurl_withdraw
from .notifications import enqueue_notification, send_payment_notification
from .payments import (
calculate_fiat_amounts,
check_transaction_status,
check_wallet_limits,
create_invoice,
fee_reserve,
fee_reserve_total,
pay_invoice,
service_fee,
update_pending_payments,
update_wallet_balance,
)
from .settings import (
check_webpush_settings,
update_cached_settings,
)
from .users import (
check_admin_settings,
create_user_account,
create_user_account_no_ckeck,
update_user_account,
update_user_extensions,
)
from .websockets import websocket_manager, websocket_updater
__all__ = [
# funding source
"get_balance_delta",
"switch_to_voidwallet",
# lnurl
"redeem_lnurl_withdraw",
"perform_lnurlauth",
# notifications
"enqueue_notification",
"send_payment_notification",
# payments
"calculate_fiat_amounts",
"check_transaction_status",
"check_wallet_limits",
"create_invoice",
"fee_reserve",
"fee_reserve_total",
"pay_invoice",
"service_fee",
"update_pending_payments",
"update_wallet_balance",
# settings
"check_webpush_settings",
"update_cached_settings",
# users
"check_admin_settings",
"create_user_account",
"create_user_account_no_ckeck",
"update_user_account",
"update_user_extensions",
# websockets
"websocket_manager",
"websocket_updater",
]

Some files were not shown because too many files have changed in this diff Show More